modify code

master
algorithmzuo 3 years ago
parent 8926b00424
commit e964eef80f

@ -0,0 +1,128 @@
package class_2022_12_2_week;
import java.math.BigDecimal;
import java.math.RoundingMode;
// 有100个犯人被关在监狱里编号0~99
// 监狱长构思了一个处决犯人的计划
// 监狱长准备了100个盒子每个盒子里面装有一个犯人的名字
// 一开始每个犯人都知道自己的盒子在哪
// 因为自己的编号,与盒子的编号一致,
// 这100个盒子排成一排放在一个房间里面盒子编号0~99从左到右排列
// 监狱长彻底随机的打乱了盒子的排列,并且犯人并没有看到打乱的过程
// 监狱长规定每个犯人依次进入房间每个犯人都可以开启50个盒子然后关上
// 每一个犯人全程无法进行任何交流,完全无法传递任何信息
// 监狱长规定每个犯人在尝试50次的过程中都需要找到自己的名字
// 如果有哪怕一个犯人没有做到这一点100个罪犯全部处决
// 但是监狱长允许这个游戏开始之前,所有犯人在一起商量策略
// 请尽量制定一个让所有人存活概率最大的策略
// 来自论文<The Cell Probe Complexity of Succinct Data Structures>
// 作者Anna Gal和Peter Bro Miltersen写于2007年
// 如今该题变成了流行科普视频,我们来玩一玩
public class Code01_PrisonersEscapeGame {
// 通过多次模拟实验得到的概率
public static double escape1(int people, int tryTimes, int testTimes) {
int escape = 0;
for (int i = 0; i < testTimes; i++) {
int[] arr = generateRandomArray(people);
if (maxCircle(arr) <= tryTimes) {
escape++;
}
}
return (double) escape / (double) testTimes;
}
// 求arr中最大环的长度
public static int maxCircle(int[] arr) {
int maxCircle = 1;
for (int i = 0; i < arr.length; i++) {
int curCircle = 1;
while (i != arr[i]) {
swap(arr, i, arr[i]);
curCircle++;
}
maxCircle = Math.max(maxCircle, curCircle);
}
return maxCircle;
}
// 生成随机arr
// 原本每个位置的数都等概率出现在自己或者其他位置
public static int[] generateRandomArray(int len) {
int[] arr = new int[len];
for (int i = 0; i < len; i++) {
arr[i] = i;
}
for (int i = len - 1; i > 0; i--) {
swap(arr, i, (int) (Math.random() * (i + 1)));
}
return arr;
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 公式计算得到的概率
// 请确保tryTimes大于等于people的一半
// 生还的方法数 : C(r,100) * (r-1)! * (100-r)!r从51~100
public static double escape2(int people, int tryTimes) {
BigDecimal kill = new BigDecimal("0");
for (int maxCircle = tryTimes + 1; maxCircle <= people; maxCircle++) {
// 选出maxCircle个人组成最大环的选择方案数
BigDecimal a = c(maxCircle, people);
// 确保这maxCircle个人一定能组成一个大环
// 而不是多个环的方法数
BigDecimal b = factorial(maxCircle - 1);
// 剩下人随意站形成的方法数
BigDecimal c = factorial(people - maxCircle);
kill = kill.add(a.multiply(b).multiply(c));
}
return 1D - kill.divide(factorial(people), 10, RoundingMode.HALF_UP).doubleValue();
}
// 组合公式
// 从总共n个数里选a个数的方法数
public static BigDecimal c(int a, int n) {
BigDecimal all = factorial(n);
BigDecimal down1 = factorial(a);
BigDecimal down2 = factorial(n - a);
return all.divide(down1).divide(down2);
}
// 求number的阶乘
public static BigDecimal factorial(int number) {
BigDecimal ans = new BigDecimal("1");
for (int i = 1; i <= number; i++) {
ans = ans.multiply(new BigDecimal(i));
}
return ans;
}
// 公式化简之后的最终简洁版
// 请确保tryTimes大于等于people的一半
public static double escape3(int people, int tryTimes) {
double a = 0;
for (int r = tryTimes + 1; r <= people; r++) {
a += (double) 1 / (double) r;
}
return (double) 1 - a;
}
public static void main(String[] args) {
int people = 100;
// 请确保tryTimes大于等于people的一半
int tryTimes = 50;
int testTimes = 100000;
System.out.println("参与游戏的人数 : " + people);
System.out.println("每人的尝试次数 : " + tryTimes);
System.out.println("模拟实验的次数 : " + testTimes);
System.out.println("通过模拟实验得到的概率为 : " + escape1(people, tryTimes, testTimes));
System.out.println("通过公式计算得到的概率为 : " + escape2(people, tryTimes));
System.out.println("通过化简公式得到的概率为 : " + escape3(people, tryTimes));
}
}

@ -0,0 +1,83 @@
package class_2022_12_2_week;
// 来自亚马逊、谷歌、微软、Facebook、Bloomberg
// 给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。
// 返回执行此操作后grid 中最大的岛屿面积是多少?
// 岛屿 由一组上、下、左、右四个方向相连的 1 形成。
// 测试链接 : https://leetcode.cn/problems/making-a-large-island/
public class Code02_MakingALargeIsland {
public static int largestIsland(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
int id = 2;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1) {
infect(grid, i, j, id++, n, m);
}
}
}
// ? ? ?
// 0 1 2 3 4 9
int[] sizes = new int[id];
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] > 1) {
// grid[i][j] = 4
// sizes[4]++
ans = Math.max(ans, ++sizes[grid[i][j]]);
}
}
}
boolean[] visited = new boolean[id];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 0) {
int up = i - 1 >= 0 ? grid[i - 1][j] : 0;
int down = i + 1 < n ? grid[i + 1][j] : 0;
int left = j - 1 >= 0 ? grid[i][j - 1] : 0;
int right = j + 1 < m ? grid[i][j + 1] : 0;
int merge = 1 + sizes[up];
visited[up] = true;
if (!visited[down]) {
merge += sizes[down];
visited[down] = true;
}
if (!visited[left]) {
merge += sizes[left];
visited[left] = true;
}
if (!visited[right]) {
merge += sizes[right];
visited[right] = true;
}
ans = Math.max(ans, merge);
visited[up] = false;
visited[down] = false;
visited[left] = false;
visited[right] = false;
}
}
}
return ans;
}
// grid
// (i,j) == 1 -> v
// (i,j) != 1 -> return什么也不做
// (i,j) 越界return什么也不做
public static void infect(int[][] grid, int i, int j, int v, int n, int m) {
if (i < 0 || i == n || j < 0 || j == m || grid[i][j] != 1) {
return;
}
// (i,j) 不越界,(i,j) == 1
grid[i][j] = v;
infect(grid, i - 1, j, v, n, m);
infect(grid, i + 1, j, v, n, m);
infect(grid, i, j - 1, v, n, m);
infect(grid, i, j + 1, v, n, m);
}
}

