modify code

master
algorithmzuo 2 years ago
parent 1a4b3862a2
commit 6ce50f1ca1

@ -0,0 +1,117 @@
package class_2023_04_4_week;
// 来自学员问题,蓝桥杯练习题
// 给定一个长度为n的数组arr
// 现在你有一次机会, 将其中连续的K个数全修改成任意一个值
// 请你计算如何修改可以使修改后的数 列的最长不下降子序列最长
// 请输出这个最长的长度。
// 最长不下降子序列:子序列中的每个数不小于在它之前的数
// 1 <= k, n <= 10^5
// 1 <= arr[i] <= 10^6
// 测试链接 : https://www.luogu.com.cn/problem/P8776
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的所有代码,并把主类名改成"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;
public class Code01_LongestNoDecreaseModifyKSubarray {
public static int MAXN = 100001;
public static int[] arr = new int[MAXN];
public static int[] right = new int[MAXN];
public static int[] ends = new int[MAXN];
public static int n, k;
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;
in.nextToken();
k = (int) (in.nval);
for (int i = 1; i <= n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
}
if (k >= n) {
out.println(n);
} else {
right();
out.println(getAns());
}
out.flush();
}
}
public static void right() {
right[n] = 1;
ends[1] = arr[n];
int len = 1;
for (int i = n - 1; i > 0; i--) {
int l = 1;
int r = len;
int m, find = len + 1;
while (l <= r) {
m = (l + r) / 2;
if (ends[m] < arr[i]) {
find = m;
r = m - 1;
} else {
l = m + 1;
}
}
ends[find] = arr[i];
len = Math.max(len, find);
right[i] = find;
}
}
public static int getAns() {
int ans = 0;
int len = 0;
for (int i = k + 1, j = 1; i <= n; i++, j++) {
int l = 1;
int r = len;
int m, find = len + 1;
while (l <= r) {
m = (l + r) / 2;
// 当前的arr[i], 利用之前的ends求左边长度
if (ends[m] > arr[i]) {
find = m;
r = m - 1;
} else {
l = m + 1;
}
}
ans = Math.max(ans, find + right[i] - 1 + k);
l = 1;
r = len;
find = len + 1;
while (l <= r) {
m = (l + r) / 2;
if (ends[m] > arr[j]) {
find = m;
r = m - 1;
} else {
l = m + 1;
}
}
len = Math.max(len, find);
ends[find] = arr[j];
}
ans = Math.max(ans, len + k);
return ans;
}
}

@ -0,0 +1,59 @@
package class_2023_04_4_week;
// 开心一下的智力题 :
// 有一个村庄一共250人
// 每一个村民要么一定说谎,要么只说真话
// 村里有A、B、C、D四个球队且每个村民只会喜欢其中的一支球队
// 但是说谎者会不告知真实喜好,而且会说是另外三支球队的支持者
// 访问所有的村民之后,得到的访谈结果如下 :
// A的支持者有90
// B的支持者有100
// C的支持者有80
// D的支持者有80
// 问村里有多少个说谎者
// 下面是正式题 :
// 你有一个凸的 n 边形,其每个顶点都有一个整数值。给定一个整数数组 values
// 其中 values[i] 是第 i 个顶点的值(即 顺时针顺序 )。
// 假设将多边形 剖分 为 n - 2 个三角形。
// 对于每个三角形,该三角形的值是顶点标记的乘积,
// 三角剖分的分数是进行三角剖分后所有 n - 2 个三角形的值之和。
// 返回 多边形进行三角剖分后可以得到的最低分 。
// 测试链接 : https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/
public class Code02_MinScoreTriangulationPolygon {
public static int minScoreTriangulation(int[] values) {
int[][] dp = new int[values.length][values.length];
for (int i = 0; i < values.length; i++) {
for (int j = 0; j < values.length; j++) {
dp[i][j] = -1;
}
}
return f(values, 0, values.length - 1, dp);
}
// values[i...j]范围上这些点,要分解成多个三角形
// 三角形一个端点是values[i]另一个端点是values[j]
// 那么第三个点在i+1....j-1之间选
// 比如选了m点 : i......m.......j
// 当前获得的分数为values[i] * values[m] * values[j]
// 接下来i.....m去分解三角形、m.....j去分解三角形
public static int f(int[] values, int i, int j, int[][] dp) {
if (i >= j - 1) {
// 不够三个点,不会有得分
return 0;
}
if (dp[i][j] != -1) {
// 缓存的答案
// 如果命中直接返回
// 看体系学习班,动态规划的章节
return dp[i][j];
}
int ans = Integer.MAX_VALUE;
for (int m = i + 1; m < j; m++) {
ans = Math.min(ans, f(values, i, m, dp) + f(values, m, j, dp) + values[i] * values[m] * values[j]);
}
dp[i][j] = ans;
return ans;
}
}

