From c2ff2076f49ef85b573abcdd8298e3132b937a50 Mon Sep 17 00:00:00 2001 From: algorithmzuo Date: Wed, 4 Jan 2023 22:42:31 +0800 Subject: [PATCH] modify code --- .../Code01_KthMissingPositiveNumber.java | 26 +++ .../Code02_NthMagicalNumber.java | 31 +++ .../Code03_MinimumCostToHireKWorkers.java | 65 +++++++ .../Code04_MinimumNumberOfRefuelingStops.java | 58 ++++++ .../Code05_OrderlyQueue.java | 181 ++++++++++++++++++ ...Code06_ValidPermutationsForDiSequence.java | 95 +++++++++ ...养的大厂算法面试题(正在直播) | 56 ++++++ 7 files changed, 512 insertions(+) create mode 100644 算法周更班/class_2023_01_1_week/Code01_KthMissingPositiveNumber.java create mode 100644 算法周更班/class_2023_01_1_week/Code02_NthMagicalNumber.java create mode 100644 算法周更班/class_2023_01_1_week/Code03_MinimumCostToHireKWorkers.java create mode 100644 算法周更班/class_2023_01_1_week/Code04_MinimumNumberOfRefuelingStops.java create mode 100644 算法周更班/class_2023_01_1_week/Code05_OrderlyQueue.java create mode 100644 算法周更班/class_2023_01_1_week/Code06_ValidPermutationsForDiSequence.java diff --git a/算法周更班/class_2023_01_1_week/Code01_KthMissingPositiveNumber.java b/算法周更班/class_2023_01_1_week/Code01_KthMissingPositiveNumber.java new file mode 100644 index 0000000..9e3c6f0 --- /dev/null +++ b/算法周更班/class_2023_01_1_week/Code01_KthMissingPositiveNumber.java @@ -0,0 +1,26 @@ +package class_2023_01_1_week; + +// 给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。 +// 请你找到这个数组里第 k 个缺失的正整数。 +// 测试链接 : https://leetcode.cn/problems/kth-missing-positive-number/ +public class Code01_KthMissingPositiveNumber { + + public int findKthPositive(int[] arr, int k) { + int l = 0; + int r = arr.length - 1; + int m = 0; + int find = arr.length; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] - (m + 1) >= k) { + find = m; + r = m - 1; + } else { + l = m + 1; + } + } + int preValue = find == 0 ? 0 : arr[find - 1]; + int under = preValue - find; + return preValue + (k - under); + } +} diff --git a/算法周更班/class_2023_01_1_week/Code02_NthMagicalNumber.java b/算法周更班/class_2023_01_1_week/Code02_NthMagicalNumber.java new file mode 100644 index 0000000..6b1888f --- /dev/null +++ b/算法周更班/class_2023_01_1_week/Code02_NthMagicalNumber.java @@ -0,0 +1,31 @@ +package class_2023_01_1_week; + +// 一个正整数如果能被 a 或 b 整除,那么它是神奇的。 +// 给定三个整数 n , a , b ,返回第 n 个神奇的数字。 +// 因为答案可能很大,所以返回答案 对 10^9 + 7 取模 后的值。 +// 测试链接 : https://leetcode.cn/problems/nth-magical-number/ +public class Code02_NthMagicalNumber { + + public static int nthMagicalNumber(int n, int a, int b) { + // 求a和b的最小公倍数 + long lcm = (long) a / gcd(a, b) * b; + long ans = 0; + // l = 0 + // r = (long) n * Math.min(a, b) + for (long l = 0, r = (long) n * Math.min(a, b), m = 0; l <= r;) { + m = (l + r) / 2; + if (m / a + m / b - m / lcm >= n) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return (int) (ans % 1000000007); + } + + public static int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } + +} diff --git a/算法周更班/class_2023_01_1_week/Code03_MinimumCostToHireKWorkers.java b/算法周更班/class_2023_01_1_week/Code03_MinimumCostToHireKWorkers.java new file mode 100644 index 0000000..da8ef82 --- /dev/null +++ b/算法周更班/class_2023_01_1_week/Code03_MinimumCostToHireKWorkers.java @@ -0,0 +1,65 @@ +package class_2023_01_1_week; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 有 n 名工人。 给定两个数组 quality 和 wage , +// 其中,quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i] 。 +// 现在我们想雇佣 k 名工人组成一个工资组。在雇佣 一组 k 名工人时, +// 我们必须按照下述规则向他们支付工资: +// 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。 +// 工资组中的每名工人至少应当得到他们的最低期望工资。 +// 给定整数 k ,返回 组成满足上述条件的付费群体所需的最小金额 +// 测试链接 : https://leetcode.cn/problems/minimum-cost-to-hire-k-workers/ +public class Code03_MinimumCostToHireKWorkers { + + public static class Employee { + public double rubbishDegree; + public int quality; + + public Employee(int w, int q) { + rubbishDegree = (double) w / (double) q; + quality = q; + } + } + + public double mincostToHireWorkers(int[] quality, int[] wage, int k) { + int n = quality.length; + Employee[] employees = new Employee[n]; + for (int i = 0; i < n; i++) { + employees[i] = new Employee(wage[i], quality[i]); + } + // 只根据垃圾指数排序 + // 要价 / 能力 + Arrays.sort(employees, (a, b) -> a.rubbishDegree <= b.rubbishDegree ? -1 : 1); + // 请维持力量最小的前K个力量 + // 大根堆!门槛堆! + PriorityQueue minTops = new PriorityQueue((a, b) -> b - a); + double ans = Double.MAX_VALUE; + for (int i = 0, qualitySum = 0; i < n; i++) { + // i : 依次所有员工的下标 + // qualitySum : 进入堆的力量总和! + // curQuality当前能力 + int curQuality = employees[i].quality; + if (minTops.size() < k) { // 堆没满 + qualitySum += curQuality; + minTops.add(curQuality); + if (minTops.size() == k) { + ans = Math.min(ans, qualitySum * employees[i].rubbishDegree); + } + } else { // 来到当前员工的时候,堆是满的! + // 当前员工的能力,可以把堆顶干掉,自己进来! + if (minTops.peek() > curQuality) { +// qualitySum -= minTops.poll(); +// qualitySum += curQuality; +// minTops.add(curQuality); + qualitySum += curQuality - minTops.poll(); + minTops.add(curQuality); + ans = Math.min(ans, qualitySum * employees[i].rubbishDegree); + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2023_01_1_week/Code04_MinimumNumberOfRefuelingStops.java b/算法周更班/class_2023_01_1_week/Code04_MinimumNumberOfRefuelingStops.java new file mode 100644 index 0000000..1dda60f --- /dev/null +++ b/算法周更班/class_2023_01_1_week/Code04_MinimumNumberOfRefuelingStops.java @@ -0,0 +1,58 @@ +package class_2023_01_1_week; + +import java.util.PriorityQueue; + +// 汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。 +// 沿途有加油站,每个 station[i] 代表一个加油站, +// 它位于出发位置东面 station[i][0] 英里处,并且有 station[i][1] 升汽油。 +// 假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。 +// 它每行驶 1 英里就会用掉 1 升汽油。 +// 当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。 +// 为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。 +// 注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。 +// 如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。 +// 测试链接 : https://leetcode.cn/problems/minimum-number-of-refueling-stops/ +public class Code04_MinimumNumberOfRefuelingStops { + + // station里,英里数一定递增! + public static int minRefuelStops(int target, int startFuel, int[][] stations) { + if (startFuel >= target) { + return 0; + } + // 大根堆 + // 维持的是:最值得加油的加油站,有多少油 + // 最值得:加得次数少,跑的还最远 + PriorityQueue heap = new PriorityQueue<>((a, b) -> b - a); + // 当前车里的油,能达到的位置 + int to = startFuel; + int cnt = 0; + for (int[] station : stations) { + int position = station[0]; + int fuel = station[1]; + if (to < position) { + while (!heap.isEmpty() && to < position) { + to += heap.poll(); + cnt++; + if (to >= target) { + return cnt; + } + } + if (to < position) { + return -1; + } + } + heap.add(fuel); + } + // 最后一个加油站的位置,都达到了 + // 但还没有到target + while (!heap.isEmpty()) { + to += heap.poll(); + cnt++; + if (to >= target) { + return cnt; + } + } + return -1; + } + +} diff --git a/算法周更班/class_2023_01_1_week/Code05_OrderlyQueue.java b/算法周更班/class_2023_01_1_week/Code05_OrderlyQueue.java new file mode 100644 index 0000000..1a65760 --- /dev/null +++ b/算法周更班/class_2023_01_1_week/Code05_OrderlyQueue.java @@ -0,0 +1,181 @@ +package class_2023_01_1_week; + +import java.util.Arrays; + +// 给定一个字符串 s 和一个整数 k 。你可以从 s 的前 k 个字母中选择一个 +// 并把它加到字符串的末尾 +// 返回 在应用上述步骤的任意数量的移动后,字典上最小的字符串 +// 测试链接 : https://leetcode.cn/problems/orderly-queue/ +public class Code05_OrderlyQueue { + + public static String orderlyQueue(String s, int k) { + if (k > 1) { + // 时间复杂度O(N*logN) + // 证明 : + // 如果k == 2 + // 总可以做到 : 1小 2小 ... + // 总可以做到 : 3小 .... 1小 2小 ... + // 总可以做到 : 3小 1小 2小 ... + // 总可以做到 : 4小 .... 1小 2小 3小 ... + // 总可以做到 : 4小 1小 2小 3小 ..... + // 总可以做到 : 5小 ..... 1小 2小 3小 4小 ... + // ... + // 所以总可以做到有序 + // k > 2就更能做到了,所以k > 1直接排序返回 + char[] str = s.toCharArray(); + Arrays.sort(str); + return String.valueOf(str); + } else { + // 时间复杂度O(N) + // k == 1时 + // 把字符串看做一个环,就是看看从哪切开字典序最小 + // 通过s = s + s的方式,长度2n,可以得到所有环 + // 然后用DC3算法看看前n个位置,谁的字典序最小即可 + // 虽然从通过百分比来看并不优异 + // 但那是因为leetcode准备的数据量太小了,字符串才1000长度所以显不出优势 + // 如果字符串很长优势就明显了 + // 因为时间复杂度O(N)一定是最优解 + String s2 = s + s; + int n = s2.length(); + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = s2.charAt(i) - 'a' + 1; + } + DC3 dc3 = new DC3(arr, 26); + n >>= 1; + int minRankIndex = 0; + for (int i = 1; i < n; i++) { + if (dc3.rank[i] < dc3.rank[minRankIndex]) { + minRankIndex = i; + } + } + return s.substring(minRankIndex) + s.substring(0, minRankIndex); + } + } + + // 如果字符串长度N, + // DC3算法搞定字符串所有后缀串字典序排名的时间复杂度O(N) + // 体系学习班有讲,有兴趣的同学可以看看 + public static class DC3 { + + public int[] sa; + + public int[] rank; + + public DC3(int[] nums, int max) { + sa = sa(nums, max); + rank = rank(); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + private int[] rank() { + int n = sa.length; + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[sa[i]] = i; + } + return ans; + } + + } + +} diff --git a/算法周更班/class_2023_01_1_week/Code06_ValidPermutationsForDiSequence.java b/算法周更班/class_2023_01_1_week/Code06_ValidPermutationsForDiSequence.java new file mode 100644 index 0000000..dc318a5 --- /dev/null +++ b/算法周更班/class_2023_01_1_week/Code06_ValidPermutationsForDiSequence.java @@ -0,0 +1,95 @@ +package class_2023_01_1_week; + +// 给定一个长度为 n 的字符串 s ,其中 s[i] 是: +// D 意味着减少 +// I 意味着增加 +// 有效排列 是对有 n + 1 个在 [0, n] 范围内的整数的一个排列 perm ,使得对所有的 i: +// 如果 s[i] == 'D',那么 perm[i] > perm[i+1],以及; +// 如果 s[i] == 'I',那么 perm[i] < perm[i+1]。 +// 返回 有效排列 perm的数量 。因为答案可能很大,所以请返回你的答案对 10^9 + 7 取余。 +// 测试链接 : https://leetcode.cn/problems/valid-permutations-for-di-sequence/ +public class Code06_ValidPermutationsForDiSequence { + + public static int numPermsDISequence1(String s) { + // 系统最大 + // -1 0.... + return ways1(s.toCharArray(), 0, s.length() + 1, s.length() + 1); + } + + // i : 填的数字的位 + // 3 5 2 + // 0 1 2 + // I D + // less : + // 之前填的数字X,后面剩下的数字中有几个比X小! + // X + // i-1 i + public static int ways1(char[] s, int i, int less, int n) { + int ans = 0; + if (i == n) { + ans = 1; + } else if (i == 0 || s[i - 1] == 'D') { + // 接下来,比当前位的数字小的,有几个 + // nextLess + for (int nextLess = 0; nextLess < less; nextLess++) { + // nextLess 0 -> 最小 + // nextLess 1 -> 次小 + ans += ways1(s, i + 1, nextLess, n); + } + } else { // s[i-1] = 'I' + for (int nextLess = less; nextLess < n - i; nextLess++) { + ans += ways1(s, i + 1, nextLess, n); + } + } + return ans; + } + + public static int numPermsDISequence2(String str) { + int mod = 1000000007; + char[] s = str.toCharArray(); + int n = s.length + 1; + int[][] dp = new int[n + 1][n + 1]; + for (int less = 0; less <= n; less++) { + dp[n][less] = 1; + } + for (int i = n - 1; i >= 0; i--) { + for (int less = 0; less <= n; less++) { + if (i == 0 || s[i - 1] == 'D') { + for (int nextLess = 0; nextLess < less; nextLess++) { + dp[i][less] = (dp[i][less] + dp[i + 1][nextLess]) % mod; + } + } else { + for (int nextLess = less; nextLess < n - i; nextLess++) { + dp[i][less] = (dp[i][less] + dp[i + 1][nextLess]) % mod; + } + } + } + } + return dp[0][n]; + } + + // 通过观察方法2,得到优化枚举的方法 + public static int numPermsDISequence3(String str) { + int mod = 1000000007; + char[] s = str.toCharArray(); + int n = s.length + 1; + int[][] dp = new int[n + 1][n + 1]; + for (int less = 0; less <= n; less++) { + dp[n][less] = 1; + } + for (int i = n - 1; i >= 0; i--) { + if (i == 0 || s[i - 1] == 'D') { + for (int less = 0; less <= n; less++) { + dp[i][less] = less - 1 >= 0 ? ((dp[i][less - 1] + dp[i + 1][less - 1]) % mod) : 0; + } + } else { // s[i-1] = 'I' + dp[i][n - i - 1] = dp[i + 1][n - i - 1]; + for (int less = n - i - 2; less >= 0; less--) { + dp[i][less] = (dp[i][less + 1] + dp[i + 1][less]) % mod; + } + } + } + return dp[0][n]; + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index aa606e3..9882aea 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -2761,6 +2761,62 @@ speed *= 2 +第054节 2023年1月第1周流行算法题目解析 + +给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。 +请你找到这个数组里第 k 个缺失的正整数。 +测试链接 : https://leetcode.cn/problems/kth-missing-positive-number/ + +一个正整数如果能被 a 或 b 整除,那么它是神奇的。 +给定三个整数 n , a , b ,返回第 n 个神奇的数字。 +因为答案可能很大,所以返回答案 对 10^9 + 7 取模 后的值。 +测试链接 : https://leetcode.cn/problems/nth-magical-number/ + +有 n 名工人。 给定两个数组 quality 和 wage , +其中,quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i] 。 +现在我们想雇佣 k 名工人组成一个工资组。在雇佣 一组 k 名工人时, +我们必须按照下述规则向他们支付工资: +对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。 +工资组中的每名工人至少应当得到他们的最低期望工资。 +给定整数 k ,返回 组成满足上述条件的付费群体所需的最小金额 +测试链接 : https://leetcode.cn/problems/minimum-cost-to-hire-k-workers/ + +汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。 +沿途有加油站,每个 station[i] 代表一个加油站, +它位于出发位置东面 station[i][0] 英里处,并且有 station[i][1] 升汽油。 +假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。 +它每行驶 1 英里就会用掉 1 升汽油。 +当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。 +为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。 +注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。 +如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。 +测试链接 : https://leetcode.cn/problems/minimum-number-of-refueling-stops/ + +给定一个字符串 s 和一个整数 k 。你可以从 s 的前 k 个字母中选择一个 +并把它加到字符串的末尾 +返回 在应用上述步骤的任意数量的移动后,字典上最小的字符串 +测试链接 : https://leetcode.cn/problems/orderly-queue/ + +给定一个长度为 n 的字符串 s ,其中 s[i] 是: +D 意味着减少 +I 意味着增加 +有效排列 是对有 n + 1 个在 [0, n] 范围内的整数的一个排列 perm ,使得对所有的 i: +如果 s[i] == 'D',那么 perm[i] > perm[i+1],以及; +如果 s[i] == 'I',那么 perm[i] < perm[i+1]。 +返回 有效排列 perm的数量 。因为答案可能很大,所以请返回你的答案对 10^9 + 7 取余。 +测试链接 : https://leetcode.cn/problems/valid-permutations-for-di-sequence/ + + + + + + + + + + + +