From fb1da4a0a3b698375aa7b674269a7399aec4a2cf Mon Sep 17 00:00:00 2001 From: algorithmzuo Date: Wed, 7 Sep 2022 22:35:49 +0800 Subject: [PATCH] modify code --- .../Code01_LongestIdealString.java | 157 +++++++++ .../Code02_MoveCityGetMoney.java | 197 +++++++++++ .../Code03_RobotAndClothes.java | 307 ++++++++++++++++++ .../Code04_QueryTopKSum.java | 196 +++++++++++ .../Code05_IsGraphBipartite.java | 77 +++++ ...养的大厂算法面试题(正在直播) | 78 +++++ 6 files changed, 1012 insertions(+) create mode 100644 算法周更班/class_2022_09_1_week/Code01_LongestIdealString.java create mode 100644 算法周更班/class_2022_09_1_week/Code02_MoveCityGetMoney.java create mode 100644 算法周更班/class_2022_09_1_week/Code03_RobotAndClothes.java create mode 100644 算法周更班/class_2022_09_1_week/Code04_QueryTopKSum.java create mode 100644 算法周更班/class_2022_09_1_week/Code05_IsGraphBipartite.java diff --git a/算法周更班/class_2022_09_1_week/Code01_LongestIdealString.java b/算法周更班/class_2022_09_1_week/Code01_LongestIdealString.java new file mode 100644 index 0000000..d36a05b --- /dev/null +++ b/算法周更班/class_2022_09_1_week/Code01_LongestIdealString.java @@ -0,0 +1,157 @@ +package class_2022_09_1_week; + +// 给你一个由小写字母组成的字符串 s ,和一个整数 k +// 如果满足下述条件,则可以将字符串 t 视作是 理想字符串 : +// t 是字符串 s 的一个子序列。 +// t 中每两个 相邻 字母在字母表中位次的绝对差值小于或等于 k 。 +// 返回 最长 理想字符串的长度。 +// 字符串的子序列同样是一个字符串,并且子序列还满足: +// 可以经由其他字符串删除某些字符(也可以不删除)但不改变剩余字符的顺序得到。 +// 注意:字母表顺序不会循环 +// 例如,'a' 和 'z' 在字母表中位次的绝对差值是 25,而不是 1 。 +// 测试链接 : https://leetcode.cn/problems/longest-ideal-subsequence/ +public class Code01_LongestIdealString { + + // 二维动态规划的解 + // N为字符串长度,E为字符集大小,K为差值要求 + // 时间复杂度O(N*E) + // 空间复杂度O(N*E) + public static int longestIdealString1(String s, int k) { + int n = s.length(); + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = s.charAt(i) - 'a'; + } + int[][] dp = new int[n][27]; + for (int i = 0; i < n; i++) { + for (int j = 0; j <= 26; j++) { + dp[i][j] = -1; // -1代表没算过 + } + } + return f(arr, 0, 26, k, dp); + } + + // 数组s中所有的值都在0~25对应a~z + // 当前在s[i...]选择数字, 并且前一个数字是p + // 如果p<26,说明选择的前一个数字是p + // 如果p==26,说明之前没有选过任何数字 + // 返回在前一个数字是p的情况下,在s[i...]上选择数字,最长理想子序列能是多长 + // dp仅仅是缓存结构,暴力递归改动态规划常规技巧 + public static int f(int[] s, int i, int p, int k, int[][] dp) { + if (i == s.length) { + return 0; + } + if (dp[i][p] != -1) { + return dp[i][p]; + } + int p1 = f(s, i + 1, p, k, dp); + int p2 = 0; + if (p == 26 || Math.abs(s[i] - p) <= k) { + p2 = 1 + f(s, i + 1, s[i], k, dp); + } + int ans = Math.max(p1, p2); + dp[i][p] = ans; + return ans; + } + + // 一维动态规划从左往右递推版 + // N为字符串长度,E为字符集大小,K为差值要求 + // 时间复杂度O(N*K) + // 空间复杂度O(E) + public static int longestIdealString2(String s, int k) { + int[] dp = new int[26]; + int c, l, r, pre, ans = 0; + for (int i = 0; i < s.length(); i++) { + c = s.charAt(i) - 'a'; + l = Math.max(c - k, 0); + r = Math.min(c + k, 25); + pre = 0; + for (int j = l; j <= r; j++) { + pre = Math.max(pre, dp[j]); + } + dp[c] = 1 + pre; + ans = Math.max(ans, dp[c]); + } + return ans; + } + + // 从左往右递推 + 线段树优化 + // N为字符串长度,E为字符集大小,K为差值要求 + // 时间复杂度O(N * logE) + // 空间复杂度O(E) + public static int longestIdealString3(String s, int k) { + // 0 0 0 + // 1(a) 2(b) ... 26(z) + SegmentTree st = new SegmentTree(26); + int c, pre, ans = 0; + for (int i = 0; i < s.length(); i++) { + // i s.charAt(i) + // a 1 + // b 2 + // z 26 + c = s.charAt(i) - 'a' + 1; + // 2 k = 3 + // 1 2 3 4 5 6 7 + // l = Math.max(c - k, 1) + // r = Math.min(c + k, 26) + pre = st.max(Math.max(c - k, 1), Math.min(c + k, 26)); + ans = Math.max(ans, 1 + pre); + st.update(c, 1 + pre); + } + return ans; + } + + public static class SegmentTree { + private int n; + private int[] max; + + public SegmentTree(int maxSize) { + n = maxSize + 1; + max = new int[n << 2]; + } + + public void update(int index, int c) { + update(index, index, c, 1, n, 1); + } + + public int max(int left, int right) { + return max(left, right, 1, n, 1); + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] = C; + return; + } + int mid = (l + r) >> 1; + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + private int max(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + int ans = 0; + if (L <= mid) { + ans = Math.max(ans, max(L, R, l, mid, rt << 1)); + } + if (R > mid) { + ans = Math.max(ans, max(L, R, mid + 1, r, rt << 1 | 1)); + } + return ans; + } + + } + +} diff --git a/算法周更班/class_2022_09_1_week/Code02_MoveCityGetMoney.java b/算法周更班/class_2022_09_1_week/Code02_MoveCityGetMoney.java new file mode 100644 index 0000000..cdd3866 --- /dev/null +++ b/算法周更班/class_2022_09_1_week/Code02_MoveCityGetMoney.java @@ -0,0 +1,197 @@ +package class_2022_09_1_week; + +import java.util.Arrays; + +// 来自美团 +// 有n个城市,城市从0到n-1进行编号。小美最初住在k号城市中 +// 在接下来的m天里,小美每天会收到一个任务 +// 她可以选择完成当天的任务或者放弃该任务 +// 第i天的任务需要在ci号城市完成,如果她选择完成这个任务 +// 若任务开始前她恰好在ci号城市,则会获得ai的收益 +// 若她不在ci号城市,她会前往ci号城市,获得bi的收益 +// 当天的任务她都会当天完成 +// 任务完成后,她会留在该任务所在的ci号城市直到接受下一个任务 +// 如果她选择放弃任务,她会停留原地,且不会获得收益 +// 小美想知道,如果她合理地完成任务,最大能获得多少收益 +// 输入描述: 第一行三个正整数n, m和k,表示城市数量,总天数,初始所在城市 +// 第二行为m个整数c1, c2,...... cm,其中ci表示第i天的任务所在地点为ci +// 第三行为m个整数a1, a2,...... am,其中ai表示完成第i天任务且地点不变的收益 +// 第四行为m个整数b1, b2,...... bm,其中bi表示完成第i天的任务且地点改变的收益 +// 0 <= k, ci <= n <= 30000 +// 1 <= m <= 30000 +// 0 <= ai, bi <= 10^9 +// 输出描述 输出一个整数,表示小美合理完成任务能得到的最大收益 +public class Code02_MoveCityGetMoney { + + // 暴力方法 + // 时间复杂度O(N^2) + // 为了验证 + public static int maxPorfit1(int n, int m, int k, int[] c, int[] a, int[] b) { + int[][] dp = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + dp[i][j] = -1; + } + } + return process1(k, 0, m, c, a, b, dp); + } + + // cur : 小美当前在哪! + // i : 当前面临的是任务编号! + // m : 一共有多少任务,固定 + // c[i] : 第i号任务要在哪个城里完成 + // a[i] : 恰好在!收益 + // b[i] : 赶过去!收益 + // 返回 : i....... 小美获得的最大收益 + public static int process1(int cur, int i, int m, int[] c, int[] a, int[] b, int[][] dp) { + if (i == m) { + return 0; + } + if (dp[cur][i] != -1) { + return dp[cur][i]; + } + // 可能性1 : 不做任务,彻底放弃,留在原地 + int p1 = process1(cur, i + 1, m, c, a, b, dp); + // 可能性2 : 做任务,要看看cur在哪,来获得收益 + int p2 = 0; + if (cur == c[i]) { + p2 = a[i] + process1(c[i], i + 1, m, c, a, b, dp); + } else { + p2 = b[i] + process1(c[i], i + 1, m, c, a, b, dp); + } + int ans = Math.max(p1, p2); + dp[cur][i] = ans; + return ans; + } + + // 正式方法 + // 时间复杂度O(N*logN) + public static int maxPorfit2(int n, int m, int k, int[] c, int[] a, int[] b) { + SegmentTree st = new SegmentTree(n); + st.update(k, 0); + int ans = 0; + for (int i = 0; i < m; i++) { + // c[i] + int curAns = Math.max(Math.max(st.max(0, c[i] - 1), st.max(c[i] + 1, n - 1)) + b[i], + st.max(c[i], c[i]) + a[i]); + ans = Math.max(ans, curAns); + st.update(c[i], curAns); + } + return ans; + } + + public static class SegmentTree { + private int n; + private int[] max; + + public SegmentTree(int N) { + n = N; + max = new int[(n + 1) << 2]; + Arrays.fill(max, Integer.MIN_VALUE); + } + + public int max(int l, int r) { + l++; + r++; + if (l > r) { + return Integer.MIN_VALUE; + } + return max(l, r, 1, n, 1); + } + + public void update(int i, int v) { + i++; + update(i, i, v, 1, n, 1); + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] = Math.max(max[rt], C); + return; + } + int mid = (l + r) >> 1; + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + private int max(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + int left = Integer.MIN_VALUE; + int right = Integer.MIN_VALUE; + if (L <= mid) { + left = max(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = max(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.max(left, right); + } + + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int M = 100; + int V = 10000; + int testTimes = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int k = (int) (Math.random() * n); + int[] c = randomArray(m, n); + int[] a = randomArray(m, V); + int[] b = randomArray(m, V); + int ans1 = maxPorfit1(n, m, k, c, a, b); + int ans2 = maxPorfit2(n, m, k, c, a, b); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int n = 100000; + int m = 100000; + int v = 1000000; + int k = (int) (Math.random() * n); + int[] c = randomArray(m, n); + int[] a = randomArray(m, v); + int[] b = randomArray(m, v); + System.out.println("城市数量 : " + n); + System.out.println("任务天数 : " + m); + System.out.println("收益数值规模 : " + v); + long start = System.currentTimeMillis(); + maxPorfit2(n, m, k, c, a, b); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + "毫秒"); + System.out.println("性能测试结束"); + + } + +} diff --git a/算法周更班/class_2022_09_1_week/Code03_RobotAndClothes.java b/算法周更班/class_2022_09_1_week/Code03_RobotAndClothes.java new file mode 100644 index 0000000..8291ff3 --- /dev/null +++ b/算法周更班/class_2022_09_1_week/Code03_RobotAndClothes.java @@ -0,0 +1,307 @@ +package class_2022_09_1_week; + +import java.util.Arrays; + +// 来自美团 +// 给定一个正数n, 表示从0位置到n-1位置每个位置放着1件衣服 +// 从0位置到n-1位置不仅有衣服,每个位置还摆着1个机器人 +// 给定两个长度为n的数组,powers和rates +// powers[i]表示i位置的机器人的启动电量 +// rates[i]表示i位置的机器人收起1件衣服的时间 +// 使用每个机器人只需要付出启动电量 +// 当i位置的机器人收起i位置的衣服,它会继续尝试往右收起i+1位置衣服 +// 如果i+1位置的衣服已经被其他机器人收了或者其他机器人正在收 +// 这个机器人就会停机, 不再收衣服。 +// 不过如果它不停机,它会同样以rates[i]的时间来收起这件i+1位置的衣服 +// 也就是收衣服的时间为每个机器人的固定属性,当它收起i+1位置的衣服, +// 它会继续检查i+2位置...一直到它停机或者右边没有衣服可以收了 +// 形象的来说,机器人会一直尝试往右边收衣服,收k件的话就耗费k * rates[i]的时间 +// 但是当它遇见其他机器人工作的痕迹,就会认为后面的事情它不用管了,进入停机状态 +// 你手里总共有电量b,准备在0时刻将所有想启动的机器人全部一起启动 +// 过后不再启动新的机器人,并且启动机器人的电量之和不能大于b +// 返回在最佳选择下,假快多久能收完所有衣服 +// 如果无论如何都收不完所有衣服,返回-1 +// 给定数据: int n, int b, int[] powers, int[] rates +// 数据范围: +// powers长度 == rates长度 == n <= 1000 +// 1 <= b <= 10^5 +// 1 <= powers[i]、rates[i] <= 10^5 +// 0号 : 10^5 * 10^3 -> 10^8 +// log 10^8 * N^2 -> 27 * 10^6 -> 10^7 +// 优化之后 : (log10^8) -> 27 * 1000 * 10 +public class Code03_RobotAndClothes { + + // 通过不了的简单动态规划方法 + // 只是为了对数器验证 + public static int fast1(int n, int b, int[] powers, int[] rates) { + int[][] dp = new int[n][b + 1]; + for (int i = 0; i < n; i++) { + for (int j = 0; j <= b; j++) { + dp[i][j] = -1; + } + } + int ans = process1(powers, rates, n, 0, b, dp); + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // i....这些衣服 + // 由i....这些机器人负责 + // 在剩余电量还有rest的情况下 + // 收完i....这些衣服最少时间是多少 + // 如果怎么都收不完 + // 返回Integer.MAX_VALUE + public static int process1(int[] powers, int[] rates, int n, int i, int rest, int[][] dp) { + if (i == n) { + return 0; + } + if (powers[i] > rest) { + return Integer.MAX_VALUE; + } + if (dp[i][rest] != -1) { + return dp[i][rest]; + } + int ans = Integer.MAX_VALUE; + for (int j = i; j < n; j++) { + int curCost = (j - i + 1) * rates[i]; + int nextCost = process1(powers, rates, n, j + 1, rest - powers[i], dp); + int curAns = Math.max(curCost, nextCost); + ans = Math.min(ans, curAns); + } + dp[i][rest] = ans; + return ans; + } + + // 正式方法 + // 时间复杂度O( N^2 * log(rates[0] * n)) + // 揭示了大的思路,可以继续用线段树优化枚举,详情看fast3 + // 解题思路: + // 二分答案 + // 定义函数minPower: + // 如果一定要在time时间内捡完所有衣服,请返回使用最少的电量 + // 如果minPower,这个函数能实现 + // 那么只要二分出最小的答案即可 + public static int fast2(int n, int b, int[] powers, int[] rates) { + if (n == 0) { + return 0; + } + if (b == 0 || powers[0] > b) { + return -1; + } + // 最小时间只可能在[1, rates[0] * n]范围上 + int l = 1; + int r = rates[0] * n; + int m = 0; + int ans = -1; + // 二分答案 + // 规定的时间就是m + // minPower(powers, rates, m): + // 如果一定要在time时间内捡完所有衣服,返回最小电量 + // 如果这个最小电量 <= 总电量,说明m时间可行,左侧继续二分答案 + // 如果这个最小电量 > 总电量,说明m时间不可行,右侧继续二分答案 + while (l <= r) { + m = (l + r) / 2; + if (minPower2(powers, rates, m) <= b) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + // 给定所有机器人的启动电量 powers[] + // 给定所有机器人的收一件衣服的时间 rates[] + // 一定要在time时间内,收完所有衣服! + // 返回 : 至少需要的电量! + public static int minPower2(int[] powers, int[] rates, int time) { + int[] dp = new int[powers.length]; + Arrays.fill(dp, -1); + return process2(powers, rates, 0, time, dp); + } + + // i....这么多的衣服 + // 在time时间内一定要收完 + // 返回最小电量 + // 如果怎么都收不完,返回系统最大值 + // N^2 + public static int process2(int[] powers, int[] rates, int i, int time, int[] dp) { + int n = powers.length; + if (i == n) { + return 0; + } + if (dp[i] != -1) { + return dp[i]; + } + // i..... + // 收当前i位置这一件衣服的时间 + int usedTime = rates[i]; + int nextMinPower = Integer.MAX_VALUE; + for (int j = i; j < n && usedTime <= time; j++) { + // i...i i+1.... + // i......i+1 i+2... + // i...........i+2 i+3... + // i....j j+1.... + nextMinPower = Math.min(nextMinPower, process2(powers, rates, j + 1, time, dp)); + usedTime += rates[i]; + } + int ans = nextMinPower == Integer.MAX_VALUE ? nextMinPower : (powers[i] + nextMinPower); + dp[i] = ans; + return ans; + } + + // fast2的思路 + 线段树优化枚举 + // 时间复杂度O(N * logN * log(rates[0] * N)) + public static int fast3(int n, int b, int[] powers, int[] rates) { + if (n == 0) { + return 0; + } + if (b == 0 || powers[0] > b) { + return -1; + } + int l = 1; + int r = rates[0] * n; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (minPower3(powers, rates, m) <= b) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + public static int minPower3(int[] powers, int[] rates, int time) { + int n = powers.length; + int[] dp = new int[n + 1]; + // dp[n-1] dp[n] + // n-1 n + SegmentTree st = new SegmentTree(n + 1); + st.update(n, 0); + for (int i = n - 1; i >= 0; i--) { + if (rates[i] > time) { + dp[i] = Integer.MAX_VALUE; + } else { + int j = Math.min(i + (time / rates[i]) - 1, n - 1); + // for.... logN + int next = st.min(i + 1, j + 1); + int ans = next == Integer.MAX_VALUE ? next : (powers[i] + next); + dp[i] = ans; + } + st.update(i, dp[i]); + } + return dp[0]; + } + + public static class SegmentTree { + private int n; + private int[] min; + + public SegmentTree(int size) { + n = size + 1; + min = new int[n << 2]; + Arrays.fill(min, Integer.MAX_VALUE); + } + + private void pushUp(int rt) { + min[rt] = Math.min(min[rt << 1], min[rt << 1 | 1]); + } + + public void update(int i, int v) { + update(i + 1, i + 1, v, 1, n, 1); + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + min[rt] = C; + return; + } + int mid = (l + r) >> 1; + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int min(int l, int r) { + return min(l + 1, r + 1, 1, n, 1); + } + + private int min(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return min[rt]; + } + int mid = (l + r) >> 1; + int left = Integer.MAX_VALUE; + int right = Integer.MAX_VALUE; + if (L <= mid) { + left = min(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = min(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.min(left, right); + } + + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v) + 1; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 200; + int B = 100; + int P = 20; + int R = 10; + int testTimes = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int b = (int) (Math.random() * B) + 1; + int[] powers = randomArray(n, P); + int[] rates = randomArray(n, R); + int ans1 = fast1(n, b, powers, rates); + int ans2 = fast2(n, b, powers, rates); + int ans3 = fast3(n, b, powers, rates); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int n = 10000; + int b = 100000; + int[] powers = randomArray(n, b); + int[] rates = randomArray(n, b); + System.out.println("衣服规模 : " + n); + System.out.println("电量规模 : " + b); + System.out.println("机器人启动费用取值规模 : " + b); + System.out.println("机器人工作速度取值规模 : " + b); + long start = System.currentTimeMillis(); + fast3(n, b, powers, rates); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + + } + +} diff --git a/算法周更班/class_2022_09_1_week/Code04_QueryTopKSum.java b/算法周更班/class_2022_09_1_week/Code04_QueryTopKSum.java new file mode 100644 index 0000000..271c12e --- /dev/null +++ b/算法周更班/class_2022_09_1_week/Code04_QueryTopKSum.java @@ -0,0 +1,196 @@ +package class_2022_09_1_week; + +// 来自学员问题 +// 给你一个长度为n的数组,并询问q次 +// 每次询问区间[l,r]之间是否存在小于等于k个数的和大于等于x +// 每条查询返回true或者false +// 1 <= n, q <= 10^5 +// k <= 10 +// 1 <= x <= 10^8 +import java.util.PriorityQueue; + +public class Code04_QueryTopKSum { + + + public static class SegmentTree { + + private int n; + private int k; + // private int[] max; + // private int[][] max = new int[][10]; + private int[][] max; + private int[][] query; + + public SegmentTree(int[] arr, int K) { + n = arr.length; + k = K; + max = new int[(n + 1) << 2][k]; + query = new int[(n + 1) << 2][k]; + for (int i = 0; i < n; i++) { + update(i, arr[i]); + } + } + + public int topKSum(int l, int r) { + collect(l + 1, r + 1, 1, n, 1); + int sum = 0; + for (int num : query[1]) { + sum += num; + } + return sum; + } + + private void update(int i, int v) { + update(i + 1, i + 1, v, 1, n, 1); + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt][0] = C; + return; + } + int mid = (l + r) >> 1; + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + merge(max[rt], max[rt << 1], max[rt << 1 | 1]); + } + + // father 要前k名 + // left k名 + // right k名 + private void merge(int[] father, int[] left, int[] right) { + for (int i = 0, p1 = 0, p2 = 0; i < k; i++) { + if (left == null || p1 == k) { + father[i] = right[p2++]; + } else if (right == null || p2 == k) { + father[i] = left[p1++]; + } else { + father[i] = left[p1] >= right[p2] ? left[p1++] : right[p2++]; + } + } + } + + private void collect(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + for (int i = 0; i < k; i++) { + query[rt][i] = max[rt][i]; + } + } else { + int mid = (l + r) >> 1; + boolean leftUpdate = false; + boolean rightUpdate = false; + if (L <= mid) { + leftUpdate = true; + collect(L, R, l, mid, rt << 1); + } + if (R > mid) { + rightUpdate = true; + collect(L, R, mid + 1, r, rt << 1 | 1); + } + merge(query[rt], leftUpdate ? query[rt << 1] : null, rightUpdate ? query[rt << 1 | 1] : null); + } + } + + } + + // 暴力实现的结构 + // 为了验证 + public static class Right { + public int[] arr; + public int k; + + public Right(int[] nums, int K) { + k = K; + arr = new int[nums.length]; + for (int i = 0; i < nums.length; i++) { + arr[i] = nums[i]; + } + } + + public int topKSum(int l, int r) { + PriorityQueue heap = new PriorityQueue<>((a, b) -> b - a); + for (int i = l; i <= r; i++) { + heap.add(arr[i]); + } + int sum = 0; + for (int i = 0; i < k && !heap.isEmpty(); i++) { + sum += heap.poll(); + } + return sum; + } + + } + + // 为了验证 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v) + 1; + } + return ans; + } + + // 为了验证 + public static void main(String[] args) { + int N = 100; + int K = 10; + int V = 100; + int testTimes = 5000; + int queryTimes = 100; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int k = (int) (Math.random() * K) + 1; + int[] arr = randomArray(n, V); + SegmentTree st = new SegmentTree(arr, k); + Right right = new Right(arr, k); + for (int j = 0; j < queryTimes; j++) { + int a = (int) (Math.random() * n); + int b = (int) (Math.random() * n); + int l = Math.min(a, b); + int r = Math.max(a, b); + int ans1 = st.topKSum(l, r); + int ans2 = right.topKSum(l, r); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + } + } + } + System.out.println("测试结束"); + + System.out.println("性能测试开始"); + int n = 100000; + int k = 10; + int[] arr = randomArray(n, n); + int[][] queries = new int[n][2]; + for (int i = 0; i < n; i++) { + int a = (int) (Math.random() * n); + int b = (int) (Math.random() * n); + queries[i][0] = Math.min(a, b); + queries[i][1] = Math.max(a, b); + } + System.out.println("数据规模 : " + n); + System.out.println("数值规模 : " + n); + System.out.println("查询规模 : " + n); + System.out.println("k规模 : " + k); + long start, end1, end2; + start = System.currentTimeMillis(); + SegmentTree st = new SegmentTree(arr, k); + end1 = System.currentTimeMillis(); + for (int i = 0; i < n; i++) { + st.topKSum(queries[i][0], queries[i][1]); + } + end2 = System.currentTimeMillis(); + System.out.println("初始化运行时间 : " + (end1 - start) + " 毫秒"); + System.out.println("执行查询运行时间 : " + (end2 - end1) + " 毫秒"); + System.out.println("总共运行时间 : " + (end2 - start) + " 毫秒"); + System.out.println("性能测试结束"); + } + +} diff --git a/算法周更班/class_2022_09_1_week/Code05_IsGraphBipartite.java b/算法周更班/class_2022_09_1_week/Code05_IsGraphBipartite.java new file mode 100644 index 0000000..e14ebcf --- /dev/null +++ b/算法周更班/class_2022_09_1_week/Code05_IsGraphBipartite.java @@ -0,0 +1,77 @@ +package class_2022_09_1_week; + +// 来自微软面试 +// 给定一个长度为n的二维数组graph,代表一张图 +// graph[i] = {a,b,c,d} 表示i讨厌(a,b,c,d),讨厌关系为双向的 +// 一共有n个人,编号0~n-1 +// 讨厌的人不能一起开会 +// 返回所有人能不能分成两组开会 +// 测试链接 : https://leetcode.cn/problems/is-graph-bipartite/ +public class Code05_IsGraphBipartite { + + public boolean isBipartite(int[][] graph) { + int n = graph.length; + UnionFind uf = new UnionFind(n); + for (int[] neighbours : graph) { + for (int i = 1; i < neighbours.length; i++) { + uf.union(neighbours[i - 1], neighbours[i]); + } + } + for (int i = 0; i < n; i++) { + for (int j : graph[i]) { + if (uf.same(i, j)) { + return false; + } + } + } + return true; + } + + public static class UnionFind { + private int[] f; + private int[] s; + private int[] h; + + public UnionFind(int n) { + f = new int[n]; + s = new int[n]; + h = new int[n]; + for (int i = 0; i < n; i++) { + f[i] = i; + s[i] = 1; + } + } + + private int find(int i) { + int hi = 0; + while (i != f[i]) { + h[hi++] = i; + i = f[i]; + } + while (hi > 0) { + f[h[--hi]] = i; + } + return i; + } + + public boolean same(int i, int j) { + return find(i) == find(j); + } + + public void union(int i, int j) { + int fi = find(i); + int fj = find(j); + if (fi != fj) { + if (s[fi] >= s[fj]) { + f[fj] = fi; + s[fi] = s[fi] + s[fj]; + } else { + f[fi] = fj; + s[fj] = s[fi] + s[fj]; + } + } + } + + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index bdc3550..c35fdac 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -1777,4 +1777,82 @@ n <= 10^6 +第039节 2022年9月第1周流行算法题目解析 + +给你一个由小写字母组成的字符串 s ,和一个整数 k +如果满足下述条件,则可以将字符串 t 视作是 理想字符串 : +t 是字符串 s 的一个子序列。 +t 中每两个 相邻 字母在字母表中位次的绝对差值小于或等于 k 。 +返回 最长 理想字符串的长度。 +字符串的子序列同样是一个字符串,并且子序列还满足: +可以经由其他字符串删除某些字符(也可以不删除)但不改变剩余字符的顺序得到。 +注意:字母表顺序不会循环 +例如,'a' 和 'z' 在字母表中位次的绝对差值是 25,而不是 1 。 +测试链接 : https://leetcode.cn/problems/longest-ideal-subsequence/ + +来自美团 +有n个城市,城市从0到n-1进行编号。小美最初住在k号城市中 +在接下来的m天里,小美每天会收到一个任务 +她可以选择完成当天的任务或者放弃该任务 +第i天的任务需要在ci号城市完成,如果她选择完成这个任务 +若任务开始前她恰好在ci号城市,则会获得ai的收益 +若她不在ci号城市,她会前往ci号城市,获得bi的收益 +当天的任务她都会当天完成 +任务完成后,她会留在该任务所在的ci号城市直到接受下一个任务 +如果她选择放弃任务,她会停留原地,且不会获得收益 +小美想知道,如果她合理地完成任务,最大能获得多少收益 +输入描述: 第一行三个正整数n, m和k,表示城市数量,总天数,初始所在城市 +第二行为m个整数c1, c2,...... cm,其中ci表示第i天的任务所在地点为ci +第三行为m个整数a1, a2,...... am,其中ai表示完成第i天任务且地点不变的收益 +第四行为m个整数b1, b2,...... bm,其中bi表示完成第i天的任务且地点改变的收益 +0 <= k, ci <= n <= 30000 +1 <= m <= 30000 +0 <= ai, bi <= 10^9 +输出描述 输出一个整数,表示小美合理完成任务能得到的最大收益 + +来自美团 +给定一个正数n, 表示从0位置到n-1位置每个位置放着1件衣服 +从0位置到n-1位置不仅有衣服,每个位置还摆着1个机器人 +给定两个长度为n的数组,powers和rates +powers[i]表示i位置的机器人的启动电量 +rates[i]表示i位置的机器人收起1件衣服的时间 +使用每个机器人只需要付出启动电量 +当i位置的机器人收起i位置的衣服,它会继续尝试往右收起i+1位置衣服 +如果i+1位置的衣服已经被其他机器人收了或者其他机器人正在收 +这个机器人就会停机, 不再收衣服。 +不过如果它不停机,它会同样以rates[i]的时间来收起这件i+1位置的衣服 +也就是收衣服的时间为每个机器人的固定属性,当它收起i+1位置的衣服, +它会继续检查i+2位置...一直到它停机或者右边没有衣服可以收了 +形象的来说,机器人会一直尝试往右边收衣服,收k件的话就耗费k * rates[i]的时间 +但是当它遇见其他机器人工作的痕迹,就会认为后面的事情它不用管了,进入停机状态 +你手里总共有电量b,准备在0时刻将所有想启动的机器人全部一起启动 +过后不再启动新的机器人,并且启动机器人的电量之和不能大于b +返回在最佳选择下,假快多久能收完所有衣服 +如果无论如何都收不完所有衣服,返回-1 +给定数据: int n, int b, int[] powers, int[] rates +数据范围: +powers长度 == rates长度 == n <= 1000 +1 <= b <= 10^5 +1 <= powers[i]、rates[i] <= 10^5 + +来自学员问题 +给你一个长度为n的数组,并询问q次 +每次询问区间[l,r]之间是否存在小于等于k个数的和大于等于x +每条查询返回true或者false +1 <= n, q <= 10^5 +k <= 10 +1 <= x <= 10^8 + +来自微软面试 +给定一个长度为n的二维数组graph,代表一张图 +graph[i] = {a,b,c,d} 表示i讨厌(a,b,c,d),讨厌关系为双向的 +一共有n个人,编号0~n-1 +讨厌的人不能一起开会 +返回所有人能不能分成两组开会 +测试链接 : https://leetcode.cn/problems/is-graph-bipartite/ + + + + +