@ -0,0 +1,57 @@
package class_2023_04_4_week;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// 给你一个正整数数组nums, 同时给你一个长度为 m 的整数数组 queries
// 第 i 个查询中,你需要将 nums 中所有元素变成 queries[i] 。你可以执行以下操作 任意 次:
// 将数组里一个元素 增大 或者 减小 1 。请你返回一个长度为 m 的数组 answer
// 其中 answer[i]是将 nums 中所有元素变成 queries[i] 的 最少 操作次数。
// 注意,每次查询后,数组变回最开始的值。
// 测试链接 : https://leetcode.cn/problems/minimum-operations-to-make-all-array-elements-equal/
public class Code03_MinOpToMakeElementsEqual {
public static List<Long> minOperations(int[] nums, int[] queries) {
int n = nums.length;
Arrays.sort(nums);
long[] sum = new long[n + 1];
for (int i = 0; i < n; i++) {
sum[i + 1] = sum[i] + nums[i];
}
ArrayList<Long> ans = new ArrayList<>();
int less, more;
long curAns;
for (int v : queries) {
less = bs(nums, v);
curAns = (long) (less + 1) * v - sum(sum, 0, less);
more = bs(nums, v + 1);
curAns += sum(sum, more + 1, n - 1) - (long) (n - more - 1) * v;
ans.add(curAns);
}
return ans;
}
// 查找 <v 最右的位置
// 没有返回-1
public static int bs(int[] nums, int v) {
int l = 0;
int r = nums.length - 1;
int m, ans = -1;
while (l <= r) {
m = (l + r) / 2;
if (nums[m] < v) {
ans = m;
l = m + 1;
} else {
r = m - 1;
}
}
return ans;
}
public static long sum(long[] sum, int l, int r) {
return l > r ? 0 : (sum[r + 1] - sum[l]);
}
}

