🌟欢迎来到 我的博客 —— 探索技术的无限可能!
11.切棍子的最小成本
题目链接:1547. 切棍子的最小成本
有一根长度为 n 个单位的木棍,棍上从 0 到 n 标记了若干位置。例如,长度为 6 的棍子可以标记如下:
给你一个整数数组 cuts ,其中 cuts[i] 表示你需要将棍子切开的位置。
你可以按顺序完成切割,也可以根据需要更改切割的顺序。
每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是历次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根木棍的长度和就是切割前木棍的长度)。请参阅第一个示例以获得更直观的解释。
返回切棍子的 最小总成本 。
示例 1:
输入:n = 7, cuts = [1,3,4,5]
输出:16
解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示:
第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4
的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。 而将切割顺序重新排列为 [3, 5, 1, 4]
后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。
示例 2:
输入:n = 9, cuts = [5,6,1,4,2]
输出:22
解释:如果按给定的顺序切割,则总成本为 25 。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 =
22,是所有可能方案中成本最小的。
提示:
2 <= n <= 10^6
1 <= cuts.length <= min(n - 1, 100)
1 <= cuts[i] <= n - 1
cuts 数组中的所有整数都 互不相同
题解:
方法:记忆化搜索
class Solution { public int minCost(int n, int[] cuts) { Arrays.sort(cuts); int m = cuts.length + 2; int[] newCuts = new int[m]; System.arraycopy(cuts, 0, newCuts, 1, m - 2); newCuts[m - 1] = n; int[][] memo = new int[m][m]; return dfs(0, m - 1, newCuts, memo); } private int dfs(int i, int j, int[] cuts, int[][] memo) { if (i + 1 == j) { // 无需切割 return 0; } if (memo[i][j] > 0) { // 之前计算过 return memo[i][j]; } int res = Integer.MAX_VALUE; for (int k = i + 1; k < j; k++) { res = Math.min(res, dfs(i, k, cuts, memo) + dfs(k, j, cuts, memo)); } return memo[i][j] = res + cuts[j] - cuts[i]; } }
12.统计满足 K 约束的子字符串数量 I
给你一个 二进制 字符串 s 和一个整数 k。
如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:
字符串中 0 的数量最多为 k。
字符串中 1 的数量最多为 k。
返回一个整数,表示 s 的所有满足 k 约束 的
子字符串
的数量。
示例 1:
输入:s = “10101”, k = 1
输出:12
解释:
s 的所有子字符串中,除了 “1010”、“10101” 和 “0101” 外,其余子字符串都满足 k 约束。
示例 2:
输入:s = “1010101”, k = 2
输出:25
解释:
s 的所有子字符串中,除了长度大于 5 的子字符串外,其余子字符串都满足 k 约束。
示例 3:
输入:s = “11111”, k = 1
输出:15
解释:
s 的所有子字符串都满足 k 约束。
提示:
1 <= s.length <= 50
1 <= k <= s.length
s[i] 是 ‘0’ 或 ‘1’。
题解:
方法:滑动窗口
class Solution { public int countKConstraintSubstrings(String S, int k) { char[] s = S.toCharArray(); int ans = 0; int left = 0; int[] cnt = new int[2]; for (int i = 0; i < s.length; i++) { cnt[s[i] & 1]++; while (cnt[0] > k && cnt[1] > k) { cnt[s[left] & 1]--; left++; } ans += i - left + 1; } return ans; } }
13.统计满足 K 约束的子字符串数量 II
题目链接:3261. 统计满足 K 约束的子字符串数量 II
给你一个 二进制 字符串 s 和一个整数 k。
另给你一个二维整数数组 queries ,其中 queries[i] = [li, ri] 。
如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:
字符串中 0 的数量最多为 k。
字符串中 1 的数量最多为 k。
返回一个整数数组 answer ,其中 answer[i] 表示 s[li…ri] 中满足 k 约束 的
子字符串
的数量。
示例 1:
输入:s = “0001111”, k = 2, queries = [[0,6]]
输出:[26]
解释:
对于查询 [0, 6], s[0…6] = “0001111” 的所有子字符串中,除 s[0…5] = “000111” 和
s[0…6] = “0001111” 外,其余子字符串都满足 k 约束。
示例 2:
输入:s = “010101”, k = 1, queries = [[0,5],[1,4],[2,3]]
输出:[15,9,3]
解释:
s 的所有子字符串中,长度大于 3 的子字符串都不满足 k 约束。
提示:
1 <= s.length <= 105
s[i] 是 ‘0’ 或 ‘1’
1 <= k <= s.length
1 <= queries.length <= 105
queries[i] == [li, ri]
0 <= li <= ri < s.length
所有查询互不相同
题解:
方法:滑动窗口+前缀和+二分查找
class Solution { public long[] countKConstraintSubstrings(String S, int k, int[][] queries) { char[] s = S.toCharArray(); int n = s.length; int[] left = new int[n]; long[] sum = new long[n + 1]; int[] cnt = new int[2]; int l = 0; for (int i = 0; i < n; i++) { cnt[s[i] & 1]++; while (cnt[0] > k && cnt[1] > k) { cnt[s[l++] & 1]--; } left[i] = l; // 记录合法子串右端点 i 对应的最小左端点 l // 计算 i-left[i]+1 的前缀和 sum[i + 1] = sum[i] + i - l + 1; } long[] ans = new long[queries.length]; for (int i = 0; i < queries.length; i++) { int ql = queries[i][0]; int qr = queries[i][1]; // 如果区间内所有数都小于 ql,结果是 j=qr+1 int j = lowerBound(left, ql - 1, qr + 1, ql); ans[i] = sum[qr + 1] - sum[j] + (long) (j - ql + 1) * (j - ql) / 2; } return ans; } // 返回在开区间 (left, right) 中的最小的 j,满足 nums[j] >= target // 如果没有这样的数,返回 right // 原理见 https://www.bilibili.com/video/BV1AP41137w7/ private int lowerBound(int[] nums, int left, int right, int target) { while (left + 1 < right) { // 区间不为空 // 循环不变量: // nums[left] < target // nums[right] >= target int mid = left + (right - left) / 2; if (nums[mid] < target) { left = mid; // 范围缩小到 (mid, right) } else { right = mid; // 范围缩小到 (left, mid) } } return right; } }
方法:预处理
class Solution { public long[] countKConstraintSubstrings(String S, int k, int[][] queries) { char[] s = S.toCharArray(); int n = s.length; int[] left = new int[n]; long[] sum = new long[n + 1]; int[] cnt = new int[2]; int l = 0; for (int i = 0; i < n; i++) { cnt[s[i] & 1]++; while (cnt[0] > k && cnt[1] > k) { cnt[s[l++] & 1]--; } left[i] = l; sum[i + 1] = sum[i] + i - l + 1; } int[] right = new int[n]; l = 0; for (int i = 0; i < n; i++) { while (l < n && left[l] < i) { l++; } right[i] = l; } long[] ans = new long[queries.length]; for (int i = 0; i < queries.length; i++) { int ql = queries[i][0]; int qr = queries[i][1]; int j = Math.min(right[ql], qr + 1); ans[i] = sum[qr + 1] - sum[j] + (long) (j - ql + 1) * (j - ql) / 2; } return ans; } }
14.统计好节点的数目
题目链接:3249. 统计好节点的数目
现有一棵 无向 树,树中包含 n 个节点,按从 0 到 n - 1 标记。树的根节点是节点 0 。给你一个长度为 n - 1 的二维整数数组 edges,其中 edges[i] = [ai, bi] 表示树中节点 ai 与节点 bi 之间存在一条边。
如果一个节点的所有子节点为根的
子树
包含的节点数相同,则认为该节点是一个 好节点。
返回给定树中 好节点 的数量。
子树 指的是一个节点以及它所有后代节点构成的一棵树。
示例 1:
输入:edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]]
输出:7
说明:
树的所有节点都是好节点。
示例 2:
输入:edges = [[0,1],[1,2],[2,3],[3,4],[0,5],[1,6],[2,7],[3,8]]
输出:6
说明:
树中有 6 个好节点。上图中已将这些节点着色。
示例 3:
输入:edges =
[[0,1],[1,2],[1,3],[1,4],[0,5],[5,6],[6,7],[7,8],[0,9],[9,10],[9,12],[10,11]]
输出:12
解释:
除了节点 9 以外其他所有节点都是好节点。
提示:
2 <= n <= 105
edges.length == n - 1
edges[i].length == 2
0 <= ai, bi < n
输入确保 edges 总表示一棵有效的树。
题解:
方法:深度优先搜索
class Solution { public int countGoodNodes(int[][] edges) { int n = edges.length + 1; List<Integer>[] g = new ArrayList[n]; Arrays.setAll(g, i -> new ArrayList<>()); for (int[] e : edges) { int x = e[0]; int y = e[1]; g[x].add(y); g[y].add(x); } dfs(0, -1, g); return ans; } private int ans; private int dfs(int x, int fa, List<Integer>[] g) { int size = 1; int sz0 = 0; boolean ok = true; for (int y : g[x]) { if (y == fa) { continue; // 不能递归到父节点 } int sz = dfs(y, x, g); if (sz0 == 0) { sz0 = sz; // 记录第一个儿子子树的大小 } else if (sz != sz0) { // 存在大小不一样的儿子子树 ok = false; // 注意不能 break,其他子树 y 仍然要递归 } size += sz; } ans += ok ? 1 : 0; return size; } }
15.最少翻转次数使二进制矩阵回文 I
给你一个 m x n 的二进制矩阵 grid 。
如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。
你可以将 grid 中任意格子的值 翻转 ,也就是将格子里的值从 0 变成 1 ,或者从 1 变成 0 。
请你返回 最少 翻转次数,使得矩阵 要么 所有行是 回文的 ,要么所有列是 回文的 。
示例 1:
输入:grid = [[1,0,0],[0,0,0],[0,0,1]]
输出:2
解释:
将高亮的格子翻转,得到所有行都是回文的。
示例 2:
输入:grid = [[0,1],[0,1],[0,0]]
输出:1
解释:
将高亮的格子翻转,得到所有列都是回文的。
示例 3:
输入:grid = [[1],[0]]
输出:0
解释:
所有行已经是回文的。
提示:
m == grid.length
n == grid[i].length
1 <= m * n <= 2 * 105
0 <= grid[i][j] <= 1
题解:
方法:回文
class Solution { public int minFlips(int[][] grid) { int m = grid.length; int n = grid[0].length; int diffRow = 0; for (int[] row : grid) { for (int j = 0; j < n / 2; j++) { if (row[j] != row[n - 1 - j]) { diffRow++; } } } int diffCol = 0; for (int j = 0; j < n; j++) { for (int i = 0; i < m / 2; i++) { if (grid[i][j] != grid[m - 1 - i][j]) { diffCol++; } } } return Math.min(diffRow, diffCol); } }
16.最少翻转次数使二进制矩阵回文 II
给你一个 m x n 的二进制矩阵 grid 。
如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。
你可以将 grid 中任意格子的值 翻转 ,也就是将格子里的值从 0 变成 1 ,或者从 1 变成 0 。
请你返回 最少 翻转次数,使得矩阵中 所有 行和列都是 回文的 ,且矩阵中 1 的数目可以被 4 整除 。
示例 1:
输入:grid = [[1,0,0],[0,1,0],[0,0,1]]
输出:3
解释:
示例 2:
输入:grid = [[0,1],[0,1],[0,0]]
输出:2
解释:
示例 3:
输入:grid = [[1],[1]]
输出:2
解释:
提示:
m == grid.length
n == grid[i].length
1 <= m * n <= 2 * 105
0 <= grid[i][j] <= 1
题解:
方法:贪心
class Solution { public int minFlips(int[][] a) { int m = a.length; int n = a[0].length; int ans = 0; for (int i = 0; i < m / 2; i++) { for (int j = 0; j < n / 2; j++) { int cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j]; ans += Math.min(cnt1, 4 - cnt1); // 全为 1 或全为 0 } } if (m % 2 > 0 && n % 2 > 0) { // 正中间的数必须是 0 ans += a[m / 2][n / 2]; } int diff = 0; int cnt1 = 0; if (m % 2 > 0) { // 统计正中间这一排 for (int j = 0; j < n / 2; j++) { if (a[m / 2][j] != a[m / 2][n - 1 - j]) { diff++; } else { cnt1 += a[m / 2][j] * 2; } } } if (n % 2 > 0) { // 统计正中间这一列 for (int i = 0; i < m / 2; i++) { if (a[i][n / 2] != a[m - 1 - i][n / 2]) { diff++; } else { cnt1 += a[i][n / 2] * 2; } } } return ans + (diff > 0 ? diff : cnt1 % 4); } }
17.适龄的朋友
题目链接:825. 适龄的朋友
在社交媒体网站上有 n 个用户。给你一个整数数组 ages ,其中 ages[i] 是第 i 个用户的年龄。
如果下述任意一个条件为真,那么用户 x 将不会向用户 y(x != y)发送好友请求:
ages[y] <= 0.5 * ages[x] + 7
ages[y] > ages[x]
ages[y] > 100 && ages[x] < 100
否则,x 将会向 y 发送一条好友请求。
注意,如果 x 向 y 发送一条好友请求,y 不必也向 x 发送一条好友请求。另外,用户不会向自己发送好友请求。
返回在该社交媒体网站上产生的好友请求总数。
示例 1:
输入:ages = [16,16]
输出:2
解释:2 人互发好友请求。
示例 2:
输入:ages = [16,17,18]
输出:2
解释:产生的好友请求为 17 -> 16 ,18 -> 17 。
示例 3:
输入:ages = [20,30,100,110,120]
输出:3
解释:产生的好友请求为 110 -> 100 ,120 -> 110 ,120 -> 100 。
提示:
n == ages.length
1 <= n <= 2 * 104
1 <= ages[i] <= 120
题解:
方法:计数+滑动窗口
class Solution { public int numFriendRequests(int[] ages) { int[] cnt = new int[121]; for (int age : ages) { cnt[age]++; } int ans = 0; int ageY = 0; int cntWindow = 0; for (int ageX = 0; ageX < cnt.length; ageX++) { cntWindow += cnt[ageX]; if (ageY * 2 <= ageX + 14) { // 不能发送好友请求 cntWindow -= cnt[ageY]; ageY++; } if (cntWindow > 0) { // 存在可以发送好友请求的用户 ans += cnt[ageX] * cntWindow - cnt[ageX]; } } return ans; } }