From ca5d66d6efcc4844788d1240f557f64a77e06f81 Mon Sep 17 00:00:00 2001 From: algorithmzuo Date: Wed, 21 Jun 2023 23:16:13 +0800 Subject: [PATCH] modify code --- .../Code01_UnhappyExperiment.java | 83 ++++++ .../Code02_PasswordWays.java | 75 +++++ .../Code03_DeleteOneNumberLenKMaxSum.java | 118 ++++++++ .../Code04_TowSubArrayMinLengthBothSumT.java | 187 ++++++++++++ .../Code05_CrossRiver.java | 74 +++++ .../Code06_BecomeMostPopularShop.java | 273 ++++++++++++++++++ ...养的大厂算法面试题(正在直播) | 68 ++++- 7 files changed, 869 insertions(+), 9 deletions(-) create mode 100644 算法周更班/class_2023_06_3_week/Code01_UnhappyExperiment.java create mode 100644 算法周更班/class_2023_06_3_week/Code02_PasswordWays.java create mode 100644 算法周更班/class_2023_06_3_week/Code03_DeleteOneNumberLenKMaxSum.java create mode 100644 算法周更班/class_2023_06_3_week/Code04_TowSubArrayMinLengthBothSumT.java create mode 100644 算法周更班/class_2023_06_3_week/Code05_CrossRiver.java create mode 100644 算法周更班/class_2023_06_3_week/Code06_BecomeMostPopularShop.java diff --git a/算法周更班/class_2023_06_3_week/Code01_UnhappyExperiment.java b/算法周更班/class_2023_06_3_week/Code01_UnhappyExperiment.java new file mode 100644 index 0000000..c9a30c3 --- /dev/null +++ b/算法周更班/class_2023_06_3_week/Code01_UnhappyExperiment.java @@ -0,0 +1,83 @@ +package class_2023_06_3_week; + +import java.util.Arrays; + +// 课前放松一下 +// 做一个令人不开心的实验 +// 一开始有100个人,每个人都有100元 +// 在每一轮都做如下的事情 : +// 每个人都必须拿出1元钱给除自己以外的其他人,给谁完全随机 +// 如果某个人在这一轮的钱数为0,那么他可以不给,但是可以接收 +// 发生很多很多轮之后,这100人的社会财富分布很均匀吗? +public class Code01_UnhappyExperiment { + + public static void main(String[] args) { + System.out.println("一个社会的基尼系数是一个在0~1之间的小数"); + System.out.println("基尼系数为0代表所有人的财富完全一样"); + System.out.println("基尼系数为1代表有1个人掌握了全社会的财富"); + System.out.println("基尼系数越小,代表社会财富分布越均衡;越大则代表财富分布越不均衡"); + System.out.println("在2022年,世界各国的平均基尼系数为0.44"); + System.out.println("目前普遍认为,当基尼系数到达 0.5 时"); + System.out.println("就意味着社会贫富差距非常大,分布非常不均匀"); + System.out.println("社会可能会因此陷入危机,比如大量的犯罪或者经历社会动荡"); + System.out.println("测试开始"); + int n = 100; + int t = 1000000; + System.out.println("人数 : " + n); + System.out.println("轮数 : " + t); + experiment(n, t); + System.out.println("测试结束"); + } + + // 完全按照说的来实验 + public static void experiment(int n, int t) { + double[] wealth = new double[n]; + boolean[] hasMoney = new boolean[n]; + Arrays.fill(wealth, 100); + for (int i = 0; i < t; i++) { + Arrays.fill(hasMoney, false); + for (int j = 0; j < n; j++) { + if (wealth[j] > 0) { + hasMoney[j] = true; + } + } + for (int j = 0; j < n; j++) { + if (hasMoney[j]) { + int other = j; + do { + other = (int) (Math.random() * n); + } while (other == j); + wealth[j]--; + wealth[other]++; + } + } + } + Arrays.sort(wealth); + System.out.println("列出每个人的财富(贫穷到富有) : "); + for (int i = 0; i < n; i++) { + System.out.print((int) wealth[i] + " "); + if (i % 10 == 9) { + System.out.println(); + } + } + System.out.println(); + System.out.println("这个社会的基尼系数为 : " + calculateGini(wealth)); + } + + // 计算基尼系数 + // 这个函数是正确的 + public static double calculateGini(double[] wealth) { + Arrays.sort(wealth); + double sumOfAbsoluteDifferences = 0; + double sumOfWealth = 0; + int n = wealth.length; + for (int i = 0; i < n; i++) { + sumOfWealth += wealth[i]; + for (int j = 0; j < n; j++) { + sumOfAbsoluteDifferences += Math.abs(wealth[i] - wealth[j]); + } + } + return sumOfAbsoluteDifferences / (2 * n * sumOfWealth); + } + +} \ No newline at end of file diff --git a/算法周更班/class_2023_06_3_week/Code02_PasswordWays.java b/算法周更班/class_2023_06_3_week/Code02_PasswordWays.java new file mode 100644 index 0000000..4591e5a --- /dev/null +++ b/算法周更班/class_2023_06_3_week/Code02_PasswordWays.java @@ -0,0 +1,75 @@ +package class_2023_06_3_week; + +// 来自字节 +// 密码是一串长度为n的小写字母,一则关于密码的线索纸条 +// 首先将字母a到z编号为0到25编号 +// 纸条上共有n个整数ai,其中a1表示密码里第一个字母的编号 +// 若i>1的话就表示第i个字母和第i-1个字母编号的差值 +// 例如,a2就代表密码中第1个字母和第2个字母编号的差值 +// 若密码是acb,那么纸条上的数字就是[5, 2, 1] +// a b c d e f +// 0 1 2 3 4 5 +// 返回可能的密码的个数,由于结果可能很大, +// 输出对1000000007取模的结果 +// 1 <= n <= 10^5 +// 0 <= ai <= 25 +public class Code02_PasswordWays { + + // 暴力递归 + public static int ways1(int[] arr) { + // + // 0 -> a + // 1 -> b + return process1(arr, 1, arr[0]); + } + + // arr : 字条 i现在来到的位置 + // pre : 前一个字符的编号 + // f ?... + // 5 + // 3 + // 返回最终,有多少合法的字符串 + // -1 >=26 + // pre pre + public static int process1(int[] arr, int i, int pre) { + int ans = 0; + if (pre < 0 || pre > 25) { + ans = 0; + } else { + // pre 有效! + if (i == arr.length) { + // 之前的转化,一定走到尽头了 + ans = 1; + } else { + // pre f 2 + // f+2..... + // f-2..... + ans += process1(arr, i + 1, pre - arr[i]); + ans += process1(arr, i + 1, pre + arr[i]); + } + } + return ans; + } + + // 动态规划 + public static int ways2(int[] arr) { + int mod = 1000000007; + int n = arr.length; + int[][] dp = new int[n + 1][26]; + for (int j = 0; j < 26; j++) { + dp[n][j] = 1; + } + for (int i = n - 1; i >= 1; i--) { + for (int j = 0; j < 26; j++) { + if (j - arr[i] >= 0) { + dp[i][j] = dp[i + 1][j - arr[i]]; + } + if (j + arr[i] < 26) { + dp[i][j] = (dp[i][j] + dp[i + 1][j + arr[i]]) % mod; + } + } + } + return dp[1][arr[0]]; + } + +} diff --git a/算法周更班/class_2023_06_3_week/Code03_DeleteOneNumberLenKMaxSum.java b/算法周更班/class_2023_06_3_week/Code03_DeleteOneNumberLenKMaxSum.java new file mode 100644 index 0000000..e10cb1b --- /dev/null +++ b/算法周更班/class_2023_06_3_week/Code03_DeleteOneNumberLenKMaxSum.java @@ -0,0 +1,118 @@ +package class_2023_06_3_week; + +// 来自字节 +// 给定整数数组arr,求删除任一元素后, +// 新数组中长度为k的子数组累加和的最大值 +public class Code03_DeleteOneNumberLenKMaxSum { + + // 暴力方法 + // 为了测试 + public static int maxSum1(int[] arr, int k) { + int n = arr.length; + if (n <= k) { + return 0; + } + int ans = Integer.MIN_VALUE; + for (int i = 0; i < n; i++) { + int[] rest = delete(arr, i); + ans = Math.max(ans, lenKmaxSum(rest, k)); + } + return ans; + } + + // 暴力方法 + // 为了测试 + public static int[] delete(int[] arr, int index) { + int len = arr.length - 1; + int[] ans = new int[len]; + int i = 0; + for (int j = 0; j < arr.length; j++) { + if (j != index) { + ans[i++] = arr[j]; + } + } + return ans; + } + + // 暴力方法 + // 为了测试 + public static int lenKmaxSum(int[] arr, int k) { + int n = arr.length; + int ans = Integer.MIN_VALUE; + for (int i = 0; i <= n - k; i++) { + int cur = 0; + for (int j = i, cnt = 0; cnt < k; j++, cnt++) { + cur += arr[j]; + } + ans = Math.max(ans, cur); + } + return ans; + } + + // 正式方法 + // 时间复杂度O(N) + public static int maxSum2(int[] arr, int k) { + int n = arr.length; + if (n <= k) { + return 0; + } + // 窗口内最小值的更新结构,双端队列! + // 看课!体系学习班,章节25 + int[] window = new int[n]; + int l = 0; + int r = 0; + // 维持窗口内的累加和 + long sum = 0; + int ans = Integer.MIN_VALUE; + // k = 10 + // 0,1,2,3,.....10 11 12 13 + // 0... 10 + // 1 ...11 + // 2 ...12 + // 3....13 + for (int i = 0; i < n; i++) { + while (l < r && arr[window[r - 1]] >= arr[i]) { + r--; + } + window[r++] = i; + sum += arr[i]; + if (i >= k) { + ans = Math.max(ans, (int) (sum - arr[window[l]])); + if (window[l] == i - k) { + l++; + } + sum -= arr[i - k]; + } + } + return ans; + } + + // 为了测试 + // 生成长度为n,值在[-v, +v]之间 + 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() * (2 * v + 1)) - v; + } + return ans; + } + + public static void main(String[] args) { + int N = 100; + int V = 1000; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int k = (int) (Math.random() * N) + 1; + int ans1 = maxSum1(arr, k); + int ans2 = maxSum2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2023_06_3_week/Code04_TowSubArrayMinLengthBothSumT.java b/算法周更班/class_2023_06_3_week/Code04_TowSubArrayMinLengthBothSumT.java new file mode 100644 index 0000000..224943b --- /dev/null +++ b/算法周更班/class_2023_06_3_week/Code04_TowSubArrayMinLengthBothSumT.java @@ -0,0 +1,187 @@ +package class_2023_06_3_week; + +import java.util.Arrays; +import java.util.HashMap; + +// 来自字节 +// 给定一个数组arr,长度为n,在其中要选两个不相交的子数组 +// 两个子数组的累加和都要是T,返回所有满足情况中,两个子数组长度之和最小是多少 +// 如果没有有效方法,返回-1 +// 正式 : +// 2 <= n <= 10^6 +// 0 <= arr[i] <= 10000 +// 1 <= T <= 10^8 +// 扩展 : +// 2 <= n <= 10^6 +// -10000 <= arr[i] <= 10000 +// 1 <= T <= 10^8 +// 都能时间复杂度,做到O(N) + +public class Code04_TowSubArrayMinLengthBothSumT { + + // 暴力方法 + // 为了验证 + // 假设数组中的值可以正、负、0(就是扩展数据范围也能正确的暴力方法) + public static int minLenBothT1(int[] arr, int t) { + int n = arr.length; + int[] sum = new int[n]; + sum[0] = arr[0]; + for (int i = 1; i < n; i++) { + sum[i] = sum[i - 1] + arr[i]; + } + int ans = Integer.MAX_VALUE; + for (int a = 0; a < n - 1; a++) { + for (int b = a; b < n - 1; b++) { + for (int c = b + 1; c < n; c++) { + for (int d = c; d < n; d++) { + if (sum(sum, a, b) == t && sum(sum, c, d) == t) { + ans = Math.min(ans, b + d - a - c + 2); + } + } + } + } + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // 暴力方法 + // 为了验证 + public static int sum(int[] sum, int l, int r) { + return l == 0 ? sum[r] : (sum[r] - sum[l - 1]); + } + + // 正式方法 + // 时间复杂度O(N) + // 数组都是非负数情况下的正确方法 + public static int minLenBothT2(int[] arr, int t) { + int n = arr.length; + if (t < 0) { + return -1; + } + if (t == 0) { + int cnt = 0; + for (int num : arr) { + if (num == 0) { + cnt++; + } + } + return cnt >= 2 ? 2 : -1; + } + int[] right = new int[n]; + Arrays.fill(right, Integer.MAX_VALUE); + for (int l = 1, r = 1, sum = 0; l < n; l++) { + r = Math.max(r, l); + while (r < n && sum < t) { + sum += arr[r++]; + } + if (sum == t) { + right[l] = r - l; + } + sum -= arr[l]; + } + for (int i = n - 2; i >= 0; i--) { + right[i] = Math.min(right[i], right[i + 1]); + } + int ans = Integer.MAX_VALUE; + for (int r = n - 2, l = n - 2, sum = 0; r >= 0; r--) { + l = Math.min(l, r); + while (l >= 0 && sum < t) { + sum += arr[l--]; + } + if (sum == t && right[r + 1] != Integer.MAX_VALUE) { + ans = Math.min(ans, r - l + right[r + 1]); + } + sum -= arr[r]; + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // 扩展方法 + // 时间复杂度O(N) + // 假设数组中的值可以正、负、0,就是扩展数据范围也能正确的方法 + public static int minLenBothT3(int[] arr, int t) { + int n = arr.length; + HashMap sums = new HashMap<>(); + sums.put(0, -1); + int[] left = new int[n]; + Arrays.fill(left, Integer.MAX_VALUE); + for (int i = 0, sum = 0; i < n - 1; i++) { + sum += arr[i]; + if (sums.containsKey(sum - t)) { + left[i] = i - sums.get(sum - t); + } + sums.put(sum, i); + } + for (int i = 1; i < n - 1; i++) { + left[i] = Math.min(left[i - 1], left[i]); + } + int ans = Integer.MAX_VALUE; + sums.clear(); + sums.put(0, n); + for (int i = n - 1, sum = 0; i >= 1; i--) { + sum += arr[i]; + if (sums.containsKey(sum - t) && left[i - 1] != Integer.MAX_VALUE) { + ans = Math.min(ans, left[i - 1] + sums.get(sum - t) - i); + } + sums.put(sum, i); + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // 为了测试 + // 随机数组长度为n + // 值在[0, v]之间随机 + public static int[] randomArray1(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; + } + + // 为了测试 + // 随机数组长度为n + // 值在[-v,+v]之间随机 + public static int[] randomArray2(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * (2 * v + 1)) - v; + } + return ans; + } + + public static void main(String[] args) { + int N = 100; + int V = 100; + int T = 100; + int testTimes = 10000; + System.out.println("正式方法测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 2; + int v = (int) (Math.random() * V) + 1; + int[] arr = randomArray1(n, v); + int t = (int) (Math.random() * T); + int ans1 = minLenBothT1(arr, t); + int ans2 = minLenBothT2(arr, t); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("正式方法测试结束"); + + System.out.println("扩展方法测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 2; + int v = (int) (Math.random() * V) + 1; + int[] arr = randomArray2(n, v); + int t = (int) (Math.random() * T); + int ans1 = minLenBothT1(arr, t); + int ans3 = minLenBothT3(arr, t); + if (ans1 != ans3) { + System.out.println("出错了!"); + } + } + System.out.println("扩展方法测试结束"); + } + +} diff --git a/算法周更班/class_2023_06_3_week/Code05_CrossRiver.java b/算法周更班/class_2023_06_3_week/Code05_CrossRiver.java new file mode 100644 index 0000000..993e0bc --- /dev/null +++ b/算法周更班/class_2023_06_3_week/Code05_CrossRiver.java @@ -0,0 +1,74 @@ +package class_2023_06_3_week; + +// 来自华为OD +// 一支n个士兵的军队正在趁夜色逃亡,途中遇到一条湍急的大河 +// 敌军在T的时长后到达河面,没到过对岸的士兵都会被消灭 +// 现在军队只找到了1只小船,这船最多能同时坐上2个士兵。 +// 1) 当1个士兵划船过河,用时为a[i] +// 2) 当2个士兵坐船同时划船过河时, 用时为max(a[j],a[i])两士兵中用时最长的 +// 3) 当2个士兵坐船只有1个士兵划船时, 用时为a[i] * 10, a[i]为划船士兵用时 +// 请帮忙给出一种解决方案,保证存活的士兵最多,且过河用时最短 +// 我们先看一下如下的题,再讲一下华为OD的扩展 +// 测试链接 : https://www.luogu.com.cn/problem/P1809 +// 有一个大晴天, Oliver与同学们一共N人出游, 他们走到一条河的东岸边,想要过河到西岸 +// 而东岸边有一条小船。船太小了,一次只能乘坐两人,每个人都有一个渡河时间T +// 船划到对岸的时间等于船上渡河时间较长的人所用时间 +// 现在已知N个人的渡河时间Ti +// Oliver 想要你告诉他,他们最少要花费多少时间,才能使所有人都过河 +// 注意,只有船在东岸(西岸)的人才能坐上船划到对岸。 + +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 Code05_CrossRiver { + + public static int MAXN = 100001; + + public static int[] arr = new int[MAXN]; + + public static int[] dp = new int[MAXN]; + + public static int n; + + 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 = 0; i < n; i++) { + in.nextToken(); + arr[i] = (int) in.nval; + } + int ans = minCost(); + out.println(ans); + out.flush(); + } + + } + + public static int minCost() { + Arrays.sort(arr, 0, n); + if (n >= 1) { + dp[0] = arr[0]; + } + if (n >= 2) { + dp[1] = arr[1]; + } + if (n >= 3) { + dp[2] = arr[0] + arr[1] + arr[2]; + } + for (int i = 3; i < n; i++) { + dp[i] = Math.min( + dp[i - 2] + arr[1] + arr[0] + arr[i] + arr[1], + dp[i - 1] + arr[i] + arr[0]); + } + return dp[n - 1]; + } + +} diff --git a/算法周更班/class_2023_06_3_week/Code06_BecomeMostPopularShop.java b/算法周更班/class_2023_06_3_week/Code06_BecomeMostPopularShop.java new file mode 100644 index 0000000..96e46d2 --- /dev/null +++ b/算法周更班/class_2023_06_3_week/Code06_BecomeMostPopularShop.java @@ -0,0 +1,273 @@ +package class_2023_06_3_week; + +import java.util.Arrays; +import java.util.ArrayList; + +// 来自华为OD +// 1号店铺贿赂问题 +// 店铺数量n,编号1~n +// 人的数量m,编号1~m +// 每个人有自己投票的店铺p,和改投1号店的报价x +// 返回想让1号店铺成为人气最高的店,至少花多少钱 +// 1 <= p,n,m <= 3000 +// 1 <= x <= 10^9 +public class Code06_BecomeMostPopularShop { + + // 暴力方法 + // 为了测试 + // n : 店铺数量 + // m : 人的数量 + // arr : 长度m,每个人有支持的店铺以及改投1号店的钱数 + // 如果有某个人已经支持了1号店铺,那么钱数认为无意义 + // 返回至少要花多少钱,才能让1号店铺成为人气最高的点 + public static long minCost1(int n, int m, int[][] arr) { + // 统计每个店铺的支持人数 + int[] cnts = new int[n + 1]; + for (int[] p : arr) { + cnts[p[0]]++; + } + boolean needChange = false; + for (int i = 2; i <= n; i++) { + if (cnts[i] >= cnts[1]) { + needChange = true; + break; + } + } + // 如果1号店铺已经是最大人气,直接返回0 + if (!needChange) { + return 0; + } + return process(arr, 0, n, new boolean[m]); + } + + // 暴力方法 + // 为了测试 + // 暴力走两个分支:每个人要么改,要么不改 + // 如果i号人改投,那么change[i] = true; + // 如果i号人不改投,那么change[i] = false; + // n : 店铺数量,固定参数 + public static long process(int[][] arr, int i, int n, boolean[] change) { + if (i == arr.length) { + // 统计投票结果 + int[] cnts = new int[n + 1]; + long sum = 0; + for (int j = 0; j < arr.length; j++) { + if (change[j]) { + // 改投的店,人气都给1号店 + cnts[1]++; + // 并且统计改投的钱数 + sum += arr[j][1]; + } else { + // 没有改投的店,统计词频 + cnts[arr[j][0]]++; + } + } + // 看看此时1号店是不是人气最高的 + boolean ok = true; + for (int j = 2; j <= n; j++) { + if (cnts[j] >= cnts[1]) { + ok = false; + break; + } + } + if (!ok) { + // 如果1号店不是人气最高的 + // 说明当前的方案无效 + return Long.MAX_VALUE; + } else { + // 否则说明当前的方案有效 + // 返回这种方案的改投钱数 + return sum; + } + } else { + // 可能性1,改投 + long p1 = Long.MAX_VALUE; + if (arr[i][0] != 1) { + // 如果当前用户不支持1号店,才存在改投的可能性 + change[i] = true; + p1 = process(arr, i + 1, n, change); + change[i] = false; + } + // 可能性1,不改投 + long p2 = process(arr, i + 1, n, change); + return Math.min(p1, p2); + } + } + + // n : 店铺的数量 + // m : 人的数量 + // arr : i [本来支持的店号, 如果改投1号店贿赂的钱] + public static long minCost2(int n, int m, int[][] arr) { + // 统计每个店铺的支持人数 + // 1 ~ n + int[] cnts = new int[n + 1]; + for (int[] p : arr) { + cnts[p[0]]++; + } + // needChange : 到底需不需要贿赂呢? + boolean needChange = false; + for (int i = 2; i <= n; i++) { + if (cnts[i] >= cnts[1]) { + needChange = true; + break; + } + } + // 如果1号店铺已经是最大人气,直接返回0 + if (!needChange) { + return 0; + } + // 1号店铺目前不是人气最高,需要贿赂 + // 把所有的人,根据改投的钱数排序 + // 钱少的在前面 + // 排序之后,人也重新编号了,因为每个人的下标变了 + Arrays.sort(arr, (a, b) -> a[1] - b[1]); + // 每个店拥有哪些人做统计 + // 比如,5号店有:13号人、16号人、23号人 + // shops.get(5) = {13, 16, 23} + // 注意,这个编号是排序之后的编号 + // 也就是说,如果经历了排序,然后此时分组 + // 5号店有:13号人、16号人、23号人 + // 那么13号人的转投钱数 <= 16号人的 <= 23号人的 + // shops.get(5) = {13, 16, 23} + // 每一个下标,都是排序之后的数组中,人的下标 + ArrayList> shops = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + shops.add(new ArrayList<>()); + } + for (int i = 0; i < m; i++) { + shops.get(arr[i][0]).add(i); + } + // 某个用户是否已经改投1号店了 + boolean[] used = new boolean[m]; + // 最小的钱数 + long ans = Long.MAX_VALUE; + // 1号店,已经有50人支持了,没赢! + // 51 52 53 ... + for (int i = cnts[1] + 1; i <= m; i++) { + // 1号店铺,只允许最终有i个人投它! + // 返回赢的情况下,最少的钱数 + // 如果规定好的人数,怎么都赢不了,返回-1 + long money = f(arr, n, cnts[1], i, shops, used); + if (money != -1) { + ans = Math.min(ans, money); + } + } + return ans; + } + + // arr : 所有人都在arr里,并且根据钱数排好序了 + // n : 一共有多少店 + // already : 一号店已经有的人数 + // must : 一号店最后一定要达到的人数,并且一定要以这个人数成为人气最高的店 + // shops : 每个店铺里都有哪些支持者,固定的 + // used : 某个人是否已经改投1号店了,辅助数组 + // 返回值 : + // 如果一号店人数最后一定要达到must,并且一定可以成为人气最高的店,那么返回至少花的钱数 + // 如果上面说的做不到,返回-1 + public static long f( + int[][] arr, int n, + int already, int must, + ArrayList> shops, + boolean[] used) { + // 最开始时,任何人都没有转投 + Arrays.fill(used, false); + // 总钱数 + long sum = 0; + for (int i = 2; i <= n; i++) { + // 从2号店开始,考察每个店铺 + // 如果当前店铺人数>=must,那么一定要减到must以下 + // needChange : 当前的店有多少人需要转投 + int needChange = Math.max(0, shops.get(i).size() - must + 1); + for (int j = 0; j < needChange; j++) { + // 因为arr中已经根据钱数排序 + // 所以当前店铺拥有的支持者里 + // 也一定是根据钱数从小到大排列的 + // 这些人都给1号店铺 + int people = shops.get(i).get(j); + sum += arr[people][1]; + // 当前的人已经算用过了,改投过了 + used[people] = true; + } + // 改投的人,要归属给1号店铺 + already += needChange; + if (already > must) { + // 如果超过了must + // 说明1号店铺就严格达到must的人数,是做不到成为人气最高的店铺的 + // 因为别的店的人数要降到must以下,转移的人都会超过must + // 所以返回-1 + return -1; + } + } + // 经过上面的过程 + // 1号店已经接受一些人了 + // 接受的人是: + // 别的店的人数要降到must以下,所以转移过来的人 + // 但是有可能1号店此时的人数already,还是不够must个 + // 那么要凑齐must个,必须再选花钱少的人,补齐 + for (int i = 0, j = 0; already + j < must; i++) { + // 补齐的人员,不能原本就属于1号店铺 + // 也不能是上面的过程中,转移过来的,因为已经用掉了 + // 所以两个条件都具备,才能给1号店去补 + if (arr[i][0] != 1 && !used[i]) { + sum += arr[i][1]; + j++; + } + } + // 返回最终花的钱数 + return sum; + } + + // 为了测试 + // 生成人数为len的数据 + // 每个人支持的店铺在1~n之间随机 + // 每个人改投的钱数在1~v之间随机 + public static int[][] randomArray(int len, int n, int v) { + int[][] arr = new int[len][2]; + for (int i = 0; i < len; i++) { + arr[i][0] = (int) (Math.random() * n) + 1; + arr[i][1] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int M = 16; + int V = 100; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[][] arr = randomArray(m, n, V); + long ans1 = minCost1(n, m, arr); + long ans2 = minCost2(n, m, arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println("n : " + n); + System.out.println("m : " + m); + for (int[] p : arr) { + System.out.println(p[0] + " , " + p[1]); + } + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + + // 这已经是题目给的最大数据量了 + // 完全可以运行通过 + int n = 3000; + int m = 3000; + int v = 1000000000; + int[][] arr = randomArray(n, m, v); + long start = System.currentTimeMillis(); + minCost2(n, m, arr); + long end = System.currentTimeMillis(); + System.out.println("最大数据量时的运行时间 : " + (end - start) + " 毫秒"); + + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index a877f5f..bd2df74 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -3899,21 +3899,71 @@ nums[i] < nums[k] < nums[j] < nums[l] 。 +第073节 2023年6月3周流行算法题目解析 +课前放松一下 +做一个令人不开心的实验 +一开始有100个人,每个人都有100元 +在每一轮都做如下的事情 : +每个人都必须拿出1元钱给除自己以外的其他人,给谁完全随机 +如果某个人在这一轮的钱数为0,那么他可以不给,但是可以接收 +发生很多很多轮之后,这100人的社会财富分布很均匀吗? +来自字节 +密码是一串长度为n的小写字母,一则关于密码的线索纸条 +首先将字母a到z编号为0到25编号 +纸条上共有n个整数ai,其中a1表示密码里第一个字母的编号 +若i>1的话就表示第i个字母和第i-1个字母编号的差值 +例如,a2就代表密码中第1个字母和第2个字母编号的差值 +若密码是acb,那么纸条上的数字就是[0, 2, 1] +返回可能的密码的个数,由于结果可能很大, +输出对1000000007取模的结果 +1 <= n <= 10^5 +0 <= ai <= 25 +来自字节 +给定整数数组arr,求删除任一元素后, +新数组中长度为k的子数组累加和的最大值 +来自字节 +给定一个数组arr,长度为n,在其中要选两个不相交的子数组 +两个子数组的累加和都要是T,返回所有满足情况中,两个子数组长度之和最小是多少 +如果没有有效方法,返回-1 +正式 : +2 <= n <= 10^6 +0 <= arr[i] <= 10000 +1 <= T <= 10^8 +扩展 : +2 <= n <= 10^6 +-10000 <= arr[i] <= 10000 +1 <= T <= 10^8 +都能时间复杂度做到O(N) +来自华为OD +一支n个士兵的军队正在趁夜色逃亡,途中遇到一条湍急的大河 +敌军在T的时长后到达河面,没到过对岸的士兵都会被消灭 +现在军队只找到了1只小船,这船最多能同时坐上2个士兵。 +1) 当1个士兵划船过河,用时为a[i] +2) 当2个士兵坐船同时划船过河时, 用时为max(a[j],a[i])两士兵中用时最长的 +3) 当2个士兵坐船只有1个士兵划船时, 用时为a[i] * 10, a[i]为划船士兵用时 +请帮忙给出一种解决方案,保证存活的士兵最多,且过河用时最短 +我们先看一下如下的题,再讲一下华为OD的扩展 +测试链接 : https://www.luogu.com.cn/problem/P1809 +有一个大晴天, Oliver与同学们一共N人出游, 他们走到一条河的东岸边,想要过河到西岸 +而东岸边有一条小船。船太小了,一次只能乘坐两人,每个人都有一个渡河时间T +船划到对岸的时间等于船上渡河时间较长的人所用时间 +现在已知N个人的渡河时间Ti +Oliver 想要你告诉他,他们最少要花费多少时间,才能使所有人都过河 +注意,只有船在东岸(西岸)的人才能坐上船划到对岸。 - - - - - - - - - +来自华为OD +1号店铺贿赂问题 +店铺数量n,编号1~n +人的数量m,编号1~m +每个人有自己投票的店铺p,和改投1号店的报价x +返回想让1号店铺成为人气最高的店,至少花多少钱 +1 <= p,n,m <= 3000 +1 <= x <= 10^9