modify code

master
algorithmzuo 1 year ago
parent 84b9277c90
commit db93f1d05d

@ -0,0 +1,56 @@
package class_2023_02_4_week;
import java.util.PriorityQueue;
// 一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试
// 给你一个二维数组 classes ,其中 classes[i] = [passi, totali]
// 表示你提前知道了第 i 个班级总共有 totali 个学生,其中只有 passi 个学生可以通过考试
// 给你一个整数 extraStudents ,表示额外有 extraStudents 个聪明的学生
// 他们 一定 能通过任何班级的期末考
// 你需要给这 extraStudents 个学生每人都安排一个班级
// 使得 所有 班级的 平均 通过率 最大 。
// 一个班级的 通过率 等于这个班级通过考试的学生人数除以这个班级的总人数
// 平均通过率 是所有班级的通过率之和除以班级数目。
// 请你返回在安排这 extraStudents 个学生去对应班级后的 最大 平均通过率
// 与标准答案误差范围在 10^-5 以内的结果都会视为正确结果。
// 测试链接 : https://leetcode.cn/problems/maximum-average-pass-ratio/
public class Code01_MaximumAveragePassRatio {
public static double maxAverageRatio(int[][] classes, int extraStudents) {
// 堆 : 谁获得一个天才,得到的通过率增益最大
// 谁先弹出
PriorityQueue<Party> heap = new PriorityQueue<>((a, b) -> a.benefit() - b.benefit() < 0 ? 1 : -1);
for (int[] p : classes) {
heap.add(new Party(p[0], p[1]));
}
Party cur;
// 一个一个天才分配
for (int i = 0; i < extraStudents; i++) {
cur = heap.poll();
cur.pass++;
cur.total++;
heap.add(cur);
}
double all = 0;
while (!heap.isEmpty()) {
cur = heap.poll();
all += cur.pass / cur.total;
}
return all / classes.length;
}
public static class Party {
public double pass;
public double total;
public Party(int p, int t) {
pass = p;
total = t;
}
public double benefit() {
return (pass + 1) / (total + 1) - pass / total;
}
}
}

@ -0,0 +1,65 @@
package class_2023_02_4_week;
// 来自学员问题
// 给你一根长度为 n 的绳子
// 请把绳子剪成整数长度的 m 段
// m、n都是整数n > 1并且m > 1
// 每段绳子的长度记为 k[0],k[1]...k[m - 1]
// 请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少
// 例如当绳子的长度是8时我们把它剪成长度分别为2、3、3的三段此时得到的最大乘积是18
// 答案需要取模1000000007
// 测试链接 : https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/
public class Code02_SplitNumberTimesMax {
public static int mod = 1000000007;
// x的n次方% mod之后是多少
// 快速幂的方式,体系学习班,斐波那契数列章节
public static long power(long x, int n) {
long ans = 1;
while (n > 0) {
if ((n & 1) == 1) {
ans = (ans * x) % mod;
}
x = (x * x) % mod;
n >>= 1;
}
return ans;
}
// 纯观察,没有为什么
public static int cuttingRope(int n) {
if (n == 2) {
return 1;
}
if (n == 3) {
return 2;
}
// n >= 4
// n = 13
// n % 3 == 1
// 4 -> 2 * 2 (n-4) / 3 -> 3的(n-4)/3 次方
// n % 3 == 2
// 2 -> 2 (n-2)/3 -> 3的(n-2)/3次方
// n:
//
// last :
// n = 9
// 3 * 3 * 3 * 1
// n = 10
// 3 * 3 * (2 * 2)
// n = 11
// 3 * 3 * 3 * (2)
// n = 9
// rest = 9 -> 3 ?
// n = 10
// rest = 10 - 4 -> 6 ?
// n == 11
// rest = 11 - 2 = 9 ?
int rest = n % 3 == 0 ? n : (n % 3 == 1 ? (n - 4) : (n - 2));
int last = n % 3 == 0 ? 1 : (n % 3 == 1 ? 4 : 2);
// (3的(rest/3)次方 * last) % mod
return (int) ((power(3, rest / 3) * last) % mod);
}
}

