modify code

master
algorithmzuo 1 year ago
parent 95d5248e92
commit 8133e4d313

@ -0,0 +1,69 @@
package class_2023_06_4_week;
// 给你一个下标从 0 开始的整数数组 nums ,它包含 n 个 互不相同 的正整数
// 如果 nums 的一个排列满足以下条件,我们称它是一个特别的排列
// 对于 0 <= i < n - 1 的下标 i
// 要么 nums[i] % nums[i+1] == 0
// 要么 nums[i+1] % nums[i] == 0
// 请你返回特别排列的总数目,由于答案可能很大,请将它对 1000000007 取余 后返回
// 测试链接 : https://leetcode.cn/problems/special-permutations/
public class Code01_SpecialPermutations {
public static int specialPerm(int[] nums) {
int n = nums.length;
int[][] dp = new int[1 << n][n];
for (int i = 0; i < (1 << n); i++) {
for (int j = 0; j < n; j++) {
dp[i][j] = -1;
}
}
return process(nums, n, 0, 0, dp);
}
public static int mod = 1000000007;
// a : 4 7 9 2 1 ... 固定数组
// 0 1 2 3 4 ...
// 4 2 1 9
// s : 1 1 1 0 1
// 6 5 4 3 2 1 0
// p = 2
// 潜台词 : 之前选的数字,一定相邻两个数,都合法!
// 返回值 : 根据上面的情况,继续把所有数字选完!返回有多少合法的!数量!
// s : 最多14位2^14 = 16384
// p : 数组下标0~13, 14种
// 16384 * 14 = 229376 * 14 -> 3 211 264
public static int process(int[] a, int n, int s, int p, int[][] dp) {
if (dp[s][p] != -1) {
return dp[s][p];
}
// 方法数
int ans = 0;
// n = 7
// s : 01111111
if (s == (1 << n) - 1) {
ans = 1;
} else {
// s : 哪些下标上的数已经选了
// p : 选的数中,最后一个数来自哪个下标
// 当前该怎么挑选数字?
// arr[p] (前) 现在 x
// 1) arr[p] % x == 0
// 2) x % arr[p] == 0
for (int i = 0; i < n; i++) {
// 0位置的数能不能做当前
// 1位置的数能不能做当前
// 2位置的数能不能做当前
// 3位置的数能不能做当前
if (s == 0 // 之前没有选择过数字,当然现在随意
||
((s & (1 << i)) == 0 && (a[p] % a[i] == 0 || a[i] % a[p] == 0))) {
ans = (ans + process(a, n, s | (1 << i), i, dp)) % mod;
}
}
}
dp[s][p] = ans;
return ans;
}
}

@ -0,0 +1,99 @@
package class_2023_06_4_week;
import java.util.Arrays;
// 给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time
// 分别表示给 n 堵不同的墙刷油漆需要的开销和时间。你有两名油漆匠
// 一位需要 付费 的油漆匠,刷第 i 堵墙需要花费 time[i] 单位的时间
// 开销为 cost[i] 单位的钱。
// 一位 免费 的油漆匠,刷 任意 一堵墙的时间为 1 单位,开销为 0
// 但是必须在付费油漆匠 工作 时,免费油漆匠才会工作
// 请你返回刷完 n 堵墙最少开销为多少
// 测试链接 : https://leetcode.cn/problems/painting-the-walls/
public class Code02_PaintingTheWalls {
// 暴力递归
// 展示了主要的思路
public static int paintWalls1(int[] cost, int[] time) {
return process1(cost, time, 0, cost.length);
}
// 来到i位置的墙整体还有s面墙需要刷
// 认为只需要选s面墙即可剩下的都刷完了
// 返回刷完所有墙的最少花费
public static int process1(int[] cost, int[] time, int i, int s) {
if (s <= 0) {
return 0;
}
// s > 0
if (i == cost.length) {
return Integer.MAX_VALUE;
} else {
// 付费的人就是不选第i面墙
int p1 = process1(cost, time, i + 1, s);
// 付费的人就是选第i面墙
int p2 = Integer.MAX_VALUE;
int next2 = process1(cost, time, i + 1, s - 1 - time[i]);
if (next2 != Integer.MAX_VALUE) {
p2 = cost[i] + next2;
}
return Math.min(p1, p2);
}
}
// 暴力递归改记忆化搜索
public static int paintWalls2(int[] cost, int[] time) {
int n = cost.length;
int[][] dp = new int[n + 1][n + 1];
for (int i = 0; i <= n; i++) {
Arrays.fill(dp[i], -1);
}
return process2(cost, time, 0, n, dp);
}
public static int process2(int[] cost, int[] time, int i, int s, int[][] dp) {
if (s <= 0) {
return 0;
}
if (dp[i][s] != -1) {
return dp[i][s];
}
int ans;
if (i == cost.length) {
ans = Integer.MAX_VALUE;
} else {
int p1 = process2(cost, time, i + 1, s, dp);
int p2 = Integer.MAX_VALUE;
int next2 = process2(cost, time, i + 1, s - 1 - time[i], dp);
if (next2 != Integer.MAX_VALUE) {
p2 = cost[i] + next2;
}
ans = Math.min(p1, p2);
}
dp[i][s] = ans;
return ans;
}
// 严格位置依赖的动态规划 + 空间压缩
public static int paintWalls3(int[] cost, int[] time) {
int n = cost.length;
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = n - 1; i >= 0; i--) {
for (int s = n; s >= 1; s--) {
if (s - 1 - time[i] <= 0) {
dp[s] = Math.min(dp[s], cost[i]);
} else if (dp[s - 1 - time[i]] != Integer.MAX_VALUE) {
dp[s] = Math.min(dp[s], cost[i] + dp[s - 1 - time[i]]);
}
}
}
return dp[n];
}
}

