modify code

master
algorithmzuo 2 years ago
parent 702a13417e
commit d3ce706ab7

@ -0,0 +1,100 @@
package class_2023_05_3_week;
// 保证一定是n*n的正方形实现从里到外转圈打印的功能
// 如果n是奇数中心点唯一比如
// a b c
// d e f
// g h i
// e是中心点依次打印 : e f i h g d a b c
// 如果n是偶数中心点为最里层2*2的右下点
// 比如
// a b c d e f
// g h i j k l
// m n o p q r
// s t u v w x
// y z 0 1 2 3
// 4 5 6 7 8 9
// 最里层是
// o p
// u v
// v是中心点依次打印 : v u o p q w ....
public class Code01_PrintFromInnerLoop {
public static void print(char[][] m) {
int n = m.length;
for (int a = (n - 1) / 2, b = (n - 1) / 2,
c = n / 2, d = n / 2;
a >= 0;
a--, b--, c++, d++) {
loop(m, a, b, c, d);
}
System.out.println();
}
public static void loop(char[][] m, int a, int b, int c, int d) {
if (a == c) {
System.out.print(m[a][b] + " ");
} else {
for (int row = a + 1; row <= c; row++) {
System.out.print(m[row][d] + " ");
}
for (int col = d - 1; col >= b; col--) {
System.out.print(m[c][col] + " ");
}
for (int row = c - 1; row >= a; row--) {
System.out.print(m[row][b] + " ");
}
for (int col = b + 1; col <= d; col++) {
System.out.print(m[a][col] + " ");
}
}
}
public static void main(String[] args) {
char[][] map1 = {
{ 'a' }
};
print(map1);
char[][] map2 = {
{ 'a', 'b' },
{ 'c', 'd' }
};
print(map2);
char[][] map3 = {
{ 'a', 'b', 'c' },
{ 'd', 'e', 'f' },
{ 'g', 'h', 'i' }
};
print(map3);
char[][] map4 = {
{ 'a', 'b', 'c', 'd' },
{ 'e', 'f', 'g', 'h' },
{ 'i', 'j', 'k', 'l' },
{ 'm', 'n', 'o', 'p' }
};
print(map4);
char[][] map5 = {
{ 'a', 'b', 'c', 'd', 'e' },
{ 'f', 'g', 'h', 'i', 'j' },
{ 'k', 'l', 'm', 'n', 'o' },
{ 'p', 'q', 'r', 's', 't' },
{ 'u', 'v', 'w', 'x', 'y' }
};
print(map5);
char[][] map6 = {
{ 'a', 'b', 'c', 'd', 'e', 'f' },
{ 'g', 'h', 'i', 'j', 'k', 'l' },
{ 'm', 'n', 'o', 'p', 'q', 'r' },
{ 's', 't', 'u', 'v', 'w', 'x' },
{ 'y', 'z', '0', '1', '2', '3' },
{ '4', '5', '6', '7', '8', '9' },
};
print(map6);
}
}

@ -0,0 +1,62 @@
package class_2023_05_3_week;
// 来自学员问题
// 假设每一次获得随机数的时候这个数字大于100的概率是P
// 尝试N次其中大于100的次数在A次~B次之间的概率是多少?
// 0 < P < 1, P是double类型
// 1 <= A <= B <= N <= 100
public class Code02_More100TimesBetweenAB {
public static double probability(double P, int N, int A, int B) {
double[][] dp = new double[N + 1][N + 1];
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= N; j++) {
dp[i][j] = -1;
}
}
double ans = 0;
for (int j = A; j <= B; j++) {
ans += process(P, 1D - P, N, j, dp);
}
return ans;
}
// 获得随机数大于100的概率是more
// 获得随机数小于等于100的概率是less
// 还有i次需要去扔
// 扔出大于100的次数必须是j次
// 返回概率
public static double process(double more, double less, int i, int j, double[][] dp) {
if (i < 0 || j < 0 || i < j) {
return 0D;
}
// i >= 0 & j >= 0 & i >= j
if (i == 0 && j == 0) {
return 1D;
}
// i < 0
// i == 0 j > 0
// i == 0 j < 0
// i == 0 j == 0
// 如果是上面四种情况,都提前返回了
// i > 0 & i >= j
if (dp[i][j] != -1) {
return dp[i][j];
}
// 1) > 100 情况more * (i - 1, j - 1)
// 2) <= 100 情况 , less * (i - 1, j)
// 总体达成的概率 = 1) + 2)
double ans = more * process(more, less, i - 1, j - 1, dp) + less * process(more, less, i - 1, j, dp);
dp[i][j] = ans;
return ans;
}
public static void main(String[] args) {
double P = 0.6;
int N = 100;
int A = 30;
int B = 50;
System.out.println(probability(P, N, A, B));
}
}

