modify code

master
algorithmzuo 3 years ago
parent c28c785900
commit b23029dbeb

@ -0,0 +1,182 @@
package class_2023_03_2_week;
// 爱丽丝和鲍勃继续他们的石子游戏
// 许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]
// 游戏以谁手中的石子最多来决出胜负。
// 爱丽丝和鲍勃轮流进行爱丽丝先开始。最初M = 1。
// 在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M
// 然后,令 M = max(M, X)。
// 游戏一直持续到所有石子都被拿走。
// 假设爱丽丝和鲍勃都发挥出最佳水平
// 返回爱丽丝可以得到的最大数量的石头。
// 测试链接 : https://leetcode.cn/problems/stone-game-ii/
public class Code01_StoneGameII {
// 暴力方法
// 提交会超时,但是怎么尝试已经详细阐述明白
// 提交时把stoneGameII1改名为stoneGameII
public static int stoneGameII1(int[] piles) {
return first1(piles, 0, 1);
}
// 当前先手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int first1(int[] piles, int index, int m) {
// 没有数字了收益肯定是0
if (index == piles.length) {
return 0;
}
int best = 0;
int pre = 0;
// 先手根据m做所有的尝试选最好的
// 1 2 3 .. 2m
// index....i 几个j个
// 1) index...index 1个 pre
// 2) index...index+1 2个 pre
// 3) index...index+2 3个 pre
// m = 100 1 2 3 ~ 200 1~2m
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
pre += piles[i];
// j个
// index...i (i+1....
best = Math.max(best, pre + second1(piles, i + 1, Math.max(j, m)));
}
return best;
}
// 当前后手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int second1(int[] piles, int index, int m) {
if (index == piles.length) {
return 0;
}
int worse = Integer.MAX_VALUE;
// 先手根据m做所有的尝试并且把最差的结果留给后手
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
worse = Math.min(worse, first1(piles, i + 1, Math.max(j, m)));
}
return worse;
}
// 暴力方法改成记忆化搜索
// 可以直接通过
// 提交时把stoneGameII2改名为stoneGameII
public static int stoneGameII2(int[] piles) {
int[][] f = new int[piles.length][piles.length + 1];
int[][] s = new int[piles.length][piles.length + 1];
for (int i = 0; i < piles.length; i++) {
for (int j = 0; j <= piles.length; j++) {
f[i][j] = -1;
s[i][j] = -1;
}
}
return first2(piles, 0, 1, f, s);
}
// 当前先手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int first2(int[] piles, int index, int m, int[][] f, int[][] s) {
// 没有数字了收益肯定是0
if (index == piles.length) {
return 0;
}
if (f[index][m] != -1) {
return f[index][m];
}
int best = 0;
int pre = 0;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
pre += piles[i];
best = Math.max(best, pre + second2(piles, i + 1, Math.min(piles.length, Math.max(j, m)), f, s));
}
f[index][m] = best;
return best;
}
// 当前后手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int second2(int[] piles, int index, int m, int[][] f, int[][] s) {
if (index == piles.length) {
return 0;
}
if (s[index][m] != -1) {
return s[index][m];
}
int worse = Integer.MAX_VALUE;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
worse = Math.min(worse, first2(piles, i + 1, Math.min(piles.length, Math.max(j, m)), f, s));
}
s[index][m] = worse;
return worse;
}
// 记忆化搜索改严格位置依赖的动态规划
// 提交时把stoneGameII3改名为stoneGameII
public static int stoneGameII3(int[] piles) {
int n = piles.length;
int[][] f = new int[n + 1][n + 1];
int[][] s = new int[n + 1][n + 1];
for (int index = n - 1; index >= 0; index--) {
for (int m = 1; m <= n; m++) {
int pre = 0;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
pre += piles[i];
f[index][m] = Math.max(f[index][m], pre + s[i + 1][Math.min(n, Math.max(j, m))]);
}
s[index][m] = Integer.MAX_VALUE;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
s[index][m] = Math.min(s[index][m], f[i + 1][Math.min(n, Math.max(j, m))]);
}
}
}
return f[0][1];
}
// 严格位置依赖的动态规划,一张表的版本
// 提交时把stoneGameII4改名为stoneGameII
public int stoneGameII4(int[] piles) {
int n = piles.length, sum = 0;
// dp[i][m] : 表示piles[i....]范围上在拿取范围是1~2*m的情况下先手能获得的最大收益
// 举个例子 :
// 比如当前先手可以在piles[7...]范围上在拿取范围是1 ~ 2 * 4(m)的情况下
// 先手想获得最大收益
// 当先手拿走7...9的块(这是长度为3的块)
// 后续轮到对手在piles[10...]范围上在拿取范围是1~2*m的情况下对手也会获得的最大收益
// 假设piles[7...]整体的累加和是sum
// 整体的累加和 = 先手收益 + 后手收益,因为先手和后手一定刮分整个累加和
// 所以,先手获得的最大值 = sum - 对手在先手时后续所有可能性中的最小值
int[][] dp = new int[n][n + 1];
for (int i = n - 1; i >= 0; i--) {
// i == n - 1 sum = arr[n-1]
// i == n - 2 sum += arr[n-2]
// i == n - 3 sum += arr[n-3]
sum += piles[i];
for (int m = 1; m <= n; m++) {
// dp[4][1] ...
// dp[4][2] ...
// dp[i][m]
if (i + 2 * m >= n) {
// 如果先手当前的拿取范围,可以全包下数组所有的数
// 那必然是全拿,不给对手任何机会
dp[i][m] = sum;
} else {
// 如果先手当前的拿取范围,不能全包下数组所有的数
// 那必然去是看看,对手在先手时后续所有可能性中的最小值
// nextMin就是这个含义 : 对手在先手时后续所有可能性中的最小值
int nextMin = Integer.MAX_VALUE;
for (int x = 1; x <= 2 * m; x++) {
nextMin = Math.min(nextMin, dp[i + x][Math.max(m, x)]);
}
// 先手获得的最大值 = sum - 对手在先手时后续所有可能性中的最小值
dp[i][m] = sum - nextMin;
}
}
}
return dp[0][1];
}
}