@ -0,0 +1,120 @@
package class_2023_06_4_week;
// 来自华为社招笔试
// 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧
// 在桥上有一些石子,青蛙很讨厌踩在这些石子上
// 由于桥的长度和青蛙一次跳过的距离都是正整数
// 我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点0...L
// 其中L是桥的长度坐标为 0 的点表示桥的起点,坐标为 L 的点表示桥的终点
// 青蛙从桥的起点开始,不停的向终点方向跳跃
// 一次跳跃的距离是 S 到 T 之间的任意正整数包括S,T
// 当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。
// 题目给出独木桥的长度 L青蛙跳跃的距离范围[S,T]
// 以及桥上石子的位置
// 你的任务是确定青蛙要想过河,最少需要踩到的石子数
// 测试链接 : https://www.luogu.com.cn/problem/P1052
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的所有代码,并把主类名改成"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 Code03_FrogHateStoneMinTimes {
public static int MAXN = 101;
public static int MAXL = 100001;
public static int MAXK = 201;
public static int[] arr = new int[MAXN];
public static int[] distance = new int[MAXN];
public static int[] dp = new int[MAXL];
public static boolean[] stone = new boolean[MAXL];
public static boolean[] reach = new boolean[201];
public static int l, s, t, m, cut;
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) {
l = (int) in.nval;
in.nextToken();
s = (int) in.nval;
in.nextToken();
t = (int) in.nval;
in.nextToken();
m = (int) in.nval;
for (int i = 1; i <= m; ++i) {
in.nextToken();
arr[i] = (int) in.nval;
}
if (s == t) {
int ans = 0;
for (int i = 1; i <= Math.min(l, m); ++i) {
if (arr[i] % s == 0) {
ans++;
}
}
out.println(ans);
} else { // s < t
Arrays.sort(arr, 1, m + 1);
// 可以直接给一个保守的距离,不需要算
// 因为s和t不大<= 10
cut = reduce(s, t);
for (int i = 1; i <= m; i++) {
distance[i] = distance[i - 1] + Math.min(arr[i] - arr[i - 1], cut);
stone[distance[i]] = true;
}
l = Math.min(l, distance[m] + cut);
Arrays.fill(dp, 1, l + 1, MAXN);
for (int i = 1; i <= l; i++) {
for (int j = Math.max(i - t, 0); j <= i - s; j++) {
dp[i] = Math.min(dp[i], dp[j] + (stone[i] ? 1 : 0));
}
}
int ans = MAXN;
for (int i = distance[m] + 1; i <= l; i++) {
ans = Math.min(ans, dp[i]);
}
out.println(ans);
}
out.flush();
}
}
// 一旦s和t定了那么距离多远就可以缩减
public static int reduce(int s, int t) {
Arrays.fill(reach, false);
int cnt = 0;
int ans = 0;
for (int i = 0; i < MAXK; i++) {
for (int j = i + s; j < Math.min(i + t + 1, MAXK); j++) {
reach[j] = true;
}
if (!reach[i]) {
cnt = 0;
} else {
cnt++;
}
if (cnt == t) {
ans = i;
break;
}
}
return ans;
}
}

