diff --git a/算法周更班/class_2023_03_3_week/Code01_DivideArrayIntoIncreasingSequences.java b/算法周更班/class_2023_03_3_week/Code01_DivideArrayIntoIncreasingSequences.java new file mode 100644 index 0000000..e95af2b --- /dev/null +++ b/算法周更班/class_2023_03_3_week/Code01_DivideArrayIntoIncreasingSequences.java @@ -0,0 +1,23 @@ +package class_2023_03_3_week; + +// 给你一个 非递减 的正整数数组 nums 和整数 K +// 判断该数组是否可以被分成一个或几个 长度至少 为 K 的 不相交的递增子序列 +// 测试链接 : https://leetcode.cn/problems/divide-array-into-increasing-sequences/ +public class Code01_DivideArrayIntoIncreasingSequences { + + public static boolean canDivideIntoSubsequences(int[] nums, int k) { + int cnt = 1; + int maxCnt = 1; + for (int i = 1; i < nums.length; i++) { + if (nums[i - 1] != nums[i]) { + maxCnt = Math.max(maxCnt, cnt); + cnt = 1; + } else { + cnt++; + } + } + maxCnt = Math.max(maxCnt, cnt); + return nums.length / maxCnt >= k; + } + +} diff --git a/算法周更班/class_2023_03_3_week/Code02_SplitToSubsetMakeMinAverageSum.java b/算法周更班/class_2023_03_3_week/Code02_SplitToSubsetMakeMinAverageSum.java new file mode 100644 index 0000000..8fb6ba5 --- /dev/null +++ b/算法周更班/class_2023_03_3_week/Code02_SplitToSubsetMakeMinAverageSum.java @@ -0,0 +1,119 @@ +package class_2023_03_3_week; + +import java.util.ArrayList; +import java.util.Arrays; + +// 来自学员问题 +// 真实大厂笔试题 +// 给定一个数组arr,长度为n +// 再给定一个数字k,表示一定要将arr划分成k个集合 +// 每个数字只能进一个集合 +// 返回每个集合内部的平均值都累加起来最小的值 +// 平均值向下取整 +// 1 <= n <= 10^5 +// 0 <= arr[i] <= 10^5 +// 1 <= k <= n +public class Code02_SplitToSubsetMakeMinAverageSum { + + // 暴力方法 + // 为了验证 + public static int minAverageSum1(int[] arr, int k) { + if (arr.length < k) { + return -1; + } + ArrayList sets = new ArrayList<>(); + for (int i = 0; i < k; i++) { + sets.add(new Info(0, 0)); + } + return process(arr, 0, k, sets); + } + + // 暴力方法 + // 为了验证 + public static class Info { + public int sum; + public int cnt; + + public Info(int s, int c) { + sum = s; + cnt = c; + } + } + + // 暴力方法 + // 为了验证 + public static int process(int[] arr, int i, int k, ArrayList sets) { + if (i == arr.length) { + int ans = 0; + for (Info set : sets) { + if (set.cnt == 0) { + return Integer.MAX_VALUE; + } else { + ans += set.sum / set.cnt; + } + } + return ans; + } else { + int ans = Integer.MAX_VALUE; + int cur = arr[i]; + for (int j = 0; j < k; j++) { + sets.get(j).sum += cur; + sets.get(j).cnt += 1; + ans = Math.min(ans, process(arr, i + 1, k, sets)); + sets.get(j).sum -= cur; + sets.get(j).cnt -= 1; + } + return ans; + } + } + + // 正式方法 + // 时间复杂度O(N * logN) + public static int minAverageSum2(int[] arr, int k) { + if (arr.length < k) { + return -1; + } + Arrays.sort(arr); + int ans = 0; + for (int i = 0; i < k - 1; i++) { + ans += arr[i]; + } + int sum = 0; + for (int i = k - 1; i < arr.length; i++) { + sum += arr[i]; + } + ans += sum / (arr.length - k + 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); + } + return ans; + } + + // 随机测试对数器 + // 为了测试 + public static void main(String[] args) { + int N = 8; + int V = 10000; + int testTimes = 2000; + 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 = minAverageSum1(arr, k); + int ans2 = minAverageSum2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2023_03_3_week/Code03_MakeSumDivisibleByP.java b/算法周更班/class_2023_03_3_week/Code03_MakeSumDivisibleByP.java new file mode 100644 index 0000000..f4f1813 --- /dev/null +++ b/算法周更班/class_2023_03_3_week/Code03_MakeSumDivisibleByP.java @@ -0,0 +1,47 @@ +package class_2023_03_3_week; + +import java.util.HashMap; + +// 给你一个正整数数组 nums,请你移除 最短 子数组(可以为 空) +// 使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。 +// 请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。 +// 子数组 定义为原数组中连续的一组元素。 +// 测试链接 : https://leetcode.cn/problems/make-sum-divisible-by-p/ +public class Code03_MakeSumDivisibleByP { + + public int minSubarray(int[] nums, int p) { + int n = nums.length; + // 求出整体的余数 + int allMod = 0; + for (int num : nums) { + allMod = (allMod + num) % p; + } + if (allMod == 0) { + return 0; + } + // 记录前缀和的某个余数,最晚出现的位置 + // 看课!然后看接下来的代码 + HashMap map = new HashMap<>(); + map.put(0, -1); + int ans = Integer.MAX_VALUE; + int curMod = 0, find; + for (int i = 0; i < n; i++) { + // 0...i 累加和的余数 + curMod = (curMod + nums[i]) % p; + // 如果p = 7,整体余数2,当前余数5,那么找之前的部分余数是3 + // 如果p = 7,整体余数2,当前余数1,那么找之前的部分余数是6 + // 整体变成下面的公式,可以自己带入各种情况验证 + find = (curMod - allMod + p) % p; + if (map.containsKey(find)) { + if (i != n - 1 || map.get(find) != -1) { + // 防止删掉整体! + // ...i(n-1) + ans = Math.min(ans, i - map.get(find)); + } + } + map.put(curMod, i); + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + +} diff --git a/算法周更班/class_2023_03_3_week/Code04_ParsingBooleanExpression.java b/算法周更班/class_2023_03_3_week/Code04_ParsingBooleanExpression.java new file mode 100644 index 0000000..6c26ca6 --- /dev/null +++ b/算法周更班/class_2023_03_3_week/Code04_ParsingBooleanExpression.java @@ -0,0 +1,145 @@ +package class_2023_03_3_week; + +// 布尔表达式 是计算结果不是 true 就是 false 的表达式 +// 有效的表达式需遵循以下约定: +// 't',运算结果为 true +// 'f',运算结果为 false +// '!(subExpr)',运算过程为对内部表达式 subExpr 进行 逻辑非(NOT)运算 +// '&(subExpr1, subExpr2, ..., subExprn)' +// 运算过程为对 2 个或以上内部表达式 +// subExpr1, subExpr2, ..., subExprn 进行 逻辑与(AND)运算 +// '|(subExpr1, subExpr2, ..., subExprn)' +// 运算过程为对 2 个或以上内部表达式 +// subExpr1, subExpr2, ..., subExprn 进行 逻辑或(OR)运算 +// 给你一个以字符串形式表述的 布尔表达式 expression,返回该式的运算结果。 +// 题目测试用例所给出的表达式均为有效的布尔表达式,遵循上述约定。 +// 测试链接 : https://leetcode.cn/problems/parsing-a-boolean-expression/ +public class Code04_ParsingBooleanExpression { + + public static boolean parseBoolExpr(String expression) { + return f(expression.toCharArray(), 0).ans; + } + + public static class Info { + public boolean ans; + // 结束下标! + public int end; + + public Info(boolean a, int e) { + ans = a; + end = e; + } + } + + // 核心递归 + public static Info f(char[] exp, int index) { + char judge = exp[index]; + if (judge == 'f') { + return new Info(false, index); + } else if (judge == 't') { + return new Info(true, index); + } else { + // ! + // & + // | + // 再说! + boolean ans; + // ! ( ? + // i i+1 i+2 + // & ( ? + // i i+1 i+2 + // | ( ? + // i i+1 i+2 + index += 2; + if (judge == '!') { + // ! ( ?...... ) + // i i+1 i+2 + Info next = f(exp, index); + ans = !next.ans; + index = next.end + 1; + } else { + // & + // i + // judge == '&' 或者 judge == '|' + ans = judge == '&'; + while (exp[index] != ')') { + if (exp[index] == ',') { + index++; + } else { + Info next = f(exp, index); + if (judge == '&') { + if (!next.ans) { + ans = false; + } + } else { + if (next.ans) { + ans = true; + } + } + index = next.end + 1; + } + } + } + return new Info(ans, index); + } + } + +// public static Info f(char[] exp, int index) { +// char judge = exp[index]; +// if (judge == 'f') { +// return new Info(false, index); +// } else if (judge == 't') { +// return new Info(true, index); +// } else { +// // ! +// // & +// // | +// // 再说! +// boolean ans; +// // ! ( ? +// // i i+1 i+2 +// // & ( ? +// // i i+1 i+2 +// // | ( ? +// // i i+1 i+2 +// index += 2; +// if (judge == '!') { +// // ! ( ?...... ) +// // i i+1 i+2 +// Info next = f(exp, index); +// ans = !next.ans; +// index = next.end + 1; +// } else if (judge == '&') { +// // & ( ?... , ?.... , ?.... ) +// // i i+1 index +// ans = true; +// while (exp[index] != ')') { +// if (exp[index] == ',') { +// index++; +// } else { +// Info next = f(exp, index); +// if (!next.ans) { +// ans = false; +// } +// index = next.end + 1; +// } +// } +// } else { +// ans = false; +// while (exp[index] != ')') { +// if (exp[index] == ',') { +// index++; +// } else { +// Info next = f(exp, index); +// if (next.ans) { +// ans = true; +// } +// index = next.end + 1; +// } +// } +// } +// return new Info(ans, index); +// } +// } + +} diff --git a/算法周更班/class_2023_03_3_week/Code05_QueryGreaterThanOrEqualTo3Stores.java b/算法周更班/class_2023_03_3_week/Code05_QueryGreaterThanOrEqualTo3Stores.java new file mode 100644 index 0000000..e90f1c5 --- /dev/null +++ b/算法周更班/class_2023_03_3_week/Code05_QueryGreaterThanOrEqualTo3Stores.java @@ -0,0 +1,298 @@ +package class_2023_03_3_week; + +import java.util.Arrays; + +// 来自学员问题,大厂笔试面经帖子 +// 假设一共有M个车库,编号1~M,时间点从早到晚是从1~T +// 一共有N个记录,每一条记录如下{a, b, c} +// 表示一辆车在b时间点进入a车库,在c时间点从a车库出去 +// 一共有K个查询,每个查询只有一个数字X,表示请问在X时刻, +// 有多少个车库包含车的数量>=3,请返回K个查询的答案 +// 1 <= M, N, K <= 10^5 +// 1 <= T <= 10^9 +public class Code05_QueryGreaterThanOrEqualTo3Stores { + + // 暴力方法 + // 为了验证 + public static int[] getAns1(int m, int[][] records, int[] queries) { + int maxT = 0; + for (int[] r : records) { + maxT = Math.max(maxT, Math.max(r[1], r[2])); + } + for (int t : queries) { + maxT = Math.max(maxT, t); + } + int[][] stores = new int[m + 1][maxT + 1]; + for (int[] record : records) { + int s = record[0]; + int l = record[1]; + int r = record[2] - 1; + for (int i = l; i <= r; i++) { + stores[s][i]++; + } + } + int k = queries.length; + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + int curAns = 0; + for (int j = 1; j <= m; j++) { + if (stores[j][queries[i]] >= 3) { + curAns++; + } + } + ans[i] = curAns; + } + return ans; + } + + // 正式方法 + // O((N + K)*log(N + K)) + public static int[] getAns2(int m, int[][] records, int[] queries) { + int n = records.length; + int k = queries.length; + // n*2 + k + int tn = (n << 1) + k; + int[] times = new int[tn + 1]; + int ti = 1; + for (int[] record : records) { + times[ti++] = record[1]; + times[ti++] = record[2] - 1; + } + for (int query : queries) { + times[ti++] = query; + } + Arrays.sort(times); + for (int[] record : records) { + record[1] = rank(times, record[1]); + record[2] = rank(times, record[2] - 1); + } + for (int i = 0; i < k; i++) { + queries[i] = rank(times, queries[i]); + } + // 所有记录,根据车库编号排序,相同车库的记录就在一起了 + // record 车库 入库时间 出库时间-1 + // 0 1 2 + Arrays.sort(records, (a, b) -> a[0] - b[0]); + SegmentTree st = new SegmentTree(tn); + for (int l = 0; l < n;) { + int r = l; + while (r < n && records[l][0] == records[r][0]) { + r++; + } + // 同一个车库的记录,records[l.....r-1] l...... l....... + countRange(records, l, r - 1, st); + l = r; + } + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + ans[i] = st.query(queries[i]); + } + return ans; + } + + // records[l.....r] + // 上面的记录都属于当前的车库 + // 找到哪些时间段,车的数量是 >= 3的 + // 改写线段树! + public static void countRange(int[][] records, int l, int r, SegmentTree st) { + int n = r - l + 1; + int[][] help = new int[n << 1][2]; + int size = 0; + for (int i = l; i <= r; i++) { + if (records[i][1] <= records[i][2]) { + help[size][0] = records[i][1]; + help[size++][1] = 1; + help[size][0] = records[i][2]; + help[size++][1] = -1; + } + } + Arrays.sort(help, 0, size, (a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (b[1] - a[1])); + int count = 0; + int start = -1; + for (int i = 0; i < size; i++) { + int point = help[i][0]; + int status = help[i][1]; + if (status == 1) { + if (++count >= 3) { + start = start == -1 ? point : start; + } + } else { + if (start != -1 && start <= point) { + st.add(start, point); + } + if (--count >= 3) { + start = point + 1; + } else { + start = -1; + } + } + } + } + + // 所有的时间点,在sorted数组中排好序了 + // 给我一个其中的时间点,v + // 返回,离散化之后的编号! + public static int rank(int[] sorted, int v) { + int l = 1; + int r = sorted.length; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (sorted[m] >= v) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + // 线段树 + // 维持任何时刻存车数量>=3的车库有几个 + // 支持范围时刻的增加 + // 支持单点时刻的查询 + public static class SegmentTree { + private int tn; + private int[] sum; + private int[] lazy; + + public SegmentTree(int n) { + tn = n; + sum = new int[(tn + 1) << 2]; + lazy = new int[(tn + 1) << 2]; + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + // l...r范围上每个时刻对应的数值+1 + public void add(int l, int r) { + add(l, r, 1, tn, 1); + } + + private void add(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + sum[rt] += r - l + 1; + lazy[rt] += 1; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + // 查询单点时刻存车数量>=3的车库有几个 + public int query(int index) { + return query(index, 1, tn, 1); + } + + private int query(int index, int l, int r, int rt) { + if (l == r) { + return sum[rt]; + } + int m = (l + r) >> 1; + pushDown(rt, m - l + 1, r - m); + int ans = 0; + if (index <= m) { + ans = query(index, l, m, rt << 1); + } else { + ans = query(index, m + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + // 为了测试 + public static int[][] randomRecords(int n, int m, int t) { + int[][] records = new int[n][3]; + for (int i = 0; i < n; i++) { + records[i][0] = (int) (Math.random() * m) + 1; + int a = (int) (Math.random() * t) + 1; + int b = (int) (Math.random() * t) + 1; + records[i][1] = Math.min(a, b); + records[i][2] = Math.max(a, b); + } + return records; + } + + // 为了测试 + public static int[] randomQueries(int k, int t) { + int[] queries = new int[k]; + for (int i = 0; i < k; i++) { + queries[i] = (int) (Math.random() * t) + 1; + } + return queries; + } + + // 为了测试 + public static boolean same(int[] ans1, int[] ans2) { + for (int i = 0; i < ans1.length; i++) { + if (ans1[i] != ans2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int M = 20; + int N = 300; + int K = 500; + int T = 5000; + int testTimes = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int m = (int) (Math.random() * M) + 1; + int n = (int) (Math.random() * N) + 1; + int k = (int) (Math.random() * K) + 1; + int t = (int) (Math.random() * T) + 1; + int[][] records = randomRecords(n, m, t); + int[] queries = randomQueries(k, t); + int[] ans1 = getAns1(m, records, queries); + int[] ans2 = getAns2(m, records, queries); + if (!same(ans1, ans2)) { + System.out.println("出错了!"); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int m = 100000; + int n = 100000; + int k = 100000; + int t = 1000000000; + int[][] records = randomRecords(n, m, t); + int[] queries = randomQueries(k, t); + System.out.println("车库规模 : " + m); + System.out.println("记录规模 : " + n); + System.out.println("查询条数 : " + k); + System.out.println("时间范围 : " + t); + long start = System.currentTimeMillis(); + getAns2(m, records, queries); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + } +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 3b996b4..70c09f0 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -3208,6 +3208,60 @@ R(e_1 + e_2) = {a + b for (a, b) in R(e_1) × R(e_2)} +第062节 2023年3月第3周流行算法题目解析 + +给你一个 非递减 的正整数数组 nums 和整数 K +判断该数组是否可以被分成一个或几个 长度至少 为 K 的 不相交的递增子序列 +测试链接 : https://leetcode.cn/problems/divide-array-into-increasing-sequences/ + +来自学员问题 +真实大厂笔试题 +给定一个数组arr,长度为n +再给定一个数字k,表示一定要将arr划分成k个集合 +每个数字只能进一个集合 +返回每个集合内部的平均值都累加起来最小的值 +平均值向下取整 +1 <= n <= 10^5 +0 <= arr[i] <= 10^5 +1 <= k <= n + +给你一个正整数数组 nums,请你移除 最短 子数组(可以为 空) +使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。 +请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。 +子数组 定义为原数组中连续的一组元素。 +测试链接 : https://leetcode.cn/problems/make-sum-divisible-by-p/ + +布尔表达式 是计算结果不是 true 就是 false 的表达式 +有效的表达式需遵循以下约定: +'t',运算结果为 true +'f',运算结果为 false +'!(subExpr)',运算过程为对内部表达式 subExpr 进行 逻辑非(NOT)运算 +'&(subExpr1, subExpr2, ..., subExprn)' +运算过程为对 2 个或以上内部表达式 +subExpr1, subExpr2, ..., subExprn 进行 逻辑与(AND)运算 +'|(subExpr1, subExpr2, ..., subExprn)' +运算过程为对 2 个或以上内部表达式 +subExpr1, subExpr2, ..., subExprn 进行 逻辑或(OR)运算 +给你一个以字符串形式表述的 布尔表达式 expression,返回该式的运算结果。 +题目测试用例所给出的表达式均为有效的布尔表达式,遵循上述约定。 +测试链接 : https://leetcode.cn/problems/parsing-a-boolean-expression/ + +来自学员问题,大厂笔试面经帖子 +假设一共有M个车库,编号1~M,时间点从早到晚是从1~T +一共有N个记录,每一条记录如下{a, b, c} +表示一辆车在b时间点进入a车库,在c时间点从a车库出去 +一共有K个查询,每个查询只有一个数字X,表示请问在X时刻, +有多少个车库包含车的数量>=3,请返回K个查询的答案 +1 <= M, N, K <= 10^5 +1 <= T <= 10^9 + + + + + + + +