@ -0,0 +1,93 @@
package class_2023_02_4_week;
import java.util.HashMap;
import java.util.HashSet;
// 在大小为 n x n 的网格 grid 上,每个单元格都有一盏灯,最初灯都处于 关闭 状态
// 给你一个由灯的位置组成的二维数组 lamps
// 其中 lamps[i] = [rowi, coli] 表示 打开 位于 grid[rowi][coli] 的灯
// 即便同一盏灯可能在 lamps 中多次列出,不会影响这盏灯处于 打开 状态
// 当一盏灯处于打开状态,它将会照亮 自身所在单元格
// 以及同一 行 、同一 列 和两条 对角线 上的 所有其他单元格
// 另给你一个二维数组 queries ,其中 queries[j] = [rowj, colj]
// 对于第 j 个查询,如果单元格 [rowj, colj] 是被照亮的
// 则查询结果为 1 ,否则为 0 。在第 j 次查询之后 [按照查询的顺序]
// 关闭 位于单元格 grid[rowj][colj] 上
// 及相邻 8 个方向上(与单元格 grid[rowi][coli] 共享角或边)的任何灯
// 返回一个整数数组 ans 作为答案, ans[j] 应等于第 j 次查询 queries[j] 的结果
// 1 表示照亮0 表示未照亮
// 测试链接 : https://leetcode.cn/problems/grid-illumination/
public class Code03_GridIllumination {
public static int[][] move = {
{ 0, 0 },
{ 0, -1 },
{ 0, 1 },
{ -1, 0 },
{ -1, -1 },
{ -1, 1 },
{ 1, 0 },
{ 1, -1 },
{ 1, 1 } };
// n -> 大区域是 n * n
// lamps
// queries
public static int[] gridIllumination(int n, int[][] lamps, int[][] queries) {
long limit = n;
HashMap<Integer, Integer> row = new HashMap<>();
HashMap<Integer, Integer> col = new HashMap<>();
HashMap<Integer, Integer> leftUpDiag = new HashMap<>();
HashMap<Integer, Integer> rightUpDiag = new HashMap<>();
// (x,y) -> x*列的数量 + y -> 得到的数字代表一个点
HashSet<Long> points = new HashSet<>();
for (int[] p : lamps) {
// 所有的灯,注册!
// 如果之前加过add方法返回fasle
// 如果之前没加过add方法返回true
if (points.add(limit * p[0] + p[1])) {
row.put(p[0], row.getOrDefault(p[0], 0) + 1);
col.put(p[1], col.getOrDefault(p[1], 0) + 1);
leftUpDiag.put(p[0] - p[1], leftUpDiag.getOrDefault(p[0] - p[1], 0) + 1);
rightUpDiag.put(p[0] + p[1], rightUpDiag.getOrDefault(p[0] + p[1], 0) + 1);
}
}
int[] ans = new int[queries.length];
int ansi = 0;
for (int[] q : queries) {
// q[0], q[1]
ans[ansi++] = (row.containsKey(q[0])
|| col.containsKey(q[1])
|| leftUpDiag.containsKey(q[0] - q[1])
|| rightUpDiag.containsKey(q[0] + q[1])) ? 1 : 0;
for (int[] m : move) {
int r = q[0] + m[0];
int c = q[1] + m[1];
// (r,c)位置,有灯就关,没灯算了!
int lu = r - c;
int ru = r + c;
if (r < 0 || r >= n || c < 0 || c >= n) {
continue;
}
// r,c -> 列数 * r + c
if (points.contains(limit * r + c)) {
points.remove(limit * r + c);
minusOrRemove(row, r);
minusOrRemove(col, c);
minusOrRemove(leftUpDiag, lu);
minusOrRemove(rightUpDiag, ru);
}
}
}
return ans;
}
public static void minusOrRemove(HashMap<Integer, Integer> map, int key) {
if (map.get(key) == 1) {
map.remove(key);
} else {
map.put(key, map.get(key) - 1);
}
}
}

