diff --git a/算法周更班/class_2023_07_2_week/Code01_GreedyPickThings.java b/算法周更班/class_2023_07_2_week/Code01_GreedyPickThings.java new file mode 100644 index 0000000..81254fb --- /dev/null +++ b/算法周更班/class_2023_07_2_week/Code01_GreedyPickThings.java @@ -0,0 +1,62 @@ +package class_2023_07_2_week; + +// 阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有N堆金币 +// 第i堆金币的总重量和总价值分别是m[i]、v[i] +// 阿里巴巴有一个承重量为T的背包,但并不一定有办法将全部的金币都装进去 +// 他想装走尽可能多价值的金币 +// 所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变 +// 请问阿里巴巴最多可以拿走多少价值的金币? +// 测试链接 : https://www.luogu.com.cn/problem/P2240 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交以下的code,提交时请把类名改成"Main",可以直接通过 + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StreamTokenizer; +import java.util.Arrays; + +public class Code01_GreedyPickThings { + + public static int MAXN = 101; + + // mv[i][0] + // mv[i][1] + public static int[][] mv = new int[MAXN][2]; + + public static int n, t; + + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + StreamTokenizer in = new StreamTokenizer(br); + PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); + while (in.nextToken() != StreamTokenizer.TT_EOF) { + n = (int) in.nval; + in.nextToken(); + t = (int) in.nval; + for (int i = 0; i < n; i++) { + in.nextToken(); + mv[i][0] = (int) in.nval; + in.nextToken(); + mv[i][1] = (int) in.nval; + } + Arrays.sort(mv, 0, n, (a, b) -> (b[1] * a[0]) - (a[1] * b[0])); + double ans = 0; + int i = 0; + int used = 0; + for (; i < n && used + mv[i][0] <= t; i++) { + used += mv[i][0]; + ans += mv[i][1]; + } + if (i < n) { + ans += (double) mv[i][1] * (t - used) / (double) mv[i][0]; + } + out.println(String.format("%.2f", ans)); + out.flush(); + } + } + +} diff --git a/算法周更班/class_2023_07_2_week/Code02_RobotPassThroughBuilding.java b/算法周更班/class_2023_07_2_week/Code02_RobotPassThroughBuilding.java new file mode 100644 index 0000000..25d9587 --- /dev/null +++ b/算法周更班/class_2023_07_2_week/Code02_RobotPassThroughBuilding.java @@ -0,0 +1,81 @@ +package class_2023_07_2_week; + +// 来自字节 +// 机器人正在玩一个古老的基于DOS的游戏 +// 游戏中有N+1座建筑,从0到N编号,从左到右排列 +// 编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位 +// 起初, 机器人在编号为0的建筑处 +// 每一步,它跳到下一个(右边)建筑。假设机器人在第k个建筑,且它现在的能量值是E +// 下一步它将跳到第个k+1建筑 +// 它将会得到或者失去正比于与H(k+1)与E之差的能量 +// 如果 H(k+1) > E 那么机器人就失去H(k+1)-E的能量值,否则它将得到E-H(k+1)的能量值 +// 游戏目标是到达第个N建筑,在这个过程中,能量值不能为负数个单位 +// 现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏 +// 测试链接 : https://www.nowcoder.com/questionTerminal/7037a3d57bbd4336856b8e16a9cafd71 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交以下的code,提交时请把类名改成"Main",可以直接通过 + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StreamTokenizer; + +public class Code02_RobotPassThroughBuilding { + + public static int MAXN = 100001; + + public static int[] arr = new int[MAXN]; + + public static int n; + + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + StreamTokenizer in = new StreamTokenizer(br); + PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); + while (in.nextToken() != StreamTokenizer.TT_EOF) { + n = (int) in.nval; + int l = 0; + int r = 0; + int max = 0; + for (int i = 0; i < n; i++) { + in.nextToken(); + arr[i] = (int) in.nval; + r = Math.max(r, arr[i]); + max = r; + } + int m, ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (ok(m, max)) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + out.println(ans); + out.flush(); + } + } + + public static boolean ok(int sum, int max) { + // 注意! + // 如果你给的能量值很大,那么这个能量增长的将非常恐怖 + // 甚至有可能超出long的范围,这就是为什么改成long也不对 + // 所以要加sum <= max这一句判断,一旦能量累加和超过高度的最大值 + // 后面肯定通关了,可以提前返回 + // 总之是很阴 + for (int i = 0; i < n && sum >= 0 && sum <= max; i++) { + if (sum <= arr[i]) { + sum -= arr[i] - sum; + } else { + sum += sum - arr[i]; + } + } + return sum >= 0; + } + +} diff --git a/算法周更班/class_2023_07_2_week/Code03_PutMarblesInBags.java b/算法周更班/class_2023_07_2_week/Code03_PutMarblesInBags.java new file mode 100644 index 0000000..62e7539 --- /dev/null +++ b/算法周更班/class_2023_07_2_week/Code03_PutMarblesInBags.java @@ -0,0 +1,35 @@ +package class_2023_07_2_week; + +import java.util.Arrays; + +// 你有 k 个背包。给你一个下标从 0 开始的整数数组 weights +// 其中 weights[i] 是第 i 个珠子的重量。同时给你整数 k +// 请你按照如下规则将所有的珠子放进 k 个背包。 +// 没有背包是空的。 +// 如果第 i 个珠子和第 j 个珠子在同一个背包里 +// 那么下标在 i 到 j 之间的所有珠子都必须在这同一个背包中 +// 如果一个背包有下标从 i 到 j 的所有珠子,那么这个背包的价格是 weights[i] + weights[j] 。 +// 一个珠子分配方案的 分数 是所有 k 个背包的价格之和。 +// 请你返回所有分配方案中,最大分数 与 最小分数 的 差值 为多少。 +// 测试链接 : https://leetcode.cn/problems/put-marbles-in-bags/ +public class Code03_PutMarblesInBags { + + public long putMarbles(int[] weights, int k) { + int n = weights.length; + long[] sums = new long[n - 1]; + for (int i = 1; i < n; i++) { + sums[i - 1] = (long) weights[i - 1] + weights[i]; + } + Arrays.sort(sums); + long ans = 0; + // 小 ........ 大 + // 0 n-2 +// 1 n-3 +// 2 n-4 + for (int i = 0, j = n - 2, p = 1; p < k; i++, j--, p++) { + ans += sums[j] - sums[i]; + } + return ans; + } + +} diff --git a/算法周更班/class_2023_07_2_week/Code04_MaximumEmployeesToBeInvitedToAMeeting.java b/算法周更班/class_2023_07_2_week/Code04_MaximumEmployeesToBeInvitedToAMeeting.java new file mode 100644 index 0000000..ba3024a --- /dev/null +++ b/算法周更班/class_2023_07_2_week/Code04_MaximumEmployeesToBeInvitedToAMeeting.java @@ -0,0 +1,104 @@ +package class_2023_07_2_week; + +// 一个公司准备组织一场会议,邀请名单上有 n 位员工 +// 公司准备了一张 圆形 的桌子,可以坐下 任意数目 的员工 +// 员工编号为 0 到 n - 1 。每位员工都有一位 喜欢 的员工 +// 每位员工 当且仅当 他被安排在喜欢员工的旁边,他才会参加会议 +// 每位员工喜欢的员工 不会 是他自己。 +// 给你一个下标从 0 开始的整数数组 favorite +// 其中 favorite[i] 表示第 i 位员工喜欢的员工。请你返回参加会议的 最多员工数目 +// 测试链接 : https://leetcode.cn/problems/maximum-employees-to-be-invited-to-a-meeting/ +public class Code04_MaximumEmployeesToBeInvitedToAMeeting { + + public static int maximumInvitations(int[] favorite) { + int[][] loved = beLoved(favorite); + int[] degree = degree(loved); + int n = favorite.length; + int[] queue = new int[n]; + int l = 0; + int r = 0; + for (int i = 0; i < n; i++) { + if (degree[i] == 0) { + queue[r++] = i; + } + } + boolean[] zeroVisited = new boolean[n]; + while (l < r) { + int cur = queue[l++]; + zeroVisited[cur] = true; + if (--degree[favorite[cur]] == 0) { + queue[r++] = favorite[cur]; + } + } + boolean[] cycleVisited = new boolean[n]; + int arrangeTwoCycle = 0; + int arrangeMoreCycle = 0; + for (int i = 0; i < n; i++) { + if (!zeroVisited[i] && !cycleVisited[i]) { + cycleVisited[i] = true; + if (favorite[favorite[i]] == i) { + cycleVisited[favorite[i]] = true; + arrangeTwoCycle += maxFollow(i, zeroVisited, loved) + maxFollow(favorite[i], zeroVisited, loved); + } else { + int cur = favorite[i]; + int curAns = 1; + while (cur != i) { + cycleVisited[cur] = true; + curAns++; + cur = favorite[cur]; + } + arrangeMoreCycle = Math.max(arrangeMoreCycle, curAns); + } + } + } + return Math.max(arrangeTwoCycle, arrangeMoreCycle); + } + + // 生成被喜欢表 + // favorite[3] = 7 + // favorite[5] = 7 + // size[ + public static int[][] beLoved(int[] favorite) { + int n = favorite.length; + int[] size = new int[n]; + // size[7] : 有多少人喜欢7 + for (int love : favorite) { + size[love]++; + } + // 7 : 3 5 + // 9 : 0 1 4 + int[][] loved = new int[n][]; + for (int i = 0; i < n; i++) { + loved[i] = new int[size[i]]; + } + for (int i = 0; i < n; i++) { + loved[favorite[i]][--size[favorite[i]]] = i; + } + return loved; + } + + // 求每个点的入度 + public static int[] degree(int[][] loved) { + int n = loved.length; + int[] degree = new int[n]; + for (int i = 0; i < n; i++) { + degree[i] = loved[i].length; + } + return degree; + } + + // cur不在环上的追随者链条最大长度 + public static int maxFollow(int cur, boolean[] zeroed, int[][] from) { + if (from[cur].length == 0) { + return 1; + } + int ans = 0; + for (int pre : from[cur]) { + if (zeroed[pre]) { + ans = Math.max(ans, maxFollow(pre, zeroed, from)); + } + } + return ans + 1; + } + +} diff --git a/算法周更班/class_2023_07_2_week/Code05_FindCriticalAndPseudoCriticalEdges.java b/算法周更班/class_2023_07_2_week/Code05_FindCriticalAndPseudoCriticalEdges.java new file mode 100644 index 0000000..e43c350 --- /dev/null +++ b/算法周更班/class_2023_07_2_week/Code05_FindCriticalAndPseudoCriticalEdges.java @@ -0,0 +1,244 @@ +package class_2023_07_2_week; + +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; + +// 给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 +// 同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti] +// 表示在 fromi 和 toi 节点之间有一条带权无向边 +// 最小生成树 (MST) 是给定图中边的一个子集 +// 它连接了所有节点且没有环,而且这些边的权值和最小 +// 请你找到给定图中最小生成树的所有关键边和伪关键边 +// 如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边 +// 伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边 +// 请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标 +// 测试链接 : https://leetcode.cn/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/ +// 本题用到并查集、最小生成树、求联通图中的桥 +// 并查集、最小生成树,在体系学习班 +// 求桥,在每周有营养的大厂算法面试题,2022年10月第1周 +// 链式前向星,2022年10月第1周 +// 务必打好基础,再来看这个题的解析 +// 这个实现打败100%的提交者 +public class Code05_FindCriticalAndPseudoCriticalEdges { + + public static int MAXN = 101; + + public static int MAXM = 201; + + // 边状态的记录 + // status[ei] = 0,代表ei这个边既不是关键边也不是伪关键边 + // status[ei] = 1,代表ei这个边是伪关键边 + // status[ei] = 2,代表ei这个边是关键边 + public static int[] status = new int[MAXM]; + + // 并查集相关 + public static int[] father = new int[MAXN]; + public static int[] size = new int[MAXN]; + public static int[] help = new int[MAXN]; + public static int sets = 0; + + // 并查集初始化 + public static void buildUnoinSet(int n) { + for (int i = 0; i < n; i++) { + father[i] = i; + size[i] = 1; + } + sets = n; + } + + // 并查集向上找代表节点 + public static int find(int i) { + int r = 0; + while (i != father[i]) { + help[r++] = i; + i = father[i]; + } + while (r > 0) { + father[help[--r]] = i; + } + return i; + } + + // 并查集合并集合 + public static void union(int i, int j) { + int fi = find(i); + int fj = find(j); + if (fi != fj) { + if (size[fi] >= size[fj]) { + father[fj] = fi; + size[fi] += size[fj]; + } else { + father[fi] = fj; + size[fj] += size[fi]; + } + sets--; + } + } + + // 边相关 + public static int[][] edges = new int[MAXM][4]; + + public static int m; + + public static void buildEdges(int[][] e) { + for (int i = 0; i < m; i++) { + edges[i][0] = i; + edges[i][1] = e[i][0]; + edges[i][2] = e[i][1]; + edges[i][3] = e[i][2]; + } + Arrays.sort(edges, 0, m, (a, b) -> a[3] - b[3]); + } + + // 通过集合编号建图相关 + // 链式前向星建图 + // 为啥用这玩意儿建图?没啥,就是想秀 + public static int[] head = new int[MAXN]; + // int[] to : i号边是去哪的! + public static int[][] info = new int[MAXM][3]; + public static int[] next = new int[MAXM]; + public static int edgeSize; + + public static void buildGraph(int k) { + for (int i = 0; i < k; i++) { + head[i] = -1; + edgeSize = 0; + } + } + + public static void addEdge(int a, int b, int ei) { + next[edgeSize] = head[a]; + info[edgeSize][0] = ei; + info[edgeSize][1] = a; + info[edgeSize][2] = b; + head[a] = edgeSize++; + } + + // 哈希表相关 + // 一个集合,给一个编号 + public static int[] id = new int[MAXN]; + + // 找桥相关 + public static int[] dfn = new int[MAXN]; + public static int[] low = new int[MAXN]; + public static int cnt; + + public static void findBridge(int k) { + Arrays.fill(dfn, 0, k, 0); + Arrays.fill(low, 0, k, 0); + cnt = 0; + for (int init = 0; init < k; init++) { + if (dfn[init] == 0) { + tarjan(init, init, -1, -1); + } + } + } + + public static void tarjan(int init, int cur, int father, int ei) { + dfn[cur] = low[cur] = ++cnt; + for (int i = head[cur]; i != -1; i = next[i]) { + int edgei = info[i][0]; + int nodei = info[i][2]; + if (nodei != father) { + if (dfn[nodei] == 0) { + tarjan(init, nodei, cur, edgei); + low[cur] = Math.min(low[cur], low[nodei]); + } else { + low[cur] = Math.min(low[cur], dfn[nodei]); + } + } + } + if (low[cur] == dfn[cur] && cur != init) { + status[ei] = 2; + } + } + + public static List> findCriticalAndPseudoCriticalEdges(int n, int[][] e) { + m = e.length; + Arrays.fill(status, 0, m, 0); + buildUnoinSet(n); + buildEdges(e); + List bridge = new ArrayList<>(); + List pseudo = new ArrayList<>(); + int start = 0; + while (sets != 1) { + int end = start + 1; + while (end < m && edges[start][3] == edges[end][3]) { + end++; + } + // start....end-1 start... + connect(start, end); + for (int i = start; i < end; i++) { + int ei = edges[i][0]; + if (status[ei] == 2) { + bridge.add(ei); + } else if (status[ei] == 1) { + pseudo.add(ei); + } + union(edges[i][1], edges[i][2]); + } + start = end; + } + return Arrays.asList(bridge, pseudo); + } + + // 大团子,一个集合,缩成一个点 + // 当前的边,[start...end) + // 做图!大团子的图,找桥! + public static void connect(int start, int end) { + for (int i = start; i < end; i++) { + id[find(edges[i][1])] = -1; + id[find(edges[i][2])] = -1; + } + int k = 0; + for (int i = start; i < end; i++) { + if (id[find(edges[i][1])] == -1) { + id[find(edges[i][1])] = k++; + } + if (id[find(edges[i][2])] == -1) { + id[find(edges[i][2])] = k++; + } + } + buildGraph(k); + // 大团子,有边!用链式前向星建图,大团子的图! + for (int i = start; i < end; i++) { + int index = edges[i][0]; + int a = id[find(edges[i][1])]; + int b = id[find(edges[i][2])]; + if (a != b) { + status[index] = 1; + addEdge(a, b, index); + addEdge(b, a, index); + } + } + findBridge(k); + // 处理重复连接 + // 什么是重复连接?不是自己指向自己,那叫自环 + // 重复连接指的是: + // 集合a到集合b有一条边,边的序号是p + // 于是,a的邻接表里有(p,b),b的邻接表里有(p,a) + // 集合a到集合b又有一条边,边的序号是t + // 于是,a的邻接表里有(t,b),b的邻接表里有(t,a) + // 那么p和t都是重复链接,因为单独删掉p或者t,不会影响联通性 + // 而在求桥的模版中,是默认没有重复链接的 + // 如果有重复链接就直接用模版,那么会出现重复链接被认为是桥的情况 + // 所以这里要单独判断,如果有重复链接被设置成了桥,要把它改成伪关键边的状态 + Arrays.sort(info, 0, edgeSize, (a, b) -> a[1] != b[1] ? (a[1] - b[1]) : (a[2] - b[2])); + int right, left = 0; + while (left < edgeSize) { + right = left + 1; + while (right < edgeSize && info[left][1] == info[right][1]) { + right++; + } + for (int i = left + 1; i < right; i++) { + if (info[i][2] == info[i - 1][2]) { + status[info[i][0]] = 1; + status[info[i - 1][0]] = 1; + } + } + left = right; + } + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 3eebdc7..dcd15f1 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -4074,6 +4074,66 @@ forceField[i] = [x,y,side] +第076节 2023年7月2周流行算法题目解析 + +阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有N堆金币 +第i堆金币的总重量和总价值分别是m[i]、v[i] +阿里巴巴有一个承重量为T的背包,但并不一定有办法将全部的金币都装进去 +他想装走尽可能多价值的金币 +所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变 +请问阿里巴巴最多可以拿走多少价值的金币? +测试链接 : https://www.luogu.com.cn/problem/P2240 + +来自字节 +机器人正在玩一个古老的基于DOS的游戏 +游戏中有N+1座建筑,从0到N编号,从左到右排列 +编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位 +起初, 机器人在编号为0的建筑处 +每一步,它跳到下一个(右边)建筑。假设机器人在第k个建筑,且它现在的能量值是E +下一步它将跳到第个k+1建筑 +它将会得到或者失去正比于与H(k+1)与E之差的能量 +如果 H(k+1) > E 那么机器人就失去H(k+1)-E的能量值,否则它将得到E-H(k+1)的能量值 +游戏目标是到达第个N建筑,在这个过程中,能量值不能为负数个单位 +现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏 +测试链接 : https://www.nowcoder.com/questionTerminal/7037a3d57bbd4336856b8e16a9cafd71 + +你有 k 个背包。给你一个下标从 0 开始的整数数组 weights +其中 weights[i] 是第 i 个珠子的重量。同时给你整数 k +请你按照如下规则将所有的珠子放进 k 个背包。 +没有背包是空的。 +如果第 i 个珠子和第 j 个珠子在同一个背包里 +那么下标在 i 到 j 之间的所有珠子都必须在这同一个背包中 +如果一个背包有下标从 i 到 j 的所有珠子,那么这个背包的价格是 weights[i] + weights[j] 。 +一个珠子分配方案的 分数 是所有 k 个背包的价格之和。 +请你返回所有分配方案中,最大分数 与 最小分数 的 差值 为多少。 +测试链接 : https://leetcode.cn/problems/put-marbles-in-bags/ + +一个公司准备组织一场会议,邀请名单上有 n 位员工 +公司准备了一张 圆形 的桌子,可以坐下 任意数目 的员工 +员工编号为 0 到 n - 1 。每位员工都有一位 喜欢 的员工 +每位员工 当且仅当 他被安排在喜欢员工的旁边,他才会参加会议 +每位员工喜欢的员工 不会 是他自己。 +给你一个下标从 0 开始的整数数组 favorite +其中 favorite[i] 表示第 i 位员工喜欢的员工。请你返回参加会议的 最多员工数目 +测试链接 : https://leetcode.cn/problems/maximum-employees-to-be-invited-to-a-meeting/ + +给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 +同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti] +表示在 fromi 和 toi 节点之间有一条带权无向边 +最小生成树 (MST) 是给定图中边的一个子集 +它连接了所有节点且没有环,而且这些边的权值和最小 +请你找到给定图中最小生成树的所有关键边和伪关键边 +如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边 +伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边 +请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标 +测试链接 : https://leetcode.cn/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/ + + + + + + +