@ -0,0 +1,122 @@
package class_2023_04_4_week;
// 来自微众银行
// 两个魔法卷轴问题
// 给定一个数组arr其中可能有正、负、0
// 一个魔法卷轴可以把arr中连续的一段全变成0你希望数组整体的累加和尽可能大
// 你有两个魔法卷轴,请返回数组尽可能大的累加和
// 1 <= arr长度 <= 100000
// -100000 <= arr里的值 <= 100000
public class Code04_MagicScrollProbelm {
// 暴力方法
// 为了测试
public static int maxSum1(int[] arr) {
int p1 = 0;
for (int num : arr) {
p1 += num;
}
int n = arr.length;
int p2 = mustOneScroll(arr, 0, n - 1);
int p3 = Integer.MIN_VALUE;
for (int i = 1; i < n; i++) {
p3 = Math.max(p3, mustOneScroll(arr, 0, i - 1) + mustOneScroll(arr, i, n - 1));
}
return Math.max(p1, Math.max(p2, p3));
}
// 为了测试
public static int mustOneScroll(int[] arr, int L, int R) {
int ans = Integer.MIN_VALUE;
for (int a = L; a <= R; a++) {
for (int b = a; b <= R; b++) {
int curAns = 0;
for (int i = L; i < a; i++) {
curAns += arr[i];
}
for (int i = b + 1; i <= R; i++) {
curAns += arr[i];
}
ans = Math.max(ans, curAns);
}
}
return ans;
}
// 正式方法
// 时间复杂度O(N)
public static int maxSum2(int[] arr) {
if (arr.length == 0) {
return 0;
}
// 一个卷轴也不用
int p1 = 0;
for (int num : arr) {
p1 += num;
}
int n = arr.length;
// left[i] : 0 ~ i范围上一定要用一次卷轴的情况下最大累加和多少
int[] left = new int[n];
// left[0] = 0 : 0 ~ 0一定要用一次卷轴的情况下最大累加和多少
// 每一步的前缀和
// 0~0 前缀和
int sum = arr[0];
// 之前所有前缀和的,最大值
int maxSum = Math.max(0, sum);
for (int i = 1; i < n; i++) {
// left[i - 1] + arr[i]
// maxSum : 之前所有前缀和的,最大值
left[i] = Math.max(left[i - 1] + arr[i], maxSum);
sum += arr[i];
maxSum = Math.max(maxSum, sum);
}
// 只用一次卷轴必须用0~n-1范围上的解第二种可能性
int p2 = left[n - 1];
// 第三种 :一定要用两次卷轴
int[] right = new int[n];
// right[i] : i ~ n-1范围上一定要用一次卷轴的情况下最大累加和多少
sum = arr[n - 1];
maxSum = Math.max(0, sum);
for (int i = n - 2; i >= 0; i--) {
right[i] = Math.max(arr[i] + right[i + 1], maxSum);
sum += arr[i];
maxSum = Math.max(maxSum, sum);
}
int p3 = Integer.MIN_VALUE;
for (int i = 1; i < n; i++) {
// 0..0 1...n-1
// 0..1 2...n-1
// 0..2 3...n-1
p3 = Math.max(p3, left[i - 1] + right[i]);
}
return Math.max(p1, Math.max(p2, p3));
}
// 为了测试
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 * 2 + 1)) - v;
}
return ans;
}
// 为了测试
public static void main(String[] args) {
int N = 50;
int V = 100;
int testTimes = 10000;
System.out.println("测试开始");
for (int i = 0; i < testTimes; i++) {
int n = (int) (Math.random() * N);
int[] arr = randomArray(n, V);
int ans1 = maxSum1(arr);
int ans2 = maxSum2(arr);
if (ans1 != ans2) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
}
}