@ -0,0 +1,40 @@
package class_2022_12_2_week;
import java.util.ArrayList;
import java.util.HashMap;
// 我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符,
// 并返回唯一字符的个数。
// 例如s = "LEETCODE" ,则其中 "L", "T","C","O","D" 都是唯一字符,
// 因为它们只出现一次,所以 countUniqueChars(s) = 5 。
// 本题将会给你一个字符串 s ,我们需要返回 countUniqueChars(t) 的总和,
// 其中 t 是 s 的子字符串。输入用例保证返回值为 32 位整数。
// 注意,某些子字符串可能是重复的,但你统计时也必须算上这些重复的子字符串
//(也就是说,你必须统计 s 的所有子字符串中的唯一字符)。
// 测试链接 : https://leetcode.cn/problems/count-unique-characters-of-all-substrings-of-a-given-string/
public class Code03_CountUniqueCharactersOfAllSubstrings {
public static int uniqueLetterString(String s) {
// key : 某一种字符
// value : 出现这种字符依次的位置
HashMap<Character, ArrayList<Integer>> indies = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (!indies.containsKey(c)) {
indies.put(c, new ArrayList<>());
indies.get(c).add(-1);
}
indies.get(c).add(i);
}
int res = 0;
for (HashMap.Entry<Character, ArrayList<Integer>> entry : indies.entrySet()) {
ArrayList<Integer> arr = entry.getValue();
arr.add(s.length());
for (int i = 1; i < arr.size() - 1; i++) {
res += (arr.get(i) - arr.get(i - 1)) * (arr.get(i + 1) - arr.get(i));
}
}
return res;
}
}

