From b9a992340ea5d109491bd64dc8b59d8a6577e9e9 Mon Sep 17 00:00:00 2001 From: algorithmzuo Date: Wed, 1 Mar 2023 23:25:41 +0800 Subject: [PATCH] modify code --- ...01_LexicographicalSmallestPermutation.java | 159 ++++++++++++++++ ...02_SplitToMakeIncreaseAndDecreaseWays.java | 169 ++++++++++++++++++ .../Code03_DiffColors.java | 114 ++++++++++++ .../Code04_ChairmanTree.java | 146 +++++++++++++++ ...养的大厂算法面试题(正在直播) | 76 ++++++++ 5 files changed, 664 insertions(+) create mode 100644 算法周更班/class_2023_03_1_week/Code01_LexicographicalSmallestPermutation.java create mode 100644 算法周更班/class_2023_03_1_week/Code02_SplitToMakeIncreaseAndDecreaseWays.java create mode 100644 算法周更班/class_2023_03_1_week/Code03_DiffColors.java create mode 100644 算法周更班/class_2023_03_1_week/Code04_ChairmanTree.java diff --git a/算法周更班/class_2023_03_1_week/Code01_LexicographicalSmallestPermutation.java b/算法周更班/class_2023_03_1_week/Code01_LexicographicalSmallestPermutation.java new file mode 100644 index 0000000..86892ca --- /dev/null +++ b/算法周更班/class_2023_03_1_week/Code01_LexicographicalSmallestPermutation.java @@ -0,0 +1,159 @@ +package class_2023_03_1_week; + +// 来自学员问题 +// 给定一个1~N的排列,每次将相邻两数相加,可以得到新的序列,长度是N-1 +// 再对新的序列,每次将相邻两数相加,可以得到新的序列,长度是N-2 +// 这样下去可以最终只剩一个数字 +// 比如 : +// 3 1 2 4 +// 4 3 6 +// 7 9 +// 16 +// 现在如果知道N,和最后的数字sum,反推最原始的序列是什么 +// 如果有多个答案,返回字典序最小的那个 +// 字典序看做所有数字拼起来的字符串字典序 +// 比如 +// 1, 10, 2... 拼起来是 1102... +// 1, 2, 3, 4... 拼起来是 1234... +// 认为 1, 10, 2...的字典序更小 +// 如果给定的n和sum,有答案,返回一个N长度的答案数组 +// 如果给定的n和sum,无答案,返回一个1长度的数组{ -1 } +// 输入 : N = 4, sum = 16 +// 输出 : 3 1 2 4 +// 输入 : N = 10, sum = 4116 +// 输出 : 1 3 5 7 10 9 8 6 4 2 +// 0 < n <= 10, sum随意 +public class Code01_LexicographicalSmallestPermutation { + + // 准备好杨辉三角形,作为每一项系数 + public static int[][] moduluses = { + {}, + { 1 }, + { 1, 1 }, + { 1, 2, 1 }, + { 1, 3, 3, 1 }, + { 1, 4, 6, 4, 1 }, + { 1, 5, 10, 10, 5, 1 }, + { 1, 6, 15, 20, 15, 6, 1 }, + { 1, 7, 21, 35, 35, 21, 7, 1 }, + { 1, 8, 28, 56, 70, 56, 28, 8, 1 }, + { 1, 9, 36, 84, 126, 126, 84, 36, 9, 1 } }; + + // sums[1] = 只有1这个数字,整出来的最大和不会超过sums[1] + // sum[i] = 只有1~i这些数字,整出来的最大和不会超过sums[i] + public static int[] sums = { 0, 1, 3, 9, 24, 61, 148, 350, 808, 1837, 4116 }; + + public static int[] lsp(int n, int sum) { + if (n < 1 || n > 10 || sum > sums[n]) { + return new int[] { -1 }; + } + int[][] dp = new int[1 << (n + 1)][sums[n] + 1]; + if (!process(((1 << (n + 1)) - 1) ^ 1, sum, 0, n, moduluses[n], dp)) { + return new int[] { -1 }; + } + int[] ans = new int[n]; + int index = 0; + // n = 7 + // 000..000 100000000 + // 000..000 011111111 + // 000..000 011111110 + int status = ((1 << (n + 1)) - 1) ^ 1; + int rest = sum; + while (status != 0) { + ans[index] = dp[status][rest]; + status ^= 1 << ans[index]; + rest -= ans[index] * moduluses[n][index]; + index++; + } + return ans; + } + + // 一开始给你1 ~ 7 + // 一开始的status : 000000..000011111110 + // 1、2、3、4、5、6 + // 当前还有2、4、5可用 + // 6 5 4 3 2 1 0 + // status : 0 1 1 0 1 0 0 + // status : 还能用的数字,都在status里! + // rest : 还剩多少和,需要去搞定! + // index : 当前可能把status里剩下的某个数字,拿出来用,需要 * 系数的! + // modulus[index]就是当前的系数! + // 剩下的过程,能不能搞定rest这个和! + // 能搞定返回true,不能搞定返回false + // 递归最终要的目的,不仅仅是获得返回值!dp表填好! + public static boolean process(int status, int rest, int index, int n, int[] modulus, int[][] dp) { + if (rest < 0) { + return false; + } + if (status == 0) { // 0000000000..000000000 + return rest == 0 ? true : false; + } + // dp[status][rest] == 0 (status,rest)之前没算过! + // dp[status][rest] == -1 (status,rest)之前算过!返回了false! + // dp[status][rest] != -1 (status,rest)之前算过!返回了true! + // dp[status][rest] : + // 从字典序小的数字开始试,一旦试出来,记录当前是哪个数字试出来的! + if (dp[status][rest] != 0) { + return dp[status][rest] != -1; + } + // n < 10 1 2 3 4 5 6... status里得有! + // n == 10 10 1 2 3 4 ... status里得有! + // ans : 哪个数字试出来的! + int ans = -1; + if (n == 10 && (status & (1 << 10)) != 0) { + // 真的可以先试10! + if (process(status ^ (1 << 10), rest - modulus[index] * 10, index + 1, n, modulus, dp)) { + ans = 10; + } + } + // ans == 10 + if (ans == -1) { + for (int i = 1; i <= n; i++) { + // i : 1 2 3 ... n status得有才可以! + if ((status & (1 << i)) != 0) { + if (process(status ^ (1 << i), rest - modulus[index] * i, index + 1, n, modulus, dp)) { + ans = i; + break; + } + } + } + } + dp[status][rest] = ans; + return ans != -1; + } + + public static void main(String[] args) { + int N1 = 4; + int sum1 = 16; + int[] ans1 = lsp(N1, sum1); + for (int num : ans1) { + System.out.print(num + " "); + } + System.out.println(); + + int N2 = 10; + int sum2 = 4116; + int[] ans2 = lsp(N2, sum2); + for (int num : ans2) { + System.out.print(num + " "); + } + System.out.println(); + + int N3 = 10; + int sum3 = 3688; + int[] ans3 = lsp(N3, sum3); + for (int num : ans3) { + System.out.print(num + " "); + } + System.out.println(); + + int N4 = 10; + int sum4 = 4013; + int[] ans4 = lsp(N4, sum4); + for (int num : ans4) { + System.out.print(num + " "); + } + System.out.println(); + } + +} \ No newline at end of file diff --git a/算法周更班/class_2023_03_1_week/Code02_SplitToMakeIncreaseAndDecreaseWays.java b/算法周更班/class_2023_03_1_week/Code02_SplitToMakeIncreaseAndDecreaseWays.java new file mode 100644 index 0000000..2b79a35 --- /dev/null +++ b/算法周更班/class_2023_03_1_week/Code02_SplitToMakeIncreaseAndDecreaseWays.java @@ -0,0 +1,169 @@ +package class_2023_03_1_week; + +// 来自学员问题,国外算法面经帖子上的题 +// 给定一个数组A, 把它分成两个数组B和C +// 对于数组A每个i位置的数来说, +// A[i] = B[i] + C[i] +// 也就是一个数字分成两份,然后各自进入B和C +// 要求B[i], C[i] >= 1 +// 最终B数组要求从左到右不能降序 +// 最终C数组要求从左到右不能升序 +// 比如 +// A = { 5, 4, 5 } +// 可以分成 +// B = { 2, 2, 3 } +// C = { 3, 2, 2 } +// 这是一种有效的划分 +// 返回有多少种有效的划分方式 +// 1 <= A的长度 <= 10^7 +// 1 <= A[i] <= 10^7 +// 最终结果可能很大,请返回对1000000007取余的结果 +public class Code02_SplitToMakeIncreaseAndDecreaseWays { + + // 暴力方法 + // 为了验证 + // 假设答案永远不会溢出 + // 小数据量可用 + public static int ways1(int[] arr) { + int ans = 0; + for (int increase = 1, decrease = arr[0] - 1; increase < arr[0]; increase++, decrease--) { + ans += process1(arr, 1, increase, decrease); + } + return ans; + } + + public static int process1(int[] arr, int i, int preIncrease, int preDecrease) { + if (i == arr.length) { + return 1; + } + int ans = 0; + for (int increase = 1, decrease = arr[i] - 1; increase < arr[i]; increase++, decrease--) { + if (preIncrease <= increase && preDecrease >= decrease) { + ans += process1(arr, i + 1, increase, decrease); + } + } + return ans; + } + + // 最优解法 + // 转化成杨辉三角形不容易想 + // 大数据量需要取余 + // 需要用到乘法逆元 + // 时间复杂度O(N + M) + // N是数组长度,M大概可以认为是数值范围 + public static int ways2(int[] arr) { + int n = arr.length; + // [ 6 + // 5 + int k = arr[0] - 1; + for (int i = 1; i < n && k > 0; i++) { + if (arr[i - 1] > arr[i]) { + k -= arr[i - 1] - arr[i]; + } + } + if (k <= 0) { + return 0; + } + return pascalTriangleModulus(k - 1 + n, n); + } + + // 组合公式,n个元素取r个的方法数 + // n! / (r! * (n-r)!) + // 用乘法逆元并且有mod时候的做法 + public static int pascalTriangleModulus(int n, int r) { + int mod = 1000000007; + long up = 1; + long inv1 = 1; + long inv2 = 1; + for (int i = 1; i <= n; i++) { + up = (up * i) % mod; + if (i == r) { + inv1 = power(up, mod - 2, mod); + } + if (i == n - r) { + inv2 = power(up, mod - 2, mod); + } + } + return (int) ((((up * inv1) % mod) * inv2) % mod); + } + + // x的n次方,% mod之后,返回是多少 + // 费马小定理 + public static long power(long x, int n, int mod) { + 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[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v) + 1; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + // 展示一下pascalTriangleModulus的用法 + System.out.println("打印部分杨辉三角形"); + for (int n = 0; n <= 10; n++) { + for (int r = 0; r <= n; r++) { + System.out.print(pascalTriangleModulus(n, r) + " "); + } + System.out.println(); + } + int N = 10; + int V = 20; + int testTimes = 20000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int ans1 = ways1(arr); + int ans2 = ways2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int n = 10000000; + int v = 10000000; + long start, end; + int[] arr = new int[n]; + System.out.println("随机生成的数据测试 : "); + System.out.println("数组长度 : " + n); + System.out.println("数值范围 : [" + 1 + "," + v + "]"); + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + + start = System.currentTimeMillis(); + ways2(arr); + end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + + System.out.println("运行最慢的数据测试 : "); + System.out.println("数组长度 : " + n); + System.out.println("数值都是 : " + v); + System.out.println("这种情况其实是复杂度最高的情况"); + for (int i = 0; i < n; i++) { + arr[i] = v; + } + start = System.currentTimeMillis(); + ways2(arr); + end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + } + +} diff --git a/算法周更班/class_2023_03_1_week/Code03_DiffColors.java b/算法周更班/class_2023_03_1_week/Code03_DiffColors.java new file mode 100644 index 0000000..3da6e75 --- /dev/null +++ b/算法周更班/class_2023_03_1_week/Code03_DiffColors.java @@ -0,0 +1,114 @@ +package class_2023_03_1_week; + +// HH有一串由各种漂亮的贝壳组成的项链 +// HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳, +// 思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。 +// 有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳? +// 这个问题很难回答... 因为项链实在是太长了 +// 于是,他只好求助睿智的你,来解决这个问题 +// 测试链接 : https://www.luogu.com.cn/problem/P1972 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交以下所有代码,把主类名改成Main +// 洛谷对java太不友好了,大量时间不是消耗在算法本身上,而是耗在了IO上 +// 多提交几次能全通过 +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_DiffColors { + + public static int MAXN = 1000010; + + public static int[] arr = new int[MAXN]; + + public static int[][] query = new int[MAXN][3]; + + public static int[] ans = new int[MAXN]; + + public static int[] map = new int[MAXN]; + + public static int[] tree = new int[MAXN]; + + public static int n, m; + + public static void buildTree() { + Arrays.fill(tree, 1, n + 1, 0); + } + + public static int sum(int l, int r) { + return sum(r) - sum(l - 1); + } + + public static int sum(int index) { + int ret = 0; + while (index > 0) { + ret += tree[index]; + index -= index & -index; + } + return ret; + } + + public static void add(int i, int d) { + while (i <= n) { + tree[i] += d; + i += i & -i; + } + } + + 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) { + n = (int) in.nval; + for (int i = 1; i <= n; i++) { + in.nextToken(); + arr[i] = (int) in.nval; + } + // arr[i] : i位置的颜色 + in.nextToken(); + m = (int) in.nval; + for (int i = 1; i <= m; i++) { + in.nextToken(); + // left + query[i][0] = (int) in.nval; + in.nextToken(); + // right + query[i][1] = (int) in.nval; + // 第i个问题 + query[i][2] = i; + } + // map[5] = 8 + // 5这个颜色,上次出现在8位置 + // map[5] = 0 + // 5这个颜色,之前没出现过 + Arrays.fill(map, 0); + buildTree(); + Arrays.sort(query, 1, m + 1, (a, b) -> a[1] - b[1]); + for (int s = 1, j = 1; j <= m; j++) { + int l = query[j][0]; + int r = query[j][1]; + int index = query[j][2]; + for (; s <= r; s++) { + int color = arr[s]; + if (map[color] != 0) { + add(map[color], -1); + } + add(s, 1); + map[color] = s; + } + ans[index] = sum(l, r); + } + for (int i = 1; i <= m; i++) { + out.println(ans[i]); + } + out.flush(); + } + } + +} diff --git a/算法周更班/class_2023_03_1_week/Code04_ChairmanTree.java b/算法周更班/class_2023_03_1_week/Code04_ChairmanTree.java new file mode 100644 index 0000000..c05a938 --- /dev/null +++ b/算法周更班/class_2023_03_1_week/Code04_ChairmanTree.java @@ -0,0 +1,146 @@ +package class_2023_03_1_week; + +// 主席树详解 +// 测试链接 : https://www.luogu.com.cn/problem/P3834 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交以下所有代码,把主类名改成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 Code04_ChairmanTree { + + public static int MAXN = 200010; + + // 输入数据相关 + // 数组 : {100, 35, 4, 800} + // 排序 : {4 , 35, 100, 800} + // 1 2 3 4 + // 1 2 3 4 + // 1 ~ 800 + // 1 ~ 4 + public static int[] origin = new int[MAXN]; + public static int[] sorted = new int[MAXN]; + + + // 每个版本的线段树头部 + // 线段树不是基于下标的,基于值,基于值转化之后的rank + // root[i] : i位置的数,加入之后,线段树的头部节点编号 + public static int[] root = new int[MAXN]; + + // 建树相关,当你的数组个数是n,一般来讲开32 * n + public static int[] left = new int[MAXN << 5]; + public static int[] right = new int[MAXN << 5]; + public static int[] sum = new int[MAXN << 5]; + + public static int cnt, n, m; + + 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) { + cnt = 0; + n = (int) in.nval; + in.nextToken(); + m = (int) in.nval; + for (int i = 1; i <= n; i++) { + in.nextToken(); + origin[i] = (int) in.nval; + sorted[i] = origin[i]; + } + Arrays.sort(sorted, 1, n + 1); + root[0] = build(1, n); + for (int i = 1; i <= n; i++) { + int x = rank(origin[i]); + root[i] = insert(root[i - 1], 1, n, x); + } + for (int i = 1; i <= m; i++) { + in.nextToken(); + int L = (int) in.nval; + in.nextToken(); + int R = (int) in.nval; + in.nextToken(); + int K = (int) in.nval; + // L..R K + // R - (L-1) + int ansIndex = query(root[L - 1], root[R], K, 1, n); + out.println(sorted[ansIndex]); + out.flush(); + } + } + } + + // 0版本来说,值的范围l~r, rt, 0个 + public static int build(int l, int r) { + int rt = ++cnt; + sum[rt] = 0; + if (l < r) { + int mid = (l + r) / 2; + left[rt] = build(l, mid); + right[rt] = build(mid + 1, r); + } + return rt; + } + + // 当前范围是 l ~ r + // pre:这个范围在上一个版本的编号 + // 当前版本,在l~r上加数,加x + // 当前版本,l~r范围,编号返回 + public static int insert(int pre, int l, int r, int x) { + int rt = ++cnt; + left[rt] = left[pre]; + right[rt] = right[pre]; + sum[rt] = sum[pre] + 1; + if (l < r) { + int mid = (l + r) / 2; + if (x <= mid) { + left[rt] = insert(left[pre], l, mid, x); + } else { + right[rt] = insert(right[pre], mid + 1, r, x); + } + } + return rt; + } + + // 请查询 下标!在 : 32 ~ 54 范围上,排名第9的数字! + // 54版本 - 31版本的线段树,请加工出来!排名第9的数字! + // u : 31版本 + // v : 54版本 + // l ~ r : 值的范围,在线段树上! + public static int query(int u, int v, int k, int l, int r) { + if (l == r) { + return l; + } + int leftSize = sum[left[v]] - sum[left[u]]; + int mid = (l + r) / 2; + if (leftSize >= k) { + return query(left[u], left[v], k, l, mid); + } else { + return query(right[u], right[v], k - leftSize, mid + 1, r); + } + } + + public static int rank(int v) { + int l = 1; + int r = n; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (sorted[m] <= v) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 43a7556..d803708 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -3084,6 +3084,82 @@ m、n都是整数,n > 1并且m > 1 +第060节 2023年3月第1周流行算法题目解析 + +来自学员问题 +给定一个1~N的排列,每次将相邻两数相加,可以得到新的序列,长度是N-1 +再对新的序列,每次将相邻两数相加,可以得到新的序列,长度是N-2 +这样下去可以最终只剩一个数字 +比如 : +3 1 2 4 + 4 3 6 + 7 9 + 16 +现在如果知道N,和最后的数字sum,反推最原始的序列是什么 +如果有多个答案,返回字典序最小的那个 +字典序看做所有数字拼起来的字符串字典序 +比如 +1, 10, 2... 拼起来是 1102... +1, 2, 3, 4... 拼起来是 1234... +认为 1, 10, 2...的字典序更小 +如果给定的n和sum,有答案,返回一个N长度的答案数组 +如果给定的n和sum,无答案,返回一个1长度的数组{ -1 } +输入 : N = 4, sum = 16 +输出 : 3 1 2 4 +输入 : N = 10, sum = 4116 +输出 : 1 3 5 7 10 9 8 6 4 2 +0 < n <= 10, sum随意 + +来自学员问题,国外算法面经帖子上的题 +给定一个数组A, 把它分成两个数组B和C +对于数组A每个i位置的数来说, +A[i] = B[i] + C[i] +也就是一个数字分成两份,然后各自进入B和C +要求B[i], C[i] >= 1 +最终B数组要求从左到右不能降序 +最终C数组要求从左到右不能升序 +比如 +A = { 5, 4, 5 } +可以分成 +B = { 2, 2, 3 } +C = { 3, 2, 2 } +这是一种有效的划分 +返回有多少种有效的划分方式 +1 <= A的长度 <= 10^7 +1 <= A[i] <= 10^7 +最终结果可能很大,请返回对1000000007取余的结果 + +HH有一串由各种漂亮的贝壳组成的项链 +HH相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳, +思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。 +有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳? +这个问题很难回答... 因为项链实在是太长了 +于是,他只好求助睿智的你,来解决这个问题 +测试链接 : https://www.luogu.com.cn/problem/P1972 +请同学们务必参考如下代码中关于输入、输出的处理 +这是输入输出处理效率很高的写法 +提交以下所有代码,把主类名改成Main +洛谷对java太不友好了,大量时间不是消耗在算法本身上,而是耗在了IO上 +多提交几次能全通过 + +主席树详解 +测试链接 : https://www.luogu.com.cn/problem/P3834 +请同学们务必参考如下代码中关于输入、输出的处理 +这是输入输出处理效率很高的写法 +提交以下所有代码,把主类名改成Main,可以通过 + + + + + + + + + + + + +