diff --git a/算法周更班/class_2023_03_4_week/Code01_MinDaysDoneAllProjects.java b/算法周更班/class_2023_03_4_week/Code01_MinDaysDoneAllProjects.java new file mode 100644 index 0000000..bd82b7d --- /dev/null +++ b/算法周更班/class_2023_03_4_week/Code01_MinDaysDoneAllProjects.java @@ -0,0 +1,52 @@ +package class_2023_03_4_week; + +// 来自学员问题 +// 一共有n个项目,每个项目都有两个信息 +// projects[i] = {a, b} +// 表示i号项目做完要a天,但是当你投入b个资源,它就会缩短1天的时间 +// 你一共有k个资源,你的目标是完成所有的项目,但是希望总天数尽可能缩短 +// 在所有项目同时开工的情况下,返回尽可能少的天数 +// 1 <= n <= 10^5 +// 1 <= k <= 10^7 +public class Code01_MinDaysDoneAllProjects { + + // 这是二分答案法几乎最简单的题了 + // 不写对数器了 + public static int minDays(int[][] projects, int k) { + // l......r + // 0 所有项目中,天数的最大值 + int l = 0; + int r = 0; + // project[0] : 既定天数 + // project[1] : 投入多少资源能减少1天 + for (int[] project : projects) { + r = Math.max(r, project[0]); + } + // l......r + int m, ans = r; + while (l <= r) { + m = (l + r) / 2; + if (yeah(projects, m) <= k) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + // 给你所有的项目!projects + // 一定要在days天内完成! + // 返回,需要的资源是多少! + public static int yeah(int[][] projects, int days) { + int ans = 0; + for (int[] p : projects) { + if (p[0] > days) { + ans += (p[0] - days) * p[1]; + } + } + return ans; + } + +} diff --git a/算法周更班/class_2023_03_4_week/Code02_MaximumNumberOfTasksYouCanAssign.java b/算法周更班/class_2023_03_4_week/Code02_MaximumNumberOfTasksYouCanAssign.java new file mode 100644 index 0000000..74f5437 --- /dev/null +++ b/算法周更班/class_2023_03_4_week/Code02_MaximumNumberOfTasksYouCanAssign.java @@ -0,0 +1,151 @@ +package class_2023_03_4_week; + +import java.util.Arrays; +import java.util.TreeMap; + +// 来自华为 +// 给你 n 个任务和 m 个工人 +// 每个任务需要一定的力量值才能完成 +// 需要的力量值保存在下标从 0 开始的整数数组 tasks 中 +// 第 i 个任务需要 tasks[i] 的力量才能完成 +// 每个工人的力量值保存在下标从 0 开始的整数数组 workers 中 +// 第 j 个工人的力量值为 workers[j] +// 每个工人只能完成 一个 任务 +// 且力量值需要 大于等于 该任务的力量要求值, 即 workers[j] >= tasks[i] +// 除此以外,你还有 pills 个神奇药丸 +// 可以给 一个工人的力量值 增加 strength +// 你可以决定给哪些工人使用药丸 +// 但每个工人 最多 只能使用 一片 药丸 +// 给你下标从 0 开始的整数数组tasks 和 workers 以及 +// 两个整数 pills 和 strength ,请你返回 最多 有多少个任务可以被完成。 +// 测试链接 : https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign/ +public class Code02_MaximumNumberOfTasksYouCanAssign { + + // 时间复杂度O(N * (logN)平方) + public static int maxTaskAssign1(int[] tasks, int[] workers, int pills, int strength) { + // l......r + // 0 n + int l = 0; + int r = tasks.length; + int m, ans = 0; + // n个任务,如果只要求其中K个任务完成,选最小工作量的K个任务 + // O(n * log n) + Arrays.sort(tasks); + // n个任务,如果只要求其中K个任务完成,让能力最大的K个工人去执行 + // O(m * log m) + Arrays.sort(workers); + // 0....n + // log n * n * log n + while (l <= r) { + // m是,当前要完成的任务数量 + m = (l + r) / 2; + // tasks[0...m-1] + // workers [ m个人] + // yeah1,至少要吃多少药! + if (yeah1(tasks, 0, m - 1, workers, workers.length - m, workers.length - 1, strength) <= pills) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static int yeah1(int[] tasks, int tl, int tr, int[] workers, int wl, int wr, int strength) { + if (wl < 0) { + return Integer.MAX_VALUE; + } + TreeMap taskMap = new TreeMap<>(); + // tasks[tl....tr] + for (int i = tl; i <= tr; i++) { + taskMap.put(tasks[i], taskMap.getOrDefault(tasks[i], 0) + 1); + } + int ans = 0; + // works[wl..wr] + for (int i = wl; i <= wr; i++) { + Integer job = taskMap.floorKey(workers[i]); + if (job != null) { + int times = taskMap.get(job); + if (times == 1) { + taskMap.remove(job); + } else { + taskMap.put(job, times - 1); + } + } else { + // 吃药了! + job = taskMap.floorKey(workers[i] + strength); + if (job == null) { + return Integer.MAX_VALUE; + } + ans++; + int times = taskMap.get(job); + if (times == 1) { + taskMap.remove(job); + } else { + taskMap.put(job, times - 1); + } + } + } + return ans; + } + + // 时间复杂度O(N * logN) + public static int maxTaskAssign2(int[] tasks, int[] workers, int pills, int strength) { + int[] help = new int[tasks.length]; + int l = 0; + int r = tasks.length; + int m, ans = 0; + Arrays.sort(tasks); + Arrays.sort(workers); + while (l <= r) { + m = (l + r) / 2; + if (yeah2(tasks, 0, m - 1, workers, workers.length - m, workers.length - 1, strength, help) <= pills) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static int yeah2(int[] tasks, int tl, int tr, int[] workers, int wl, int wr, int strength, int[] help) { + if (wl < 0) { + return Integer.MAX_VALUE; + } + int l = 0; + int r = 0; + int ti = tl; + int ans = 0; + // help : 辅助队列,双端队列!双端队列用数组实现! + for (int wi = wl; wi <= wr; wi++) { + // 4 6 7 + // 0 1 2 + // ti ti ti + // 工人 6 + // help : 0 1 + // 0 1 2 3 ... + // l r + for (; ti <= tr && tasks[ti] <= workers[wi]; ti++) { + help[r++] = ti; + } + if (l < r && tasks[help[l]] <= workers[wi]) { + l++; + } else { + // workers[wi] + strength + for (; ti <= tr && tasks[ti] <= workers[wi] + strength; ti++) { + help[r++] = ti; + } + if (l < r) { + ans++; + r--; + } else { + return Integer.MAX_VALUE; + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2023_03_4_week/Code03_MaximumEarningsFromTaxi.java b/算法周更班/class_2023_03_4_week/Code03_MaximumEarningsFromTaxi.java new file mode 100644 index 0000000..d142863 --- /dev/null +++ b/算法周更班/class_2023_03_4_week/Code03_MaximumEarningsFromTaxi.java @@ -0,0 +1,147 @@ +package class_2023_03_4_week; + +import java.util.Arrays; + +// 你驾驶出租车行驶在一条有 n 个地点的路上 +// 这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n +// 通过接乘客订单盈利。你只能沿着编号递增的方向前进,不能改变方向 +// 乘客信息用一个下标从 0 开始的二维数组 rides 表示 +// 其中 rides[i] = [starti, endi, tipi] +// 表示第 i 位乘客需要从地点 starti 前往 endi +// 愿意支付 tipi 元的小费 +// 每一位 你选择接单的乘客 i ,你可以 盈利 endi - starti + tipi 元 +// 你同时 最多 只能接一个订单。 +// 给你 n 和 rides ,请你返回在最优接单方案下,你能盈利 最多 多少元。 +// 注意:你可以在一个地点放下一位乘客,并在同一个地点接上另一位乘客。 +// 测试链接 : https://leetcode.cn/problems/maximum-earnings-from-taxi/ +public class Code03_MaximumEarningsFromTaxi { + + public static int MAXN = 100001; + + public static long[] max = new long[MAXN << 2]; + + public static int n; + + public static void build(int l, int r, int rt) { + if (l == r) { + max[rt] = 0; + } else { + int mid = (l + r) / 2; + build(l, mid, rt << 1); + build(mid + 1, r, rt << 1 | 1); + pushUp(rt); + } + } + + public static long max(int r) { + if (r < 1) { + return 0; + } + return max(1, r, 1, n, 1); + } + + private static long max(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + long 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; + } + + public static void update(int index, long c) { + update(index, c, 1, n, 1); + } + + private static void update(int index, long c, int l, int r, int rt) { + if (l == r) { + max[rt] = Math.max(max[rt], c); + } else { + int mid = (l + r) >> 1; + if (index <= mid) { + update(index, c, l, mid, rt << 1); + } else { + update(index, c, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + } + + private static void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + public static long maxTaxiEarnings1(int len, int[][] rides) { + Arrays.sort(rides, (a, b) -> a[0] - b[0]); + n = len; + build(1, n, 1); + for (int[] ride : rides) { + long money = max(ride[0]) + ride[1] - ride[0] + ride[2]; + update(ride[1], money); + } + return max(n); + } + + public static int[] sorted = new int[MAXN]; + + public static long[] dp = new long[MAXN]; + + public static long maxTaxiEarnings2(int len, int[][] rides) { + int m = rides.length; + for (int i = 0, j = 0; i < m; i++) { + sorted[j++] = rides[i][0]; + sorted[j++] = rides[i][1]; + } + Arrays.sort(rides, (a, b) -> a[0] - b[0]); + Arrays.sort(sorted, 0, m << 1); + Arrays.fill(dp, 0, m << 1, 0); + int dpi = 0; + // pre : 讲的时候的max,之前的最大收入 + long pre = 0; + long ans = 0; + // 13 29 29 30 30 45 + // 1 2 4 6 + for (int[] ride : rides) { + int start = ride[0]; + int end = ride[1]; + int tips = ride[2]; + // 29 -> 2 + // 45 -> 6 + int srank = rank(sorted, m << 1, start); + int erank = rank(sorted, m << 1, end); + // 70 + // dp dp[0......70] max值,之前的最大收入! + while (dpi <= srank) { + pre = Math.max(pre, dp[dpi++]); + } + long money = pre + end - start + tips; + ans = Math.max(money, ans); + dp[erank] = Math.max(dp[erank], money); + } + return ans; + } + + public static int rank(int[] sorted, int len, int num) { + int ans = 0; + int l = 0; + int r = len - 1; + int m = 0; + while (l <= r) { + m = (l + r) / 2; + if (sorted[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + +} diff --git a/算法周更班/class_2023_03_4_week/Code04_MaxLenOfIntegratedSubarray.java b/算法周更班/class_2023_03_4_week/Code04_MaxLenOfIntegratedSubarray.java new file mode 100644 index 0000000..afd9feb --- /dev/null +++ b/算法周更班/class_2023_03_4_week/Code04_MaxLenOfIntegratedSubarray.java @@ -0,0 +1,95 @@ +package class_2023_03_4_week; + +import java.util.Arrays; +import java.util.HashSet; + +// 最长可整合子数组的长度 +// 数组中的数字排序之后,相邻两数的差值是1 +// 这种数组就叫可整合数组 +// 给定一个数组,求最长可整合子数组的长度 +public class Code04_MaxLenOfIntegratedSubarray { + + // 返回最长的可整合子数组长度 + // 时间复杂度O(N^2),确实无法更好 + public static int maxLen(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int ans = 1; + HashSet set = new HashSet<>(); + for (int start = 0; start < arr.length; start++) { + set.clear(); + int min = arr[start]; + int max = arr[start]; + set.add(arr[start]); + for (int end = start + 1; end < arr.length; end++) { + if (!set.add(arr[end])) { + break; + } + min = Math.min(min, arr[end]); + max = Math.max(max, arr[end]); + if (max - min == end - start) { + // start...end 可整合! + ans = Math.max(end - start + 1, ans); + } + } + } + return ans; + } + + // 暴力方法 + // 为了验证 + public static int right(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + int ans = 0; + int[] help = new int[n]; + for (int l = 0; l < n; l++) { + for (int r = l; r < n; r++) { + help[r] = arr[r]; + Arrays.sort(help, l, r + 1); + boolean ok = true; + for (int i = l + 1; i <= r; i++) { + if (help[i - 1] + 1 != help[i]) { + ok = false; + break; + } + } + if (ok) { + ans = Math.max(ans, r - l + 1); + } + } + } + return ans; + } + + // 为了测试 + 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 V = 50; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N); + int[] arr = randomArray(n, V); + int ans1 = maxLen(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2023_03_4_week/Code05_UniqueSubstringsWithEqualDigitFrequency.java b/算法周更班/class_2023_03_4_week/Code05_UniqueSubstringsWithEqualDigitFrequency.java new file mode 100644 index 0000000..8520836 --- /dev/null +++ b/算法周更班/class_2023_03_4_week/Code05_UniqueSubstringsWithEqualDigitFrequency.java @@ -0,0 +1,82 @@ +package class_2023_03_4_week; + +import java.util.Arrays; +import java.util.HashSet; + +// 给你一个由数字组成的字符串 s,返回 s 中独特子字符串数量 +// 其中的每一个数字出现的频率都相同。 +// 测试链接 : https://leetcode.cn/problems/unique-substrings-with-equal-digit-frequency/ +public class Code05_UniqueSubstringsWithEqualDigitFrequency { + +// public static int number(String s) { +// int ans = 1; +// long base = 1000000007; +// long hashCode = 0; +// HashSet set = new HashSet<>(); +// for (int start = 0; start < s.length(); start++) { +// for (int end = start; end < s.length(); end++) { +// // start...end 是不是独特的! +// // 判断是不是独特,搞定了!O(1) +// // 1) 词频表 +// // 2) 总的种类数量 +// // 3) 最大次 +// // 4) 达到最大次的种类数! +// // 更新的时候,O(1),判断O(1) +// // 如果是独特的!s[start....end] 加入到set +// // 哈希表会帮助去重! +// // '0' -> 0 +// // '1' -> 1 +// // '2' -> 2 +// int curVal = s.charAt(end) - '0'; +// +// hashCode = hashCode * base + curVal + 1; +// +// } +// } +// return set.size(); +// } +// + + // 如下的方法是帖子上的一个很骚的方法 + // 其实是不对的,只是可以通过所有当前的测试用例而已 + // 可以构造出让这种方法不通过的例子,原因是这种简陋的hash函数太容易碰撞了 + // 其实这个题的最优解,依然需要使用DC3算法生成后缀数组来做 + // 但是很难,具体可以参考LongestChunkedPalindromeDecomposition问题 + // 课上会简单提一下,详细的就不讲了,因为很少考这么难 + // 课上重点讲一下这个很骚的方法,构造了简陋的hash函数,算是一种博闻强识吧 + // 时间复杂度O(N^2),确实无法更好 + public static int equalDigitFrequency(String s) { + long base = 1000000007; + HashSet set = new HashSet<>(); + int[] cnts = new int[10]; + for (int l = 0; l < s.length(); l++) { + Arrays.fill(cnts, 0); + long hashCode = 0; + int curVal, maxCnt = 0, maxKinds = 0, allKinds = 0; + for (int r = l; r < s.length(); r++) { + curVal = s.charAt(r) - '0'; + // l....r 字符串的哈希值 + // l...r-1 算出的哈希值 * base + 当前位 + 1 + // 真的哈希函数,不是这样的! + // 但是对于一般考试的数据量,真的不会重! + // 很荒谬,但好用 + hashCode = hashCode * base + curVal + 1; + cnts[curVal]++; + if (cnts[curVal] == 1) { + allKinds++; + } + if (cnts[curVal] > maxCnt) { + maxCnt = cnts[curVal]; + maxKinds = 1; + } else if (cnts[curVal] == maxCnt) { + maxKinds++; + } + if (maxKinds == allKinds) { + set.add(hashCode); + } + } + } + return set.size(); + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 70c09f0..04fb135 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -3257,6 +3257,65 @@ subExpr1, subExpr2, ..., subExprn 进行 逻辑或(OR)运算 +第063节 2023年3月第4周流行算法题目解析 + +来自学员问题 +一共有n个项目,每个项目都有两个信息 +projects[i] = {a, b} +表示i号项目做完要a天,但是当你投入b个资源,它就会缩短1天的时间 +你一共有k个资源,你的目标是完成所有的项目,但是希望总天数尽可能缩短 +在所有项目同时开工的情况下,返回尽可能少的天数 +1 <= n <= 10^5 +1 <= k <= 10^7 + +来自华为 +给你 n 个任务和 m 个工人 +每个任务需要一定的力量值才能完成 +需要的力量值保存在下标从 0 开始的整数数组 tasks 中 +第 i 个任务需要 tasks[i] 的力量才能完成 +每个工人的力量值保存在下标从 0 开始的整数数组 workers 中 +第 j 个工人的力量值为 workers[j] +每个工人只能完成 一个 任务 +且力量值需要 大于等于 该任务的力量要求值, 即 workers[j] >= tasks[i] +除此以外,你还有 pills 个神奇药丸 +可以给 一个工人的力量值 增加 strength +你可以决定给哪些工人使用药丸 +但每个工人 最多 只能使用 一片 药丸 +给你下标从 0 开始的整数数组tasks 和 workers 以及 +两个整数 pills 和 strength ,请你返回 最多 有多少个任务可以被完成。 +测试链接 : https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign/ + +你驾驶出租车行驶在一条有 n 个地点的路上 +这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n +通过接乘客订单盈利。你只能沿着编号递增的方向前进,不能改变方向 +乘客信息用一个下标从 0 开始的二维数组 rides 表示 +其中 rides[i] = [starti, endi, tipi] +表示第 i 位乘客需要从地点 starti 前往 endi +愿意支付 tipi 元的小费 +每一位 你选择接单的乘客 i ,你可以 盈利 endi - starti + tipi 元 +你同时 最多 只能接一个订单。 +给你 n 和 rides ,请你返回在最优接单方案下,你能盈利 最多 多少元。 +注意:你可以在一个地点放下一位乘客,并在同一个地点接上另一位乘客。 +测试链接 : https://leetcode.cn/problems/maximum-earnings-from-taxi/ + +最长可整合子数组的长度 +数组中的数字排序之后,相邻两数的差值是1 +这种数组就叫可整合数组 +给定一个数组,求最长可整合子数组的长度 + +给你一个由数字组成的字符串 s,返回 s 中独特子字符串数量 +其中的每一个数字出现的频率都相同。 +测试链接 : https://leetcode.cn/problems/unique-substrings-with-equal-digit-frequency/ + + + + + + + + + +