@ -0,0 +1,82 @@
package class_2023_02_4_week;
import java.util.ArrayList;
import java.util.Arrays;
// 你想要用小写字母组成一个目标字符串 target。
// 开始的时候,序列由 target.length 个 '?' 记号组成
// 而你有一个小写字母印章 stamp。
// 在每个回合,你可以将印章放在序列上,并将序列中的每个字母替换为印章上的相应字母
// 你最多可以进行 10 * target.length 个回合
// 举个例子,如果初始序列为 "?????",而你的印章 stamp 是 "abc"
// 那么在第一回合,你可以得到 "abc??"、"?abc?"、"??abc"
//(请注意,印章必须完全包含在序列的边界内才能盖下去。)
// 如果可以印出序列,那么返回一个数组,该数组由每个回合中被印下的最左边字母的索引组成
// 如果不能印出序列,就返回一个空数组。
// 例如,如果序列是 "ababc",印章是 "abc"
// 那么我们就可以返回与操作 "?????" -> "abc??" -> "ababc" 相对应的答案 [0, 2]
// 另外,如果可以印出序列,那么需要保证可以在 10 * target.length 个回合内完成
// 任何超过此数字的答案将不被接受
// 测试链接 : https://leetcode.cn/problems/stamping-the-sequence/
public class Code04_StampingTheSequence {
public static int[] movesToStamp(String stamp, String target) {
char[] s = stamp.toCharArray();
char[] t = target.toCharArray();
int m = s.length;
int n = t.length;
int[] inDegrees = new int[n - m + 1];
// 所有在target里开头的字符串认为和stamp每个位置都不一样
Arrays.fill(inDegrees, m);
ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
// 入度为0的开头进入到queue里
int[] queue = new int[n - m + 1];
int l = 0;
int r = 0;
for (int i = 0; i <= n - m; i++) {
// i....撸m个字符
for (int j = 0; j < m; j++) {
if (t[i + j] == s[j]) {
if (--inDegrees[i] == 0) {
queue[r++] = i;
}
} else {
graph.get(i + j).add(i);
}
}
}
// visited[17] == true
boolean[] visited = new boolean[n];
int[] path = new int[n - m + 1];
int size = 0;
while (l < r) {
// cur位置开头的字符串弹出了入度为0恭喜啊
int cur = queue[l++];
path[size++] = cur;
for (int i = 0; i < m; i++) {
if (!visited[cur + i]) {
visited[cur + i] = true;
for (int next : graph.get(cur + i)) {
if (--inDegrees[next] == 0) {
queue[r++] = next;
}
}
}
}
}
if (size != n - m + 1) {
return new int[0];
}
// path里
for (int i = 0, j = size - 1; i < j; i++, j--) {
int tmp = path[i];
path[i] = path[j];
path[j] = tmp;
}
return path;
}
}