@ -0,0 +1,54 @@
package class_2023_03_2_week;
// 给出两个字符串 str1 和 str2
// 返回同时以 str1 和 str2 作为子序列的最短字符串
// 如果答案不止一个,则可以返回满足条件的任意一个答案。
// 测试链接 : https://leetcode.cn/problems/shortest-common-supersequence/
// 体系学习班,最长公共子序列问题
// 大厂刷题班章节11根据动态规划表生成路径
public class Code02_ShortestCommonSupersequence {
public static String shortestCommonSupersequence(String str1, String str2) {
int n = str1.length();
int m = str2.length();
int[][] dp = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
}
}
}
// str1 n
// str2 m
// dp[n][m] n + m - dp[n][m]
char[] ans = new char[n + m - dp[n][m]];
int ansi = ans.length - 1;
int i = n;
int j = m;
while (i > 0 && j > 0) {
// i str1的前缀长度
// j str2的前缀长度
if (dp[i][j] == dp[i - 1][j - 1] + 1 && str1.charAt(i - 1) == str2.charAt(j - 1)) {
ans[ansi--] = str1.charAt(i - 1);
i--;
j--;
} else if (dp[i][j] == dp[i - 1][j]) {
ans[ansi--] = str1.charAt(i - 1);
i--;
} else {
ans[ansi--] = str2.charAt(j - 1);
j--;
}
}
for (; i > 0; i--) {
ans[ansi--] = str1.charAt(i - 1);
}
for (; j > 0; j--) {
ans[ansi--] = str2.charAt(j - 1);
}
return String.valueOf(ans);
}
}

