diff --git a/算法周更班/class_2022_10_1_week/Code01_CriticalConnectionsInANetwork.java b/算法周更班/class_2022_10_1_week/Code01_CriticalConnectionsInANetwork.java new file mode 100644 index 0000000..b52368c --- /dev/null +++ b/算法周更班/class_2022_10_1_week/Code01_CriticalConnectionsInANetwork.java @@ -0,0 +1,64 @@ +package class_2022_10_1_week; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// 力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号 +// 它们之间以「服务器到服务器」点对点的形式相互连接组成了一个内部集群 +// 其中连接 connections 是无向的 +// 从形式上讲,connections[i] = [a, b] 表示服务器 a 和 b 之间形成连接 +// 任何服务器都可以直接或者间接地通过网络到达任何其他服务器。 +// "关键连接"是在该集群中的重要连接,也就是说,假如我们将它移除 +// 便会导致某些服务器无法访问其他服务器。 +// 请你以任意顺序返回该集群内的所有"关键连接" +// 测试链接 : https://leetcode.cn/problems/critical-connections-in-a-network/ +public class Code01_CriticalConnectionsInANetwork { + + public static int[] dfn = new int[100010]; + + public static int[] low = new int[100010]; + + public static int dfnCnt = 0; + + public static List> criticalConnections(int n, List> connections) { + ArrayList> graph = new ArrayList<>(); + for (int i = 0; i < n; i++) { + graph.add(new ArrayList<>()); + } + for (List edge : connections) { + graph.get(edge.get(0)).add(edge.get(1)); + graph.get(edge.get(1)).add(edge.get(0)); + } + Arrays.fill(dfn, 0, n, 0); + Arrays.fill(low, 0, n, 0); + dfnCnt = 0; + List> ans = new ArrayList<>(); + tarjan(0, -1, graph, ans); + return ans; + } + + // tarjan dfs过程 + // 点的编号是cur,dfn X low X + public static void tarjan(int cur, int father, + ArrayList> graph, + List> ans) { + // 第一次来到cur + // 分配dfn、low序号 + dfn[cur] = low[cur] = ++dfnCnt; + for (Integer next : graph.get(cur)) { + if (next != father) { + if (dfn[next] == 0) { // 下级的节点没跑过,就去跑 + tarjan(next, cur, graph, ans); + low[cur] = Math.min(low[cur], low[next]); + } else { // 下级的节点跑过了,直接更新low + low[cur] = Math.min(low[cur], dfn[next]); + } + } + } + if (low[cur] == dfn[cur] && cur != 0) { + ans.add(Arrays.asList(father, cur)); + } + } + +} diff --git a/算法周更班/class_2022_10_1_week/Code02_CaptureStrongHold.java b/算法周更班/class_2022_10_1_week/Code02_CaptureStrongHold.java new file mode 100644 index 0000000..5d1e703 --- /dev/null +++ b/算法周更班/class_2022_10_1_week/Code02_CaptureStrongHold.java @@ -0,0 +1,163 @@ +package class_2022_10_1_week; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// 来自Leetcode周赛 +// 魔物了占领若干据点,这些据点被若干条道路相连接, +// roads[i] = [x, y] 表示编号 x、y 的两个据点通过一条道路连接。 +// 现在勇者要将按照以下原则将这些据点逐一夺回: +// 在开始的时候,勇者可以花费资源先夺回一些据点, +// 初始夺回第 j 个据点所需消耗的资源数量为 cost[j] +// 接下来,勇者在不消耗资源情况下, +// 每次可以夺回一个和「已夺回据点」相连接的魔物据点, +// 并对其进行夺回 +// 为了防止魔物暴动,勇者在每一次夺回据点后(包括花费资源夺回据点后), +// 需要保证剩余的所有魔物据点之间是相连通的(不经过「已夺回据点」)。 +// 请返回勇者夺回所有据点需要消耗的最少资源数量。 +// 输入保证初始所有据点都是连通的,且不存在重边和自环 +// 测试链接 : https://leetcode.cn/problems/s5kipK/ +public class Code02_CaptureStrongHold { + + public static long minimumCost(int[] cost, int[][] roads) { + int n = cost.length; + if (n == 1) { + return cost[0]; + } + int m = roads.length; + DoubleConnectedComponents dc + = new DoubleConnectedComponents(n, m, roads); + long ans = 0; + // dcc {a,b,c} {c,d,e} + if (dc.dcc.size() == 1) { + ans = Integer.MAX_VALUE; + for (int num : cost) { + ans = Math.min(ans, num); + } + } else { // 不只一个点双连通分量 + ArrayList arr = new ArrayList<>(); + for (List set : dc.dcc) { + int cutCnt = 0; + int curCost = Integer.MAX_VALUE; + for (int nodes : set) { + if (dc.cut[nodes]) { + cutCnt++; + } else { + curCost = Math.min(curCost, cost[nodes]); + } + } + if (cutCnt == 1) { + arr.add(curCost); + } + } + arr.sort((a, b) -> a - b); + for (int i = 0; i < arr.size() - 1; i++) { + ans += arr.get(i); + } + } + return ans; + } + + public static class DoubleConnectedComponents { + public int[] head; + public int[] next; + public int[] to; + public int[] dfn; + public int[] low; + public int[] stack; + public List> dcc; + public boolean[] cut; + public static int edgeCnt; + public static int dfnCnt; + public static int top; + public static int root; + + public DoubleConnectedComponents(int n, int m, int[][] roads) { + init(n, m); + createGraph(roads); + creatDcc(n); + } + + private void init(int n, int m) { + head = new int[n]; + Arrays.fill(head, -1); + next = new int[m << 1]; + to = new int[m << 1]; + dfn = new int[n]; + low = new int[n]; + stack = new int[n]; + dcc = new ArrayList<>(); + cut = new boolean[n]; + edgeCnt = 0; + dfnCnt = 0; + top = 0; + root = 0; + } + + private void createGraph(int[][] roads) { + for (int[] edges : roads) { + add(edges[0], edges[1]); + add(edges[1], edges[0]); + } + } + + private void add(int u, int v) { + to[edgeCnt] = v; + next[edgeCnt] = head[u]; + head[u] = edgeCnt++; + } + + private void creatDcc(int n) { + for (int i = 0; i < n; i++) { + // 0 1 2 3 n-1 + if (dfn[i] == 0) { + root = i; + tarjan(i); + } + } + } + + private void tarjan(int x) { + dfn[x] = low[x] = ++dfnCnt; + stack[top++] = x; + int flag = 0; + if (x == root && head[x] == -1) { + dcc.add(new ArrayList<>()); + dcc.get(dcc.size() - 1).add(x); + } else { + // 当前来到的节点是x + // x {a,b,c} + for (int i = head[x]; i >= 0; i = next[i]) { + // y是下级节点 + int y = to[i]; + if (dfn[y] == 0) { // y点没遍历过! + tarjan(y); + if (low[y] >= dfn[x]) { // 正在扎口袋 + flag++; + if (x != root || flag > 1) { + cut[x] = true; + } + List curAns = new ArrayList<>(); + // 从栈里一次弹出节点 + // 弹到y停! + // 弹出的节点都加入集合,x也加入,x不弹出 + for (int z = stack[--top]; z != y; z = stack[--top]) { + curAns.add(z); + } + curAns.add(y); + curAns.add(x); + dcc.add(curAns); + } + low[x] = Math.min(low[x], low[y]); + } else { // y点已经遍历过了! + low[x] = Math.min(low[x], dfn[y]); + } + } + } + + } + + } + +} diff --git a/算法周更班/class_2022_10_1_week/Code03_MakeASortedMinSwaps.java b/算法周更班/class_2022_10_1_week/Code03_MakeASortedMinSwaps.java new file mode 100644 index 0000000..8e35a1e --- /dev/null +++ b/算法周更班/class_2022_10_1_week/Code03_MakeASortedMinSwaps.java @@ -0,0 +1,109 @@ +package class_2022_10_1_week; + +import java.util.Arrays; + +// 来自学员问题 +// 商场中有一展柜A,其大小固定,现已被不同的商品摆满 +// 商家提供了一些新商品B,需要对A中的部分商品进行更新替换 +// B中的商品可以自由使用,也就是可以用B中的任何商品替换A中的任何商品 +// A中的商品一旦被替换,就认为消失了!而不是回到了B中! +// 要求更新过后的展柜中,商品严格按照价格由低到高进行排列 +// 不能有相邻商品价格相等的情况 +// A[i]为展柜中第i个位置商品的价格,B[i]为各个新商品的价格 +// 求能够满足A中商品价格严格递增的最小操作次数,若无法满足则返回-1 +public class Code03_MakeASortedMinSwaps { + + // 可以用B里的数字,替换A里的数字,想让A严格递增 + // 返回至少换几个数字 + public static int minSwaps(int[] A, int[] B) { + // 根据题意,B里的数字随意拿 + // 所以B里的数字排序,不会影响拿 + // 同时,A如果从左往右考虑,依次被B替换上去的数字,肯定是从小到大的 + // 这是一定的!比如B = {5,3,2,9} + // 可能先用5替换A的某个左边的数,再用2替换A的某个右边的数吗?不可能 + // 所以将B排序 + Arrays.sort(B); + int ans = process(A, B, 0, 0, 0); + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // 参数解释: + // A[0...ai-1]范围上已经做到升序了 + // 接下来请让A[ai....]范围上的数字做到升序 + // 之前的过程中,B里可能已经拿过一些数字了 + // 拿过的数字都在B[0...bi-1]范围上,不一定都拿了 + // 但是最后拿的数字一定是B[bi-1] + // 如果想用B里的数字替换当前的A[ai],请在B[bi....]范围上考虑拿数字 + // pre : 表示之前的A[ai-1]被替换了没有, + // 如果pre==0,表示A[ai-1]没被替换 + // 如果pre==1,表示A[ai-1]被替换了,被谁替换的?被B[bi-1]替换的! + // 返回值:让A[ai....]范围上的数字做到升序,最少还要在B[bi...]上拿几个数字 + // 如果返回值是Integer.MAX_VALUE,表示怎么都做不到 + // 这就是一个三维动态规划,自己改! + // ai 是N范围 + // bi 是M范围 + // pre 只有0、1两种值 + // 所以时间复杂度O(N*M*logM) + // 这个logM怎么来的,二分来的,看代码! + public static int process(int[] A, int[] B, int ai, int bi, int pre) { + if (ai == A.length) { + return 0; + } + // 之前的数是什么 + int preNum = 0; + if (ai == 0) { + preNum = Integer.MIN_VALUE; + } else { + if (pre == 0) { + preNum = A[ai - 1]; + } else { + preNum = B[bi - 1]; + } + } + // 可能性1,搞定当前的A[ai],不依靠交换 + int p1 = preNum < A[ai] ? process(A, B, ai + 1, bi, 0) : Integer.MAX_VALUE; + // 可能性2,搞定当前的A[ai],依靠交换 + int p2 = Integer.MAX_VALUE; + // 在B[bi....]这个范围上,找到>preNum,最左的位置 + // 这一步是可以二分的!因为B整体有序 + int nearMoreIndex = bs(B, bi, preNum); + if (nearMoreIndex != -1) { + int next2 = process(A, B, ai + 1, nearMoreIndex + 1, 1); + if (next2 != Integer.MAX_VALUE) { + p2 = 1 + next2; + } + } + return Math.min(p1, p2); + } + + // 在B[start....]这个范围上,找到>num,最左的位置 + public static int bs(int[] B, int start, int num) { + int ans = -1; + int l = start; + int r = B.length - 1; + int m = 0; + while (l <= r) { + m = (l + r) / 2; + if (B[m] > num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + public static void main(String[] args) { + int[] A1 = { 1, 8, 3, 6, 9 }; + int[] B1 = { 1, 3, 2, 4 }; + System.out.println(minSwaps(A1, B1)); + int[] A2 = { 4, 8, 3, 10, 5 }; + int[] B2 = { 1, 3, 2, 4 }; + System.out.println(minSwaps(A2, B2)); + int[] A3 = { 1, 8, 3, 6, 9 }; + int[] B3 = { 4, 3, 1 }; + System.out.println(minSwaps(A3, B3)); + } + +} diff --git a/算法周更班/class_2022_10_1_week/Code04_TwoTeamsSortedMinSwap.java b/算法周更班/class_2022_10_1_week/Code04_TwoTeamsSortedMinSwap.java new file mode 100644 index 0000000..3e841ee --- /dev/null +++ b/算法周更班/class_2022_10_1_week/Code04_TwoTeamsSortedMinSwap.java @@ -0,0 +1,111 @@ +package class_2022_10_1_week; + +import java.util.HashMap; + +// 来自美团 +// 两种颜色的球,蓝色和红色,都按1~n编号,共计2n个 +// 为方便放在一个数组中,红球编号取负,篮球不变,并打乱顺序, +// 要求同一种颜色的球按编号升序排列,可以进行如下操作: +// 交换相邻两个球,求最少操作次数。 +// [3,-3,1,-4,2,-2,-1,4] +// 最终交换结果为 +// [1,2,3,-1,-2,-3,-4,4] +// 最少交换次数为10 +// n <= 1000 +public class Code04_TwoTeamsSortedMinSwap { + + // [3,-3,1,-4,2,-2,-1,4] + // -3 -4 -2 -1 -> -1 -2 -3 -4 + // 3 1 2 4 -> 1 2 3 4 + + // 这个题写对数器太麻烦了 + // 所以这就是正式解 + public static int minSwaps(int[] arr) { + int n = arr.length; + HashMap map = new HashMap<>(); + int topA = 0; + int topB = 0; + for (int i = 0; i < n; i++) { + if (arr[i] > 0) { + topA = Math.max(topA, arr[i]); + } else { + topB = Math.max(topB, Math.abs(arr[i])); + } + map.put(arr[i], i); + } + IndexTree it = new IndexTree(n); + for (int i = 0; i < n; i++) { + it.add(i, 1); + } + return f(topA, topB, it, n - 1, map); + } + + // 可以改二维动态规划! + // 因为it的状态,只由topA和topB决定 + // 所以it的状态不用作为可变参数! + public static int f(int topA, int topB, + IndexTree it, int end, + HashMap map) { + if (topA == 0 && topB == 0) { + return 0; + } + int p1 = Integer.MAX_VALUE; + int p2 = Integer.MAX_VALUE; + int index, cost, next; + if (topA != 0) { + index = map.get(topA); + cost = it.sum(index, end) - 1; + it.add(index, -1); + next = f(topA - 1, topB, it, end, map); + it.add(index, 1); + p1 = cost + next; + } + if (topB != 0) { + index = map.get(-topB); + cost = it.sum(index, end) - 1; + it.add(index, -1); + next = f(topA, topB - 1, it, end, map); + it.add(index, 1); + p2 = cost + next; + } + return Math.min(p1, p2); + } + + public static class IndexTree { + private int[] tree; + private int N; + + public IndexTree(int size) { + N = size; + tree = new int[N + 1]; + } + + public void add(int i, int v) { + i++; + while (i <= N) { + tree[i] += v; + i += i & -i; + } + } + + public int sum(int l, int r) { + return l == 0 ? sum(r + 1) : (sum(r + 1) - sum(l)); + } + + private int sum(int index) { + int ans = 0; + while (index > 0) { + ans += tree[index]; + index -= index & -index; + } + return ans; + } + + } + + public static void main(String[] args) { + int[] arr = { 3, -3, 1, -4, 2, -2, -1, 4 }; + System.out.println(minSwaps(arr)); + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 38f0ea8..7231c28 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -2064,7 +2064,53 @@ B[i] = { e, f }表示,第i条查询想知道e号、f号实验,一共有多 +第043节 2022年10月第1周流行算法题目解析 + +力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号 +它们之间以「服务器到服务器」点对点的形式相互连接组成了一个内部集群 +其中连接 connections 是无向的 +从形式上讲,connections[i] = [a, b] 表示服务器 a 和 b 之间形成连接 +任何服务器都可以直接或者间接地通过网络到达任何其他服务器。 +"关键连接"是在该集群中的重要连接,也就是说,假如我们将它移除 +便会导致某些服务器无法访问其他服务器。 +请你以任意顺序返回该集群内的所有"关键连接" +测试链接 : https://leetcode.cn/problems/critical-connections-in-a-network/ + +来自Leetcode周赛 +魔物了占领若干据点,这些据点被若干条道路相连接, +roads[i] = [x, y] 表示编号 x、y 的两个据点通过一条道路连接。 +现在勇者要将按照以下原则将这些据点逐一夺回: +在开始的时候,勇者可以花费资源先夺回一些据点, +初始夺回第 j 个据点所需消耗的资源数量为 cost[j] +接下来,勇者在不消耗资源情况下, +每次可以夺回一个和「已夺回据点」相连接的魔物据点, +并对其进行夺回 +为了防止魔物暴动,勇者在每一次夺回据点后(包括花费资源夺回据点后), +需要保证剩余的所有魔物据点之间是相连通的(不经过「已夺回据点」)。 +请返回勇者夺回所有据点需要消耗的最少资源数量。 +输入保证初始所有据点都是连通的,且不存在重边和自环 +测试链接 : https://leetcode.cn/problems/s5kipK/ +来自学员问题 +商场中有一展柜A,其大小固定,现已被不同的商品摆满 +商家提供了一些新商品B,需要对A中的部分商品进行更新替换 +B中的商品可以自由使用,也就是可以用B中的任何商品替换A中的任何商品 +A中的商品一旦被替换,就认为消失了!而不是回到了B中! +要求更新过后的展柜中,商品严格按照价格由低到高进行排列 +不能有相邻商品价格相等的情况 +A[i]为展柜中第i个位置商品的价格,B[i]为各个新商品的价格 +求能够满足A中商品价格严格递增的最小操作次数,若无法满足则返回-1 + +来自美团 +两种颜色的球,蓝色和红色,都按1~n编号,共计2n个 +为方便放在一个数组中,红球编号取负,篮球不变,并打乱顺序, +要求同一种颜色的球按编号升序排列,可以进行如下操作: +交换相邻两个球,求最少操作次数。 +[3,-3,1,-4,2,-2,-1,4] +最终交换结果为 +[1,2,3,-1,-2,-3,-4,4] +最少交换次数为10 +n <= 1000