@ -0,0 +1,105 @@
package class_2022_12_2_week;
import java.util.Arrays;
// 石子游戏中,爱丽丝和鲍勃轮流进行自己的回合,爱丽丝先开始 。
// 有 n 块石子排成一排。
// 每个玩家的回合中,可以从行中 移除 最左边的石头或最右边的石头,
// 并获得与该行中剩余石头值之 和 相等的得分。当没有石头可移除时,得分较高者获胜。
// 鲍勃发现他总是输掉游戏(可怜的鲍勃,他总是输),
// 所以他决定尽力 减小得分的差值 。爱丽丝的目标是最大限度地 扩大得分的差值 。
// 给你一个整数数组 stones ,其中 stones[i] 表示 从左边开始 的第 i 个石头的值,
// 如果爱丽丝和鲍勃都 发挥出最佳水平 ,请返回他们 得分的差值 。
// 测试链接 : https://leetcode.cn/problems/stone-game-vii/
public class Code04_StoneGameVII {
// 会超时但是思路是对的,如果想通过就把这个暴力递归改成下面的动态规划
// 改法课上都讲了
public static int stoneGameVII1(int[] stones) {
int sum = 0;
for (int num : stones) {
sum += num;
}
int alice = f(stones, sum, 0, stones.length - 1);
int bob = s(stones, sum, 0, stones.length - 1);
return Math.abs(alice - bob);
}
// 先手
public static int f(int[] stones, int sum, int L, int R) {
if (L == R) { // 只能一块儿了!
return 0;
}
// p1
// L
int p1 = sum - stones[L] + s(stones, sum - stones[L], L + 1, R);
int against1 = f(stones, sum - stones[L], L + 1, R);
// p2
// R
int p2 = sum - stones[R] + s(stones, sum - stones[R], L, R - 1);
int against2 = f(stones, sum - stones[R], L, R - 1);
return (p1 - against1) > (p2 - against2) ? p1 : p2;
}
// 后手!
public static int s(int[] stones, int sum, int L, int R) {
if (L == R) {
return 0;
}
// 当前的是后手
// 对手,先手!
int against1 = sum - stones[L] + s(stones, sum - stones[L], L + 1, R);
// 当前用户的得分!后手!是对手决定的!
int get1 = f(stones, sum - stones[L], L + 1, R);
int against2 = sum - stones[R] + s(stones, sum - stones[R], L, R - 1);
int get2 = f(stones, sum - stones[R], L, R - 1);
return (against1 - get1) > (against2 - get2) ? get1 : get2;
}
// 动态规划版
public static int stoneGameVII2(int[] stones) {
int N = stones.length;
int[] presum = new int[N + 1];
for (int i = 0; i < N; i++) {
presum[i + 1] = presum[i] + stones[i];
}
int[][] dpf = new int[N][N];
int[][] dps = new int[N][N];
for (int L = N - 2; L >= 0; L--) {
for (int R = L + 1; R < N; R++) {
int sumLR = presum[R + 1] - presum[L];
int a = sumLR - stones[L] + dps[L + 1][R];
int b = dpf[L + 1][R];
int c = sumLR - stones[R] + dps[L][R - 1];
int d = dpf[L][R - 1];
dpf[L][R] = (a - b > c - d) ? a : c;
dps[L][R] = (a - b > c - d) ? b : d;
}
}
return Math.abs(dpf[0][N - 1] - dps[0][N - 1]);
}
// 另一种尝试 + static动态规划表 + 空间压缩 + 尽量优化
// dp[len][i] : 从i出发当长度为len的情况下Alice能比Bob多多少分
// 要注意结算时机!这是这种尝试的核心!
public static int[] dp = new int[1000];
// 时间复杂度和刚才讲的一样!
public int stoneGameVII3(int[] s) {
int n = s.length;
Arrays.fill(dp, 0, n, 0);
if (n % 2 == 0) {
for (int i = 0; i < n; i++) {
dp[i] = s[i];
}
}
boolean alicePick = n % 2 == 0;
for (int len = 2; len <= n; len++, alicePick = !alicePick) {
for (int i = 0, j = len - 1; j < n; i++, j++) {
dp[i] = alicePick ? Math.max(dp[i], dp[i + 1]) : Math.min(dp[i] + s[j], s[i] + dp[i + 1]);
}
}
return dp[0];
}
}