@ -0,0 +1,95 @@
package class_2023_03_2_week;
import java.util.Arrays;
// 来自学员问题
// 给定N、M两个参数
// 一共有N个格子每个格子可以涂上一种颜色颜色在M种里选
// 当涂满N个格子并且M种颜色都使用了叫一种有效方法
// 求一共有多少种有效方法
// 1 <= N, M <= 5000
// 返回结果比较大,请把结果 % 1000000007 之后返回
public class Code03_FillCellsUseAllColorsWays {
// 暴力方法
// 为了验证
public static int ways1(int n, int m) {
return process(new int[n], new boolean[m + 1], 0, n, m);
}
public static int process(int[] path, boolean[] set, int i, int n, int m) {
if (i == n) {
Arrays.fill(set, false);
int colors = 0;
for (int c : path) {
if (!set[c]) {
set[c] = true;
colors++;
}
}
return colors == m ? 1 : 0;
} else {
int ans = 0;
for (int j = 1; j <= m; j++) {
path[i] = j;
ans += process(path, set, i + 1, n, m);
}
return ans;
}
}
// 正式方法
// 时间复杂度O(N*M)
public static int MAXN = 5001;
public static int[][] dp = new int[MAXN][MAXN];
public static int mod = 1000000007;
public static int ways2(int n, int m) {
for (int i = 1; i <= n; i++) {
dp[i][1] = m;
}
for (int i = 2; i <= n; i++) {
for (int j = 2; j <= m; j++) {
dp[i][j] = (int) (((long) dp[i - 1][j] * j) % mod);
dp[i][j] = (int) ((((long) dp[i - 1][j - 1] * (m - j + 1)) + dp[i][j]) % mod);
}
}
return dp[n][m];
}
public static void main(String[] args) {
int N = 9;
int M = 9;
System.out.println("功能测试开始");
for (int n = 1; n <= N; n++) {
for (int m = 1; m <= M; m++) {
int ans1 = ways1(n, m);
int ans2 = ways2(n, m);
if (ans1 != ans2) {
System.out.println("出错了!");
System.out.println("n : " + n);
System.out.println("m : " + m);
System.out.println("ans1 : " + ans1);
System.out.println("ans2 : " + ans2);
break;
}
}
}
System.out.println("功能测试结束");
System.out.println("性能测试开始");
int n = 5000;
int m = 4877;
System.out.println("n : " + n);
System.out.println("m : " + m);
long start = System.currentTimeMillis();
int ans = ways2(n, m);
long end = System.currentTimeMillis();
System.out.println("取余之后的结果 : " + ans);
System.out.println("运行时间 : " + (end - start) + " 毫秒");
System.out.println("性能测试结束");
}
}