@ -0,0 +1,201 @@
package class_2023_02_4_week;
// 给你一个 rows * cols 大小的矩形披萨和一个整数 k
// 矩形包含两种字符: 'A' (表示苹果)和 '.' (表示空白格子)
// 你需要切披萨 k-1 次,得到 k 块披萨并送给别人
// 切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置
// 将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人
// 如果水平地切,那么需要把上面的部分送给一个人
// 在切完最后一刀后,需要把剩下来的一块送给最后一个人
// 请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数
// 由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果
// 测试链接 : https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/
public class Code05_NumberOfWaysOfCuttingPizza {
// 暴力方法
public static int ways1(String[] pizza, int k) {
int n = pizza.length;
int m = pizza[0].length();
int[][] sum = new int[n + 1][m + 1];
setAppleMatrix(pizza, sum, n, m);
return process(sum, n, m, 1, 1, k);
}
// 暴力方法
// sum : 帮助你查询(a,b)做左上角,(c,d)做右下角,有几个苹果,快速查询! O(1)
// 披萨饼的整体规模 : n * m
// 剩余没切的披萨饼, 左上角点(row, col),右下角点(n, m)
// 剩余的区域一定要切出rest份!
// 返回有多少种方法!
public static int process(int[][] sum, int n, int m, int row, int col, int rest) {
if (apple(sum, row, col, n, m) == 0) {
return 0;
}
// 剩余区域上,有苹果
if (rest == 1) {
return 1;
}
int ways = 0;
for (int i = col; i < m; i++) {
if (apple(sum, row, col, n, i) > 0) {
ways += process(sum, n, m, row, i + 1, rest - 1);
ways %= mod;
}
}
for (int i = row; i < n; i++) {
if (apple(sum, row, col, i, m) > 0) {
ways += process(sum, n, m, i + 1, col, rest - 1);
ways %= mod;
}
}
return ways;
}
// 暴力方法改动态规划 + 小优化
// 时间复杂度O(N * M * K * (N + M))
public static int ways2(String[] pizza, int k) {
int n = pizza.length;
int m = pizza[0].length();
int[][] sum = new int[n + 1][m + 1];
setAppleMatrix(pizza, sum, n, m);
int[][][] dp = new int[k + 1][n + 1][m + 1];
for (int r = 1; r <= n; r++) {
for (int c = 1; c <= m; c++) {
if (apple(sum, r, c, n, m) > 0) {
dp[1][r][c] = 1;
}
}
}
for (int level = 2; level <= k; level++) {
for (int row = n; row >= 1; row--) {
for (int col = m; col >= 1; col--) {
int ways = 0;
for (int c = col; c < m; c++) {
if (apple(sum, row, col, n, c) > 0) {
for (int s = c + 1; s <= m; s++) {
ways += dp[level - 1][row][s];
ways %= mod;
}
break;
}
}
for (int r = row; r < n; r++) {
if (apple(sum, row, col, r, m) > 0) {
for (int s = r + 1; s <= n; s++) {
ways += dp[level - 1][s][col];
ways %= mod;
}
break;
}
}
dp[level][row][col] = ways;
}
}
}
return dp[k][1][1];
}
// 动态规划 + 观察位置依赖的大优化
// 基本上LeetCode上面题解都没有做到这个复杂度的
// 时间复杂度O(N * M * K)
public static int ways3(String[] pizza, int k) {
int n = pizza.length;
int m = pizza[0].length();
int[][] sum = new int[n + 1][m + 1];
setAppleMatrix(pizza, sum, n, m);
int[][] nearr = new int[n + 1][m + 1];
int[][] nearc = new int[n + 1][m + 1];
setNear(sum, nearr, nearc, n, m);
int[][] dp = new int[n + 1][m + 1];
int[][] rs = new int[n + 1][m + 1];
int[][] cs = new int[n + 1][m + 1];
for (int r = 1; r <= n; r++) {
for (int c = 1; c <= m; c++) {
if (apple(sum, r, c, n, m) > 0) {
dp[r][c] = 1;
}
}
}
setRowColSums(dp, rs, cs, n, m);
for (int level = 2; level <= k; level++) {
for (int r = 1; r <= n; r++) {
for (int c = 1; c <= m; c++) {
// (r,c) 剩余区域,离的最近的苹果,在哪一列
// (r,c) 剩余区域,离的最近的苹果,在哪一行
dp[r][c] = nearr[r][c] < n ? (rs[nearr[r][c] + 1][c]) : 0;
dp[r][c] = (dp[r][c] + (nearc[r][c] < m ? cs[r][nearc[r][c] + 1] : 0)) % mod;
}
}
// 并列关系!
// 又来两个for循环
setRowColSums(dp, rs, cs, n, m);
}
return dp[1][1];
}
public static int mod = 1000000007;
public static void setAppleMatrix(String[] pizza, int[][] sum, int n, int m) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
sum[i + 1][j + 1] = pizza[i].charAt(j) == 'A' ? 1 : 0;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
}
}
}
public static int apple(int[][] sum, int a, int b, int c, int d) {
return sum[c][d] - sum[c][b - 1] - sum[a - 1][d] + sum[a - 1][b - 1];
}
public static void setNear(int[][] sum, int[][] nearr, int[][] nearc, int n, int m) {
for (int r = 1; r <= n; r++) {
int right = m + 1;
int number = 0;
for (int c = m; c >= 1; c--) {
int curApple = apple(sum, r, c, n, m);
if (curApple > number) {
number = curApple;
right = c;
}
nearc[r][c] = right;
}
}
for (int c = 1; c <= m; c++) {
int down = n + 1;
int number = 0;
for (int r = n; r >= 1; r--) {
int curApple = apple(sum, r, c, n, m);
if (curApple > number) {
number = curApple;
down = r;
}
nearr[r][c] = down;
}
}
}
public static void setRowColSums(int[][] dp, int[][] rs, int[][] cs, int n, int m) {
rs[n][m] = dp[n][m];
cs[n][m] = dp[n][m];
for (int r = n - 1; r >= 1; r--) {
cs[r][m] = dp[r][m];
rs[r][m] = (dp[r][m] + rs[r + 1][m]) % mod;
}
for (int c = m - 1; c >= 1; c--) {
rs[n][c] = dp[n][c];
cs[n][c] = (dp[n][c] + cs[n][c + 1]) % mod;
}
for (int r = n - 1; r >= 1; r--) {
for (int c = m - 1; c >= 1; c--) {
rs[r][c] = (dp[r][c] + rs[r + 1][c]) % mod;
cs[r][c] = (dp[r][c] + cs[r][c + 1]) % mod;
}
}
}
}