@ -0,0 +1,51 @@
package class_2023_05_3_week;
// 在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始
// 并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0)
// 右下单元格是 (n - 1, n - 1)象棋骑士有8种可能的走法
// 每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格,类似马走日
// 每次骑士要移动时它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。
// 骑士继续移动,直到它走了 k 步或离开了棋盘
// 返回 骑士在棋盘停止移动后仍留在棋盘上的概率
// 测试链接 : https://leetcode.cn/problems/knight-probability-in-chessboard/
public class Code03_KnightProbabilityInChessboard {
public double knightProbability(int n, int k, int row, int column) {
double[][][] dp = new double[n][n][k + 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int t = 0; t <= k; t++) {
dp[i][j][t] = -1;
}
}
}
return process2(n, k, row, column, dp);
}
// (r,c) , 还剩rest步去走
// 走的过程中,出棋盘就算死,走完之后还活的概率
public double process2(int n, int rest, int r, int c, double[][][] dp) {
if (r < 0 || r >= n || c < 0 || c >= n) {
return 0;
}
if (dp[r][c][rest] != -1) {
return dp[r][c][rest];
}
double ans = 0;
if (rest == 0) {
ans = 1;
} else {
ans += (process2(n, rest - 1, r - 2, c + 1, dp) / 8);
ans += (process2(n, rest - 1, r - 1, c + 2, dp) / 8);
ans += (process2(n, rest - 1, r + 1, c + 2, dp) / 8);
ans += (process2(n, rest - 1, r + 2, c + 1, dp) / 8);
ans += (process2(n, rest - 1, r + 2, c - 1, dp) / 8);
ans += (process2(n, rest - 1, r + 1, c - 2, dp) / 8);
ans += (process2(n, rest - 1, r - 1, c - 2, dp) / 8);
ans += (process2(n, rest - 1, r - 2, c - 1, dp) / 8);
}
dp[r][c][rest] = ans;
return ans;
}
}

@ -0,0 +1,113 @@
package class_2023_05_3_week;
// 来自小红书
// 给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1
// 每一次移动,你可以选择 相邻 两个数字并将它们交换
// 请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数
// 测试链接 : https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/
public class Code04_MinimumAdjacentSwapsForConsecutiveOnes {
// . . . 1 0 1 0 0 | 0 0 X X |
// 3 4 5 6 7 8 9
// 假设3位置的1和5位置的1移动到8、9位置
// 最优移动是 :
// 5位置的1移动到9位置
// 3位置的1移动到8位置
// 距离和 = 9 - 5 + 8 - 3
// 距离和 = 9 + 8 - (5 + 3)
// 推广 :
// 假设最优移动是 :
// a位置的1向右移动到c位置
// b位置的1向右移动到d位置
// 距离和 = c + d - (a + b)
// 这是对所有1向右移动来说的
// 那么所有1向左移动是同理的
// . . . | X X 0 0 | 0 1 0 0 1
// c d 5 a 7 8 b
// 假设最优移动是 :
// a位置的1向左移动到c位置
// b位置的1向左移动到d位置
// 距离和 = a - c + b - d = c + d - (a + b) 整体的绝对值
// 综上,不管向左还是向右
// 假设最优移动是 :
// a位置的1移动到c位置
// b位置的1移动到d位置
// 距离和 = | c + d - (a + b) |
// 枚举所有K长度的窗口 :
// 对窗口的左半边来说就是再左边的1向右移动
// 对窗口的右半边来说就是再右边的1向左移动
// 举个例子
// 0 1 1 0 1 1 1 | 0 0 0 0 | 1 0 1 1
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 假设K = 4
// 当前窗口来到7~10位置的区域
// 那么这个窗口的7位置、8位置是左半边
// 这个窗口的9位置、10位置是右半边
// 此时的最优策略为 :
// 对左半边来说,
// 6位置的1应该去8位置
// 5位置的1应该去7位置
// 对右半边来说,
// 11位置的1应该去9位置
// 13位置的1应该去10位置
// 上面就是某一个长度为K的窗口最优的移动策略
// 那么枚举每一个长度为K的窗口选出代价最好的时候就是答案
// 当然求每一个窗口的代价,就是上文给你说的公式
public static int minMoves(int[] nums, int k) {
if (k == 1) {
return 0;
}
int n = nums.length;
int x = (k - 1) / 2;
int leftAimIndiesSum = x * (x + 1) / 2;
int rightAimIndiesSum = (int) ((long) (k - 1) * k / 2 - leftAimIndiesSum);
int ans = Integer.MAX_VALUE;
int l = 0;
int m = (k - 1) / 2;
int r = k - 1;
int leftNeedOnes = m + 1;
int leftWindowL = 0;
int leftWindowOnes = 0;
int leftWindowOnesIndiesSum = 0;
for (int i = 0; i < m; i++) {
if (nums[i] == 1) {
leftWindowOnes++;
leftWindowOnesIndiesSum += i;
}
}
int rightNeedOnes = k - leftNeedOnes;
int rightWindowR = m;
int rightWindowOnes = nums[m];
int rightWindowOnesIndiesSum = nums[m] == 1 ? m : 0;
for (; r < n; l++, m++, r++) {
if (nums[m] == 1) {
leftWindowOnes++;
leftWindowOnesIndiesSum += m;
rightWindowOnes--;
rightWindowOnesIndiesSum -= m;
}
while (leftWindowOnes > leftNeedOnes) {
if (nums[leftWindowL] == 1) {
leftWindowOnes--;
leftWindowOnesIndiesSum -= leftWindowL;
}
leftWindowL++;
}
while (rightWindowOnes < rightNeedOnes && rightWindowR + 1 < n) {
if (nums[rightWindowR + 1] == 1) {
rightWindowOnes++;
rightWindowOnesIndiesSum += rightWindowR + 1;
}
rightWindowR++;
}
if (leftWindowOnes == leftNeedOnes && rightWindowOnes == rightNeedOnes) {
ans = Math.min(ans,
leftAimIndiesSum - leftWindowOnesIndiesSum + rightWindowOnesIndiesSum - rightAimIndiesSum);
}
leftAimIndiesSum += m + 1 - l;
rightAimIndiesSum += r - m;
}
return ans;
}
}

