modify code

master
algorithmzuo 3 years ago
parent 3793b1aa1c
commit b9a992340e

@ -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();
}
}

@ -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("性能测试结束");
}
}

@ -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();
}
}
}

@ -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;
}
}

@ -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可以通过

Loading…
Cancel
Save