@ -3015,19 +3015,72 @@ X价值如果不是所有剩余宝石价值中的最小值你会将该宝石
第059节 2023年2月第4周流行算法题目解析
一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试
给你一个二维数组 classes ,其中 classes[i] = [passi, totali]
表示你提前知道了第 i 个班级总共有 totali 个学生,其中只有 passi 个学生可以通过考试
给你一个整数 extraStudents ,表示额外有 extraStudents 个聪明的学生
他们 一定 能通过任何班级的期末考
你需要给这 extraStudents 个学生每人都安排一个班级
使得 所有 班级的 平均 通过率 最大 。
一个班级的 通过率 等于这个班级通过考试的学生人数除以这个班级的总人数
平均通过率 是所有班级的通过率之和除以班级数目。
请你返回在安排这 extraStudents 个学生去对应班级后的 最大 平均通过率
与标准答案误差范围在 10^-5 以内的结果都会视为正确结果。
测试链接 : https://leetcode.cn/problems/maximum-average-pass-ratio/
来自学员问题
给你一根长度为 n 的绳子
请把绳子剪成整数长度的 m 段
m、n都是整数n > 1并且m > 1
每段绳子的长度记为 k[0],k[1]...k[m - 1]
请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少
例如当绳子的长度是8时我们把它剪成长度分别为2、3、3的三段此时得到的最大乘积是18
答案需要取模1000000007
测试链接 : https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/
在大小为 n x n 的网格 grid 上,每个单元格都有一盏灯,最初灯都处于 关闭 状态
给你一个由灯的位置组成的二维数组 lamps
其中 lamps[i] = [rowi, coli] 表示 打开 位于 grid[rowi][coli] 的灯
即便同一盏灯可能在 lamps 中多次列出,不会影响这盏灯处于 打开 状态
当一盏灯处于打开状态,它将会照亮 自身所在单元格
以及同一 行 、同一 列 和两条 对角线 上的 所有其他单元格
另给你一个二维数组 queries ,其中 queries[j] = [rowj, colj]
对于第 j 个查询,如果单元格 [rowj, colj] 是被照亮的
则查询结果为 1 ,否则为 0 。在第 j 次查询之后 [按照查询的顺序]
关闭 位于单元格 grid[rowj][colj] 上
及相邻 8 个方向上(与单元格 grid[rowi][coli] 共享角或边)的任何灯
返回一个整数数组 ans 作为答案, ans[j] 应等于第 j 次查询 queries[j] 的结果
1 表示照亮0 表示未照亮
测试链接 : https://leetcode.cn/problems/grid-illumination/
你想要用小写字母组成一个目标字符串 target。
开始的时候,序列由 target.length 个 '?' 记号组成
而你有一个小写字母印章 stamp。
在每个回合,你可以将印章放在序列上,并将序列中的每个字母替换为印章上的相应字母
你最多可以进行 10 * target.length 个回合
举个例子,如果初始序列为 "?????",而你的印章 stamp 是 "abc"
那么在第一回合,你可以得到 "abc??"、"?abc?"、"??abc"
(请注意,印章必须完全包含在序列的边界内才能盖下去。)
如果可以印出序列,那么返回一个数组,该数组由每个回合中被印下的最左边字母的索引组成
如果不能印出序列,就返回一个空数组。
例如,如果序列是 "ababc",印章是 "abc"
那么我们就可以返回与操作 "?????" -> "abc??" -> "ababc" 相对应的答案 [0, 2]
另外,如果可以印出序列,那么需要保证可以在 10 * target.length 个回合内完成
任何超过此数字的答案将不被接受
测试链接 : https://leetcode.cn/problems/stamping-the-sequence/
给你一个 rows * cols 大小的矩形披萨和一个整数 k
矩形包含两种字符: 'A' (表示苹果)和 '.' (表示空白格子)
你需要切披萨 k-1 次,得到 k 块披萨并送给别人
切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置
将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人
如果水平地切,那么需要把上面的部分送给一个人
在切完最后一刀后,需要把剩下来的一块送给最后一个人
请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数
由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果
测试链接 : https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/

Loading…
Cancel
Save