modify code

master
algorithmzuo 3 years ago
parent 152e710b69
commit c2ff2076f4

@ -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);
}
}

@ -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);
}
}

@ -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<Integer> minTops = new PriorityQueue<Integer>((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;
}
}

@ -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<Integer> 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;
}
}

@ -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;
}
}
}

@ -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];
}
}

@ -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/

Loading…
Cancel
Save