@ -0,0 +1,116 @@
package class_2023_04_4_week;
// 来自微众银行
// 给出两个长度均为n的数组
// A = { a1, a2, ... ,an }
// B = { b1, b2, ... ,bn }。
// 你需要求出其有多少个区间[L,R]满足:
// 数组A中下标在[L,R]中的元素之和在[La,Ra]之中
// 数组B中下标在[L,R]中的元素之和在[Lb,Rb]之中
// 输入
// 第一行有一个正整数N(1<=N<=100000),代表两个数组的长度。
// 第二行有N个非负整数范围在0到1000000000之间代表数组中的元素。
// 第三行有N个非负整数范围在0到1000000000之间代表数组中的元素。
// 第四行有4个整数La,Ra,Lb,Rb范围在0到10^18之间代表题目描述中的参数。
// 输出
// 输出一个整数,代表所求的答案。
public class Code05_ValidRangeBetweenTwoArrays {
// 暴力方法
// 为了测试
public static int nums1(int[] A, int[] B, int la, int ra, int lb, int rb) {
int n = A.length;
int ans = 0;
for (int l = 0; l < n; l++) {
for (int r = l; r < n; r++) {
int sumA = 0;
int sumB = 0;
for (int i = l; i <= r; i++) {
sumA += A[i];
sumB += B[i];
}
if (sumA >= la && sumA <= ra && sumB >= lb && sumB <= rb) {
ans++;
}
}
}
return ans;
}
// 正式方法
// 时间复杂度O(N)
public static int nums2(int[] A, int[] B, int la, int ra, int lb, int rb) {
int n = A.length;
int ans = 0;
int rightA1 = 0, sumA1 = 0, rightA2 = 0, sumA2 = 0, rightB1 = 0, sumB1 = 0, rightB2 = 0, sumB2 = 0;
for (int l = 0; l < n; l++) {
while (rightA1 < n && sumA1 + A[rightA1] < la) {
sumA1 += A[rightA1++];
}
while (rightA2 < n && sumA2 + A[rightA2] <= ra) {
sumA2 += A[rightA2++];
}
while (rightB1 < n && sumB1 + B[rightB1] < lb) {
sumB1 += B[rightB1++];
}
while (rightB2 < n && sumB2 + B[rightB2] <= rb) {
sumB2 += B[rightB2++];
}
int left = Math.max(rightA1, rightB1);
int right = Math.min(rightA2, rightB2);
if (left < right) {
ans += right - left;
}
if (rightA1 == l) {
rightA1++;
} else {
sumA1 -= A[l];
}
sumA2 -= A[l];
if (rightB1 == l) {
rightB1++;
} else {
sumB1 -= B[l];
}
sumB2 -= B[l];
}
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);
}
return ans;
}
// 为了测试
public static void main(String[] args) {
int N = 50;
int V = 100;
int testTimes = 10000;
System.out.println("测试开始");
for (int i = 0; i < testTimes; i++) {
int n = (int) (Math.random() * N);
int[] A = randomArray(n, V);
int[] B = randomArray(n, V);
int a = (int) (Math.random() * V);
int b = (int) (Math.random() * V);
int c = (int) (Math.random() * V);
int d = (int) (Math.random() * V);
int la = Math.min(a, b);
int ra = Math.max(a, b);
int lb = Math.min(c, d);
int rb = Math.max(c, d);
int ans1 = nums1(A, B, la, ra, lb, rb);
int ans2 = nums2(A, B, la, ra, lb, rb);
if (ans1 != ans2) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
}
}

@ -0,0 +1,69 @@
package class_2023_04_4_week;
import java.util.ArrayList;
// 给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1
// 给你整数 n 和一个长度为 n - 1 的二维整数数组 edges
// 其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间有一条边。
// 再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 1
// 1 表示节点 i 处有一个金币。
// 一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:
// 收集距离当前节点距离为 2 以内的所有金币,或者 移动到树中一个相邻节点。
// 你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。
// 如果你多次经过一条边,每一次经过都会给答案加一。
// 测试链接 : https://leetcode.cn/problems/collect-coins-in-a-tree/
public class Code06_CollectCoinsInTree {
public static int collectTheCoins(int[] coins, int[][] edges) {
int n = coins.length;
ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
for (int[] edge : edges) {
// 8 - 13
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
inDegree[edge[0]]++;
inDegree[edge[1]]++;
}
int[] queue = new int[n];
int l = 0, r = 0;
for (int i = 0; i < n; ++i) {
if (inDegree[i] == 1 && coins[i] == 0) {
queue[r++] = i;
}
}
while (l < r) {
int cur = queue[l++];
for (int next : graph.get(cur)) {
if (--inDegree[next] == 1 && coins[next] == 0) {
queue[r++] = next;
}
}
}
for (int i = 0; i < n; ++i) {
if (inDegree[i] == 1 && coins[i] == 1) {
queue[r++] = i;
}
}
int[] rank = new int[n];
while (l < r) {
int cur = queue[l++];
for (int next : graph.get(cur)) {
if (--inDegree[next] == 1) {
rank[next] = rank[cur] + 1;
queue[r++] = next;
}
}
}
int ans = 0;
for (int[] edge : edges)
if (rank[edge[0]] >= 2 && rank[edge[1]] >= 2) {
ans += 2;
}
return ans;
}
}