@ -0,0 +1,198 @@
package class_2023_05_3_week;
import java.util.ArrayList;
// 现有一棵无向、无根的树,树中有 n 个节点,按从 0 到 n - 1 编号
// 给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges
// 其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。
// 每个节点都关联一个价格。给你一个整数数组 price ,其中 price[i] 是第 i 个节点的价格。
// 给定路径的 价格总和 是该路径上所有节点的价格之和。
// 另给你一个二维整数数组 trips ,其中 trips[i] = [starti, endi] 表示
// 从节点 starti 开始第 i 次旅行,并通过任何你喜欢的路径前往节点 endi 。
// 在执行第一次旅行之前,你可以选择一些 非相邻节点 并将价格减半。
// 返回执行所有旅行的最小价格总和。
// 测试链接 : https://leetcode.cn/problems/minimize-the-total-price-of-the-trips/
public class Code05_MinimizeTheTotalPriceOfTheTrips {
// trips : a,b c,k s,t ....
// x -> y x : y y : x
public static int minimumTotalPrice(int n, int[][] edges, int[] price, int[][] trips) {
ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
ArrayList<ArrayList<int[]>> queries = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
queries.add(new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
int m = trips.length;
int[] lcs = new int[m];
for (int i = 0; i < m; i++) {
// a a
if (trips[i][0] == trips[i][1]) {
lcs[i] = trips[i][0];
} else {
// a b
// a [b,i]
// b [a,i]
queries.get(trips[i][0]).add(new int[] { trips[i][1], i });
queries.get(trips[i][1]).add(new int[] { trips[i][0], i });
}
}
UnionFind uf = new UnionFind(n);
int[] fathers = new int[n];
tarjan(graph, 0, -1, uf, queries, fathers, lcs);
int[] cnts = new int[n];
for (int i = 0; i < m; i++) {
// a -> b lcs[i] -> father[lcs[i]]
cnts[trips[i][0]]++;
cnts[trips[i][1]]++;
cnts[lcs[i]]--;
if (fathers[lcs[i]] != -1) {
cnts[fathers[lcs[i]]]--;
}
}
dfs(graph, 0, -1, cnts);
int[] ans = dp(graph, 0, -1, cnts, price);
return Math.min(ans[0], ans[1]);
}
// 整张图 : graph
// 当前来到cur点父节点father
// uf : 并查集,一开始所有节点,独立的结合
// {a} {b} {c} .....
// uf.union(a,b) : a所在的集合和b所在的集合合并
// uf.setTag(a,x) : a所在的集合整个集合打上tag x
// uf.getTag(a) : a所在的集合tag是啥
// ====
// {a,b}, {a,k}, {f,t}, {x, x}(被洗掉)
// 0 1 2
// a(0) : {b, 0} 、{k, 1}
// b : {a, 0}
// k : {a, 1}
// f : {t, 2}
// t : {f, 2}
// int[] fathers : 所有节点的父亲节点,填好
// int[] lcs : 答案数组
public static void tarjan(
ArrayList<ArrayList<Integer>> graph,
int cur,
int father,
UnionFind uf,
ArrayList<ArrayList<int[]>> queries,
int[] fathers,
int[] lcs) {
fathers[cur] = father;
for (int next : graph.get(cur)) {
if (next != father) {
tarjan(graph, next, cur, uf, queries, fathers, lcs);
uf.union(cur, next);
uf.setTag(cur, cur);
}
}
// 处理cur自己的问题!
for (int[] query : queries.get(cur)) {
// query : query[0] cur和它有问题
// query[1]
int tag = uf.getTag(query[0]);
if (tag != -1) {
lcs[query[1]] = tag;
}
}
}
// a -> b a和b的最低公共祖先假设是xx的父亲假设是f
// 当初一定做了这件事:
// cnts[a]++
// cnts[b]++
// cnts[x]--
// cnts[f]--
public static void dfs(ArrayList<ArrayList<Integer>> graph, int cur, int father, int[] cnts) {
for (int next : graph.get(cur)) {
if (next != father) {
dfs(graph, next, cur, cnts);
cnts[cur] += cnts[next];
}
}
}
// 当前节点来到curcur的父节点是father
// cnts[cur],当前节点的次数
// price[cur],当前节点的价值
// 你可以把一些节点,价值减半!但是,减半的节点不能相邻!
// 返回整棵树的最小价值
// graph : 整棵树
public static int[] dp(ArrayList<ArrayList<Integer>> graph,
int cur, int father, int[] cnts, int[] price) {
// 当前节点价值不减半! 当前节点的价值 : price[cur] * cnts[cur]
int no = price[cur] * cnts[cur];
// 当前节点价值决定减半! 当前节点的价值 : (price[cur] / 2) * cnts[cur]
int yes = (price[cur] / 2) * cnts[cur];
for (int next : graph.get(cur)) {
if (next != father) {
int[] nextAns = dp(graph, next, cur, cnts, price);
no += Math.min(nextAns[0], nextAns[1]);
yes += nextAns[0];
}
}
return new int[] { no, yes };
}
public static class UnionFind {
public int[] father;
public int[] size;
public int[] tag;
public int[] help;
public UnionFind(int n) {
father = new int[n];
size = new int[n];
tag = new int[n];
help = new int[n];
for (int i = 0; i < n; i++) {
father[i] = i;
size[i] = 1;
tag[i] = -1;
}
}
public int find(int i) {
int size = 0;
while (i != father[i]) {
help[size++] = i;
i = father[i];
}
while (size > 0) {
father[help[--size]] = i;
}
return i;
}
public void union(int i, int j) {
int fi = find(i);
int fj = find(j);
if (fi != fj) {
if (size[fi] >= size[fj]) {
father[fj] = fi;
size[fi] += size[fj];
} else {
father[fi] = fj;
size[fj] += size[fi];
}
}
}
public void setTag(int i, int t) {
tag[find(i)] = t;
}
public int getTag(int i) {
return tag[find(i)];
}
}
}

@ -3620,4 +3620,66 @@ ans[i] 是将 1 放到位置 i 处的 最少 翻转操作次数,
第069节 2023年5月第3周流行算法题目解析
保证一定是n*n的正方形实现从里到外转圈打印的功能
如果n是奇数中心点唯一比如
a b c
d e f
g h i
e是中心点依次打印 : e f i h g d a b c
如果n是偶数中心点为最里层2*2的右下点
比如
a b c d e f
g h i j k l
m n o p q r
s t u v w x
y z 0 1 2 3
4 5 6 7 8 9
最里层是
o p
u v
v是中心点依次打印 : v u o p q w ....
来自学员问题
假设每一次获得随机数的时候这个数字大于100的概率是P
尝试N次其中大于100的次数在A次~B次之间的概率是多少?
0 < P < 1, P是double类型
1 <= A <= B <= N <= 100
在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始
并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0)
右下单元格是 (n - 1, n - 1)象棋骑士有8种可能的走法
每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格,类似马走日
每次骑士要移动时它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。
骑士继续移动,直到它走了 k 步或离开了棋盘
返回 骑士在棋盘停止移动后仍留在棋盘上的概率
测试链接 : https://leetcode.cn/problems/knight-probability-in-chessboard/
来自小红书
给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1
每一次移动,你可以选择 相邻 两个数字并将它们交换
请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数
测试链接 : https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/
现有一棵无向、无根的树,树中有 n 个节点,按从 0 到 n - 1 编号
给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges
其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。
每个节点都关联一个价格。给你一个整数数组 price ,其中 price[i] 是第 i 个节点的价格。
给定路径的 价格总和 是该路径上所有节点的价格之和。
另给你一个二维整数数组 trips ,其中 trips[i] = [starti, endi] 表示
从节点 starti 开始第 i 次旅行,并通过任何你喜欢的路径前往节点 endi 。
在执行第一次旅行之前,你可以选择一些 非相邻节点 并将价格减半。
返回执行所有旅行的最小价格总和。
测试链接 : https://leetcode.cn/problems/minimize-the-total-price-of-the-trips/

Loading…
Cancel
Save