@ -0,0 +1,90 @@
package class_2022_12_2_week;
// 给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表
// 如果在二叉树中,存在一条一直向下的路径
// 且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True
// 否则返回 False 。
// 一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。
// 测试链接 : https://leetcode.cn/problems/linked-list-in-binary-tree/
// 最优解是KMP算法来解
// 官方题解都没有写的最优解
// 如果二叉树节点数是N链表节点数M时间复杂度为O(M+N)
public class Code05_LinkedListInBinaryTree {
// 不提交这个类
public class ListNode {
int val;
ListNode next;
}
// 不提交这个类
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
}
// 最优解
// 官方题解都没有写的最优解
// KMP算法来解
// 如果二叉树节点数是N链表节点数M时间复杂度为O(M+N)
// 提交如下的所有方法,可以直接通过
public static boolean isSubPath(ListNode head, TreeNode root) {
int n = 0;
ListNode tmp = head;
while (tmp != null) {
n++;
tmp = tmp.next;
}
int[] match = new int[n];
n = 0;
while (head != null) {
match[n++] = head.val;
head = head.next;
}
int[] next = getNextArray(match);
return find(root, 0, match, next);
}
public static int[] getNextArray(int[] match) {
if (match.length == 1) {
return new int[] { -1 };
}
int[] next = new int[match.length];
next[0] = -1;
next[1] = 0;
int i = 2;
int cn = 0;
while (i < next.length) {
if (match[i - 1] == match[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
}
// 当前目标串 -> TreeNode cur 这个字符
// 一个叫match串matchmi位置的字符
// 返回cur后续的路能不能把match串配完
public static boolean find(TreeNode cur, int mi, int[] match, int[] next) {
if (mi == match.length) {
return true;
}
if (cur == null) {
return false;
}
// 当前目标串的字符 : cur.val
// 当前match串的字符 : match[mi]
while (mi >= 0 && cur.val != match[mi]) {
mi = next[mi];
}
// 后续的字符,先走左,配去!
// 后续的字符,分裂可能性了!走右,配去!
return find(cur.left, mi + 1, match, next) || find(cur.right, mi + 1, match, next);
}
}

@ -2650,7 +2650,60 @@ number调用次数 <= 100000
第052节 2022年12月第2周流行算法题目解析
有100个犯人被关在监狱里编号0~99
监狱长构思了一个处决犯人的计划
监狱长准备了100个盒子每个盒子里面装有一个犯人的名字
一开始每个犯人都知道自己的盒子在哪
因为自己的编号,与盒子的编号一致,
这100个盒子排成一排放在一个房间里面盒子编号0~99从左到右排列
监狱长彻底随机的打乱了盒子的排列,并且犯人并没有看到打乱的过程
监狱长规定每个犯人依次进入房间每个犯人都可以开启50个盒子然后关上
每一个犯人全程无法进行任何交流,完全无法传递任何信息
监狱长规定每个犯人在尝试50次的过程中都需要找到自己的名字
如果有哪怕一个犯人没有做到这一点100个罪犯全部处决
但是监狱长允许这个游戏开始之前,所有犯人在一起商量策略
请尽量制定一个让所有人存活概率最大的策略
来自论文<The Cell Probe Complexity of Succinct Data Structures>
作者Anna Gal和Peter Bro Miltersen写于2007年
如今该题变成了流行科普视频,我们来玩一玩
来自亚马逊、谷歌、微软、Facebook、Bloomberg
给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。
返回执行此操作后grid 中最大的岛屿面积是多少?
岛屿 由一组上、下、左、右四个方向相连的 1 形成。
测试链接 : https://leetcode.cn/problems/making-a-large-island/
我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符,
并返回唯一字符的个数。
例如s = "LEETCODE" ,则其中 "L", "T","C","O","D" 都是唯一字符,
因为它们只出现一次,所以 countUniqueChars(s) = 5 。
本题将会给你一个字符串 s ,我们需要返回 countUniqueChars(t) 的总和,
其中 t 是 s 的子字符串。输入用例保证返回值为 32 位整数。
注意,某些子字符串可能是重复的,但你统计时也必须算上这些重复的子字符串
(也就是说,你必须统计 s 的所有子字符串中的唯一字符)。
测试链接 : https://leetcode.cn/problems/count-unique-characters-of-all-substrings-of-a-given-string/
石子游戏中,爱丽丝和鲍勃轮流进行自己的回合,爱丽丝先开始 。
有 n 块石子排成一排。
每个玩家的回合中,可以从行中 移除 最左边的石头或最右边的石头,
并获得与该行中剩余石头值之 和 相等的得分。当没有石头可移除时,得分较高者获胜。
鲍勃发现他总是输掉游戏(可怜的鲍勃,他总是输),
所以他决定尽力 减小得分的差值 。爱丽丝的目标是最大限度地 扩大得分的差值 。
给你一个整数数组 stones ,其中 stones[i] 表示 从左边开始 的第 i 个石头的值,
如果爱丽丝和鲍勃都 发挥出最佳水平 ,请返回他们 得分的差值 。
测试链接 : https://leetcode.cn/problems/stone-game-vii/
给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表
如果在二叉树中,存在一条一直向下的路径
且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True
否则返回 False 。
一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。
测试链接 : https://leetcode.cn/problems/linked-list-in-binary-tree/
最优解是KMP算法来解
官方题解都没有写的最优解
如果二叉树节点数是N链表节点数M时间复杂度为O(M+N)

Loading…
Cancel
Save