@ -0,0 +1,80 @@
package class_2023_06_4_week;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
// 欧拉路径问题
// 给你一个下标从 0 开始的二维整数数组 pairs
// 其中 pairs[i] = [starti, endi]
// 如果 pairs 的一个重新排列
// 满足对每一个下标 i 1 <= i < pairs.length
// 都有 endi-1 == starti
// 那么我们就认为这个重新排列是 pairs 的一个 合法重新排列
// 请你返回 任意一个 pairs 的合法重新排列
// 注意:数据保证至少存在一个 pairs 的合法重新排列
// 测试链接 : https://leetcode.cn/problems/valid-arrangement-of-pairs/
public class Code04_ValidArrangementOfPairs {
// 欧拉路 : 从图中某个点出发,遍历整个图,每条边通过且只通过一次
// 欧拉回路 : 起点和终点相同的欧拉路
// 怎么判断是否存在欧拉路
// 1) 判断联通性用dfs或者并查集来检查
// 2) 无向连通图存在欧拉路的判断
// 在无向图中 : 一个点上连接边的数量,称为这个点的度数,
// 度数为奇数,称这个点为奇点;度数为偶数,称这个点为偶点。
// 如果图中的点都是偶点,则存在欧拉回路,任意点都可以成为起点或终点
// 如果图中只有两个奇点,则存在欧拉路,其中一个奇点是起点,另一个是终点
// 3) 有向连通图存在欧拉路的判断
// 在有向图中 : 分为出度和入度,出度记为+1入度记为-1
// 那么每个点自己的入度与出度相加后的值,叫做该点的度数
// 一个有向图存在欧拉回路的条件 :
// 必须该图中所有点的度数为0起点可以为任意点
// 一个有向图存在欧拉路的条件 :
// 只有一个点度数为+1只有一个点度数为-1其他点度数为0
// 并且起点是度数+1的点终点是度数-1的点
// 如上的判断,非常容易写代码,只要了解图结构,代码都不难,不再赘述
// 本题目既有有向图欧拉路的判断,但重点是欧拉路的第二个常考类型 :
// 怎么输出一个欧拉路
// 实在没有什么难度就是利用剪枝的dfs代码硬记也没多少
public static int[][] validArrangement(int[][] pairs) {
HashMap<Integer, LinkedList<Integer>> graph = new HashMap<>();
HashMap<Integer, Integer> degrees = new HashMap<>();
for (int[] pair : pairs) {
graph.putIfAbsent(pair[0], new LinkedList<>());
graph.putIfAbsent(pair[1], new LinkedList<>());
degrees.putIfAbsent(pair[0], 0);
degrees.putIfAbsent(pair[1], 0);
}
for (int[] pair : pairs) {
graph.get(pair[0]).add(pair[1]);
degrees.put(pair[0], degrees.get(pair[0]) + 1);
degrees.put(pair[1], degrees.get(pair[1]) - 1);
}
int from = pairs[0][0];
for (Integer cur : degrees.keySet()) {
if (degrees.get(cur) == 1) {
from = cur;
break;
}
}
ArrayList<int[]> record = new ArrayList<>();
dfs(from, graph, record);
int n = record.size();
int[][] ans = new int[n][2];
for (int i = n - 1, j = 0; j < n; i--, j++) {
ans[i] = record.get(j);
}
return ans;
}
public static void dfs(int from, HashMap<Integer, LinkedList<Integer>> graph, ArrayList<int[]> record) {
LinkedList<Integer> next = graph.get(from);
while (!next.isEmpty()) {
int to = next.poll();
dfs(to, graph, record);
record.add(new int[] { from, to });
}
}
}

@ -3967,3 +3967,53 @@ Oliver 想要你告诉他,他们最少要花费多少时间,才能使所有
第074节 2023年6月4周流行算法题目解析
给你一个下标从 0 开始的整数数组 nums ,它包含 n 个 互不相同 的正整数
如果 nums 的一个排列满足以下条件,我们称它是一个特别的排列
对于 0 <= i < n - 1 的下标 i
要么 nums[i] % nums[i+1] == 0
要么 nums[i+1] % nums[i] == 0
请你返回特别排列的总数目,由于答案可能很大,请将它对 1000000007 取余 后返回
测试链接 : https://leetcode.cn/problems/special-permutations/
给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time
分别表示给 n 堵不同的墙刷油漆需要的开销和时间。你有两名油漆匠
一位需要 付费 的油漆匠,刷第 i 堵墙需要花费 time[i] 单位的时间
开销为 cost[i] 单位的钱。
一位 免费 的油漆匠,刷 任意 一堵墙的时间为 1 单位,开销为 0
但是必须在付费油漆匠 工作 时,免费油漆匠才会工作
请你返回刷完 n 堵墙最少开销为多少
测试链接 : https://leetcode.cn/problems/painting-the-walls/
来自华为社招笔试
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧
在桥上有一些石子,青蛙很讨厌踩在这些石子上
由于桥的长度和青蛙一次跳过的距离都是正整数
我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点0...L
其中L是桥的长度坐标为 0 的点表示桥的起点,坐标为 L 的点表示桥的终点
青蛙从桥的起点开始,不停的向终点方向跳跃
一次跳跃的距离是 S 到 T 之间的任意正整数包括S,T
当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度 L青蛙跳跃的距离范围[S,T]
以及桥上石子的位置
你的任务是确定青蛙要想过河,最少需要踩到的石子数
测试链接 : https://www.luogu.com.cn/problem/P1052
欧拉路径问题
给你一个下标从 0 开始的二维整数数组 pairs
其中 pairs[i] = [starti, endi]
如果 pairs 的一个重新排列
满足对每一个下标 i 1 <= i < pairs.length
都有 endi-1 == starti
那么我们就认为这个重新排列是 pairs 的一个 合法重新排列
请你返回 任意一个 pairs 的合法重新排列
注意:数据保证至少存在一个 pairs 的合法重新排列
测试链接 : https://leetcode.cn/problems/valid-arrangement-of-pairs/

Loading…
Cancel
Save