@ -0,0 +1,145 @@
package class_2023_03_2_week;
// 给定正整数 n
// 返回在 [1, n] 范围内具有 至少 1 位 重复数字的正整数的个数。
// 测试链接 : https://leetcode.cn/problems/numbers-with-repeated-digits/
public class Code04_NumbersWithRepeatedDigits {
public static int numDupDigitsAtMostN(int n) {
if (n <= 10) {
return 0;
}
// n = 23645
// len = 5长度
// n = 723645
// offset = 100000
int len = 1;
int offset = 1;
int tmp = n / 10;
while (tmp > 0) {
len++;
offset *= 10;
tmp /= 10;
}
// n = 23645 5长度
// 1长度的有几个、2长度的有几个、3长度的有几个、4长度的有几个
// 直接公式决定
int noRepeat = 0;
for (int i = 1; i < len; i++) {
noRepeat += numAllLength(i);
}
// num = 723645
// 单独求6长度的数字不重复的有几个
if (len <= 10) {
// int a = 0b 11
int status = 0b1111111111;
// n = 732645
// offset 100000
// n / offset = 7
// 1 2 3 ... 6 7-1
// ====
// n = 732645
// offset 100000
// 10000
noRepeat += ((n / offset) - 1) * numberRest(offset / 10, status ^ 1);
// n = 732645
// offset 100000
// 7.....
noRepeat += process(offset / 10, status ^ (1 << (n / offset)), n);
}
return n + 1 - noRepeat;
}
// 10进制长度必须为len的所有数中
// 每一位数字都不重复的数有多少
// 10
// 9 * 9
// 9 * 9 * 8
// 9 * 9 * 8 * 7
public static int numAllLength(int len) {
if (len > 10) {
return 0;
}
if (len == 1) {
return 10;
}
int ans = 9;
int cur = 9;
while (--len > 0) {
ans *= cur;
cur--;
}
return ans;
}
// n = 732645
// 10000
// status : 还剩哪些数字可以选
// 返回值 : 前面的数字都和n一样剩下的位里有多少数字剩下的数字都不同
public static int process(int offset, int status, int n) {
if (offset == 0) {
// n = 732645
// 0
return 1;
}
int ans = 0;
// n = 732645
// off 100
// 732....
// 7320...
// 7321...
// 7322... X
// 7323... X
// 7324...
// 7325...
//
// n = 732645
// off 100
// 6
// 7326 % 10 = 6
int first = (n / offset) % 10;
for (int cur = 0; cur < first; cur++) {
if ((status & (1 << cur)) != 0) {
ans += numberRest(offset / 10, status ^ (1 << cur));
}
}
// n = 732645
// off 10
// 7326 ..
if ((status & (1 << first)) != 0) {
ans += process(offset / 10, status ^ (1 << first), n);
}
return ans;
}
// offset : 1000, 还剩4长度
// offset : 100000, 还剩7长度
// status : 还有哪些数字可以选
// 状态!
// 9 8 7 6 5 4 3 2 1 0
// 0 1 1 1 0 0 0 1 1 1
public static int numberRest(int offset, int status) {
// c 还有几种数字可以选!
// offset = 1000; 100 10 1 0
// c = 7 6 5 4 3
// ans = 1 * 7 * 6 * 5 * 4
int c = hammingWeight(status);
int ans = 1;
while (offset > 0) {
ans *= c;
c--;
offset /= 10;
}
return ans;
}
public static int hammingWeight(int n) {
n = (n & 0x55555555) + ((n >>> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f);
n = (n & 0x00ff00ff) + ((n >>> 8) & 0x00ff00ff);
n = (n & 0x0000ffff) + ((n >>> 16) & 0x0000ffff);
return n;
}
}

@ -0,0 +1,101 @@
package class_2023_03_2_week;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
// 如果你熟悉 Shell 编程,那么一定了解过花括号展开,它可以用来生成任意字符串。
// 花括号展开的表达式可以看作一个由 花括号、逗号 和 小写英文字母 组成的字符串
// 定义下面几条语法规则:
// 如果只给出单一的元素 x那么表达式表示的字符串就只有 "x"。R(x) = {x}
// 例如,表达式 "a" 表示字符串 "a"。
// 而表达式 "w" 就表示字符串 "w"。
// 当两个或多个表达式并列,以逗号分隔,我们取这些表达式中元素的并集
// R({e_1,e_2,...}) = R(e_1) R(e_2) ...
// 例如,表达式 "{a,b,c}" 表示字符串 "a","b","c"。
// 而表达式 "{{a,b},{b,c}}" 也可以表示字符串 "a","b","c"。
// 要是两个或多个表达式相接,中间没有隔开时,
// 我们从这些表达式中各取一个元素依次连接形成字符串
// R(e_1 + e_2) = {a + b for (a, b) in R(e_1) × R(e_2)}
// 例如,表达式 "{a,b}{c,d}" 表示字符串 "ac","ad","bc","bd"。
// 表达式之间允许嵌套,单一元素与表达式的连接也是允许的。
// 例如,表达式 "a{b,c,d}" 表示字符串 "ab","ac","ad"​​​​​​。
// 例如,表达式 "a{b,c}{d,e}f{g,h}"
// 可以表示字符串 :
// "abdfg", "abdfh", "abefg", "abefh",
// "acdfg", "acdfh", "acefg", "acefh"。
// 给出表示基于给定语法规则的表达式 expression
// 返回它所表示的所有字符串组成的有序列表。
// 测试链接 : https://leetcode.cn/problems/brace-expansion-ii/
public class Code05_BraceExpansionII {
public static List<String> braceExpansionII(String expression) {
return new ArrayList<>(process(expression.toCharArray(), 0).ans);
}
public static class Info {
public TreeSet<String> ans;
public int end;
public Info(TreeSet<String> a, int e) {
ans = a;
end = e;
}
}
// exp[start..........] 遇到我的} 或者exp终止位置
public static Info process(char[] exp, int start) {
// 最终的结果,返回
TreeSet<String> ans = new TreeSet<>();
// 集合A X 集合B X 集合C , 遇到, parts清空
// ................ 遇到, parts清空
// 每一回的结果加入到ans
List<TreeSet<String>> parts = new ArrayList<>();
// 收集字符
StringBuilder builder = new StringBuilder();
while (start != exp.length && exp[start] != '}') {
if (exp[start] == '{') {
addStringToParts(builder, parts);
Info next = process(exp, start + 1);
parts.add(next.ans);
start = next.end + 1;
} else if (exp[start] == ',') {
addStringToParts(builder, parts);
addPartsToSet(ans, parts);
start++;
parts.clear();
} else { // 遇到英文字母
builder.append(exp[start]);
start++;
}
}
addStringToParts(builder, parts);
addPartsToSet(ans, parts);
return new Info(ans, start);
}
public static void addStringToParts(StringBuilder builder, List<TreeSet<String>> parts) {
if (builder.length() != 0) {
parts.add(new TreeSet<>());
parts.get(parts.size() - 1).add(builder.toString());
builder.delete(0, builder.length());
}
}
public static void addPartsToSet(TreeSet<String> ans, List<TreeSet<String>> parts) {
process(parts, 0, "", ans);
}
public static void process(List<TreeSet<String>> list, int i, String path, TreeSet<String> ans) {
if (i == list.size()) {
if (!path.equals("")) {
ans.add(path);
}
} else {
for (String cur : list.get(i)) {
process(list, i + 1, path + cur, ans);
}
}
}
}

@ -3150,15 +3150,61 @@ HH相信不同的贝壳会带来好运所以每次散步完后他都会随
第061节 2023年3月第2周流行算法题目解析
爱丽丝和鲍勃继续他们的石子游戏
许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]
游戏以谁手中的石子最多来决出胜负。
爱丽丝和鲍勃轮流进行爱丽丝先开始。最初M = 1。
在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M
然后,令 M = max(M, X)。
游戏一直持续到所有石子都被拿走。
假设爱丽丝和鲍勃都发挥出最佳水平
返回爱丽丝可以得到的最大数量的石头。
测试链接 : https://leetcode.cn/problems/stone-game-ii/
给出两个字符串 str1 和 str2
返回同时以 str1 和 str2 作为子序列的最短字符串
如果答案不止一个,则可以返回满足条件的任意一个答案。
测试链接 : https://leetcode.cn/problems/shortest-common-supersequence/
体系学习班,最长公共子序列问题
大厂刷题班章节11根据动态规划表生成路径
来自学员问题
给定N、M两个参数
一共有N个格子每个格子可以涂上一种颜色颜色在M种里选
当涂满N个格子并且M种颜色都使用了叫一种有效方法
求一共有多少种有效方法
1 <= N, M <= 5000
返回结果比较大,请把结果 % 1000000007 之后返回
给定正整数 n
返回在 [1, n] 范围内具有 至少 1 位 重复数字的正整数的个数。
测试链接 : https://leetcode.cn/problems/numbers-with-repeated-digits/
如果你熟悉 Shell 编程,那么一定了解过花括号展开,它可以用来生成任意字符串。
花括号展开的表达式可以看作一个由 花括号、逗号 和 小写英文字母 组成的字符串
定义下面几条语法规则:
如果只给出单一的元素 x那么表达式表示的字符串就只有 "x"。R(x) = {x}
例如,表达式 "a" 表示字符串 "a"。
而表达式 "w" 就表示字符串 "w"。
当两个或多个表达式并列,以逗号分隔,我们取这些表达式中元素的并集
R({e_1,e_2,...}) = R(e_1) R(e_2) ...
例如,表达式 "{a,b,c}" 表示字符串 "a","b","c"。
而表达式 "{{a,b},{b,c}}" 也可以表示字符串 "a","b","c"。
要是两个或多个表达式相接,中间没有隔开时,
我们从这些表达式中各取一个元素依次连接形成字符串
R(e_1 + e_2) = {a + b for (a, b) in R(e_1) × R(e_2)}
例如,表达式 "{a,b}{c,d}" 表示字符串 "ac","ad","bc","bd"。
表达式之间允许嵌套,单一元素与表达式的连接也是允许的。
例如,表达式 "a{b,c,d}" 表示字符串 "ab","ac","ad"​​​​​​。
例如,表达式 "a{b,c}{d,e}f{g,h}"
可以表示字符串 :
"abdfg", "abdfh", "abefg", "abefh",
"acdfg", "acdfh", "acefg", "acefh"。
给出表示基于给定语法规则的表达式 expression
返回它所表示的所有字符串组成的有序列表。
测试链接 : https://leetcode.cn/problems/brace-expansion-ii/

Loading…
Cancel
Save