@ -3473,5 +3473,82 @@ subtexti == subtextk - i + 1 表示所有 i 的有效值( 即 1 <= i <= k )
第067节 2023年4月第4周流行算法题目解析
(因为上节课网络卡顿,所以这节课安排重讲)
来自学员问题,蓝桥杯练习题
给定一个长度为n的数组arr
现在你有一次机会, 将其中连续的K个数全修改成任意一个值
请你计算如何修改可以使修改后的数 列的最长不下降子序列最长
请输出这个最长的长度。
最长不下降子序列:子序列中的每个数不小于在它之前的数
1 <= k, n <= 10^5
1 <= arr[i] <= 10^6
测试链接 : https://www.luogu.com.cn/problem/P8776
开心一下的智力题 :
有一个村庄一共250人
每一个村民要么一定说谎,要么只说真话
村里有A、B、C、D四个球队且每个村民只会喜欢其中的一支球队
但是说谎者会不告知真实喜好,而且会说是另外三支球队的支持者(全支持另外三支球队)
访问所有的村民之后,得到的访谈结果如下 :
A的支持者有90
B的支持者有100
C的支持者有80
D的支持者有80
问村里有多少个说谎者
下面是正式题 :
你有一个凸的 n 边形,其每个顶点都有一个整数值。给定一个整数数组 values
其中 values[i] 是第 i 个顶点的值(即 顺时针顺序 )。
假设将多边形 剖分 为 n - 2 个三角形。
对于每个三角形,该三角形的值是顶点标记的乘积,
三角剖分的分数是进行三角剖分后所有 n - 2 个三角形的值之和。
返回 多边形进行三角剖分后可以得到的最低分 。
测试链接 : https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/
给你一个正整数数组nums, 同时给你一个长度为 m 的整数数组 queries
第 i 个查询中,你需要将 nums 中所有元素变成 queries[i] 。你可以执行以下操作 任意 次:
将数组里一个元素 增大 或者 减小 1 。请你返回一个长度为 m 的数组 answer
其中 answer[i]是将 nums 中所有元素变成 queries[i] 的 最少 操作次数。
注意,每次查询后,数组变回最开始的值。
测试链接 : https://leetcode.cn/problems/minimum-operations-to-make-all-array-elements-equal/
来自微众银行
两个魔法卷轴问题
给定一个数组arr其中可能有正、负、0
一个魔法卷轴可以把arr中连续的一段全变成0你希望数组整体的累加和尽可能大
你有两个魔法卷轴,请返回数组尽可能大的累加和
1 <= arr长度 <= 100000
-100000 <= arr里的值 <= 100000
来自微众银行
给出两个长度均为n的数组
A = { a1, a2, ... ,an }
B = { b1, b2, ... ,bn }。
你需要求出其有多少个区间[L,R]满足:
数组A中下标在[L,R]中的元素之和在[La,Ra]之中
数组B中下标在[L,R]中的元素之和在[Lb,Rb]之中
输入
第一行有一个正整数N(1<=N<=100000),代表两个数组的长度。
第二行有N个非负整数范围在0到1000000000之间代表数组中的元素。
第三行有N个非负整数范围在0到1000000000之间代表数组中的元素。
第四行有4个整数La,Ra,Lb,Rb范围在0到10^18之间代表题目描述中的参数。
输出
输出一个整数,代表所求的答案
给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1
给你整数 n 和一个长度为 n - 1 的二维整数数组 edges
其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间有一条边。
再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 1
1 表示节点 i 处有一个金币。
一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:
收集距离当前节点距离为 2 以内的所有金币,或者 移动到树中一个相邻节点。
你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。
如果你多次经过一条边,每一次经过都会给答案加一。
测试链接 : https://leetcode.cn/problems/collect-coins-in-a-tree/

Loading…
Cancel
Save