🌟欢迎来到 我的博客 —— 探索技术的无限可能!
22.引爆最多的炸弹
题目链接:2101. 引爆最多的炸弹
给你一个炸弹列表。一个炸弹的 爆炸范围 定义为以炸弹为圆心的一个圆。
炸弹用一个下标从 0 开始的二维整数数组 bombs 表示,其中 bombs[i] = [xi, yi, ri] 。xi 和 yi 表示第 i 个炸弹的 X 和 Y 坐标,ri 表示爆炸范围的 半径 。
你需要选择引爆 一个 炸弹。当这个炸弹被引爆时,所有 在它爆炸范围内的炸弹都会被引爆,这些炸弹会进一步将它们爆炸范围内的其他炸弹引爆。
给你数组 bombs ,请你返回在引爆 一个 炸弹的前提下,最多 能引爆的炸弹数目。
示例 1:
输入:bombs = [[2,1,3],[6,1,4]]
输出:2
解释:
上图展示了 2 个炸弹的位置和爆炸范围。
如果我们引爆左边的炸弹,右边的炸弹不会被影响。
但如果我们引爆右边的炸弹,两个炸弹都会爆炸。
所以最多能引爆的炸弹数目是 max(1, 2) = 2 。
示例 2:
输入:bombs = [[1,1,5],[10,10,5]]
输出:1
解释:
引爆任意一个炸弹都不会引爆另一个炸弹。所以最多能引爆的炸弹数目为 1 。
示例 3:
输入:bombs = [[1,2,3],[2,3,1],[3,4,2],[4,5,3],[5,6,4]]
输出:5
解释:
最佳引爆炸弹为炸弹 0 ,因为:
- 炸弹 0 引爆炸弹 1 和 2 。红色圆表示炸弹 0 的爆炸范围。
- 炸弹 2 引爆炸弹 3 。蓝色圆表示炸弹 2 的爆炸范围。
- 炸弹 3 引爆炸弹 4 。绿色圆表示炸弹 3 的爆炸范围。
所以总共有 5 个炸弹被引爆。
提示:
1 <= bombs.length <= 100
bombs[i].length == 3
1 <= xi, yi, ri <= 105
题解:
方法:DFS
class Solution { public int maximumDetonation(int[][] bombs) { int n = bombs.length; List<Integer>[] g = new ArrayList[n]; Arrays.setAll(g, i -> new ArrayList<>()); for (int i = 0; i < n; i++) { long x = bombs[i][0]; long y = bombs[i][1]; long r = bombs[i][2]; for (int j = 0; j < n; j++) { long dx = x - bombs[j][0]; long dy = y - bombs[j][1]; if (j != i && dx * dx + dy * dy <= r * r) { g[i].add(j); // i 可以引爆 j } } } int ans = 0; boolean[] vis = new boolean[n]; for (int i = 0; i < n && ans < n; i++) { Arrays.fill(vis, false); ans = Math.max(ans, dfs(g, vis, i)); } return ans; } private int dfs(List<Integer>[] g, boolean[] vis, int x) { vis[x] = true; int cnt = 1; for (int y : g[x]) { if (!vis[y]) { cnt += dfs(g, vis, y); } } return cnt; } }
23.求出所有子序列的能量和
题目链接:3098. 求出所有子序列的能量和
给你一个长度为 n 的整数数组 nums 和一个 正 整数 k 。
一个
子序列
的 能量 定义为子序列中 任意 两个元素的差值绝对值的 最小值 。
请你返回 nums 中长度 等于 k 的 所有 子序列的 能量和 。
由于答案可能会很大,将答案对 109 + 7 取余 后返回。
示例 1:
输入:nums = [1,2,3,4], k = 3
输出:4
解释:
nums 中总共有 4 个长度为 3 的子序列:[1,2,3] ,[1,3,4] ,[1,2,4] 和 [2,3,4] 。能量和为 |2 -
3| + |3 - 4| + |2 - 1| + |3 - 4| = 4 。
示例 2:
输入:nums = [2,2], k = 2
输出:0
解释:
nums 中唯一一个长度为 2 的子序列是 [2,2] 。能量和为 |2 - 2| = 0 。
示例 3:
输入:nums = [4,3,-1], k = 2
输出:10
解释:
nums 总共有 3 个长度为 2 的子序列:[4,3] ,[4,-1] 和 [3,-1] 。能量和为 |4 - 3| + |4 -
(-1)| + |3 - (-1)| = 10 。
提示:
2 <= n == nums.length <= 50
-108 <= nums[i] <= 108
2 <= k <= n
题解:
方法:记忆化搜索
class Solution { private Map<Long, Integer> f = new HashMap<>(); private final int mod = (int) 1e9 + 7; private int[] nums; public int sumOfPowers(int[] nums, int k) { Arrays.sort(nums); this.nums = nums; return dfs(0, nums.length, k, Integer.MAX_VALUE); } private int dfs(int i, int j, int k, int mi) { if (i >= nums.length) { return k == 0 ? mi : 0; } if (nums.length - i < k) { return 0; } long key = (1L * mi) << 18 | (i << 12) | (j << 6) | k; if (f.containsKey(key)) { return f.get(key); } int ans = dfs(i + 1, j, k, mi); if (j == nums.length) { ans += dfs(i + 1, i, k - 1, mi); } else { ans += dfs(i + 1, i, k - 1, Math.min(mi, nums[i] - nums[j])); } ans %= mod; f.put(key, ans); return ans; } }
24.重新放置石块
题目链接:2766. 重新放置石块
给你一个下标从 0 开始的整数数组 nums ,表示一些石块的初始位置。再给你两个长度 相等 下标从 0 开始的整数数组 moveFrom 和 moveTo 。
在 moveFrom.length 次操作内,你可以改变石块的位置。在第 i 次操作中,你将位置在 moveFrom[i] 的所有石块移到位置 moveTo[i] 。
完成这些操作后,请你按升序返回所有 有 石块的位置。
注意:
如果一个位置至少有一个石块,我们称这个位置 有 石块。
一个位置可能会有多个石块。
示例 1:
输入:nums = [1,6,7,8], moveFrom = [1,7,2], moveTo = [2,9,5]
输出:[5,6,8,9]
解释:一开始,石块在位置 1,6,7,8 。
第 i = 0 步操作中,我们将位置 1 处的石块移到位置 2 处,位置 2,6,7,8 有石块。
第 i = 1 步操作中,我们将位置 7 处的石块移到位置 9 处,位置 2,6,8,9 有石块。
第 i = 2 步操作中,我们将位置 2 处的石块移到位置 5 处,位置 5,6,8,9 有石块。
最后,至少有一个石块的位置为 [5,6,8,9] 。
示例 2:
输入:nums = [1,1,3,3], moveFrom = [1,3], moveTo = [2,2]
输出:[2]
解释:一开始,石块在位置 [1,1,3,3] 。
第 i = 0 步操作中,我们将位置 1 处的石块移到位置 2 处,有石块的位置为 [2,2,3,3] 。
第 i = 1 步操作中,我们将位置 3 处的石块移到位置 2 处,有石块的位置为 [2,2,2,2] 。
由于 2 是唯一有石块的位置,我们返回 [2] 。
提示:
1 <= nums.length <= 105
1 <= moveFrom.length <= 105
moveFrom.length == moveTo.length
1 <= nums[i], moveFrom[i], moveTo[i] <= 109
测试数据保证在进行第 i 步操作时,moveFrom[i] 处至少有一个石块。
题解:
方法:哈希表
class Solution { public List<Integer> relocateMarbles(int[] nums, int[] moveFrom, int[] moveTo) { Set<Integer> pos = new HashSet<>(); for (int x : nums) { pos.add(x); } for (int i = 0; i < moveFrom.length; ++i) { pos.remove(moveFrom[i]); pos.add(moveTo[i]); } List<Integer> ans = new ArrayList<>(pos); ans.sort((a, b) -> a - b); return ans; } }
25.生成特殊数字的最少操作
题目链接:2844. 生成特殊数字的最少操作
给你一个下标从 0 开始的字符串 num ,表示一个非负整数。
在一次操作中,您可以选择 num 的任意一位数字并将其删除。请注意,如果你删除 num 中的所有数字,则 num 变为 0。
返回最少需要多少次操作可以使 num 变成特殊数字。
如果整数 x 能被 25 整除,则该整数 x 被认为是特殊数字。
示例 1:
输入:num = “2245047”
输出:2
解释:删除数字 num[5] 和 num[6] ,得到数字 “22450” ,可以被 25 整除。
可以证明要使数字变成特殊数字,最少需要删除 2 位数字。
示例 2:
输入:num = “2908305”
输出:3
解释:删除 num[3]、num[4] 和 num[6] ,得到数字 “2900” ,可以被 25 整除。
可以证明要使数字变成特殊数字,最少需要删除 3 位数字。
示例 3:
输入:num = “10”
输出:1
解释:删除 num[0] ,得到数字 “0” ,可以被 25 整除。
可以证明要使数字变成特殊数字,最少需要删除 1 位数字。
提示
1 <= num.length <= 100
num 仅由数字 ‘0’ 到 ‘9’ 组成
num 不含任何前导零
题解:
方法:记忆化搜索
class Solution { private Integer[][] f; private String num; private int n; public int minimumOperations(String num) { n = num.length(); this.num = num; f = new Integer[n][25]; return dfs(0, 0); } private int dfs(int i, int k) { if (i == n) { return k == 0 ? 0 : n; } if (f[i][k] != null) { return f[i][k]; } f[i][k] = dfs(i + 1, k) + 1; f[i][k] = Math.min(f[i][k], dfs(i + 1, (k * 10 + num.charAt(i) - '0') % 25)); return f[i][k]; } }
26.找出分区值
题目链接:2740. 找出分区值
给你一个 正 整数数组 nums 。
将 nums 分成两个数组:nums1 和 nums2 ,并满足下述条件:
数组 nums 中的每个元素都属于数组 nums1 或数组 nums2 。
两个数组都 非空 。
分区值 最小 。
分区值的计算方法是 |max(nums1) - min(nums2)| 。
其中,max(nums1) 表示数组 nums1 中的最大元素,min(nums2) 表示数组 nums2 中的最小元素。
返回表示分区值的整数。
示例 1:
输入:nums = [1,3,2,4]
输出:1
解释:可以将数组 nums 分成 nums1 = [1,2] 和 nums2 = [3,4] 。
- 数组 nums1 的最大值等于 2 。
- 数组 nums2 的最小值等于 3 。
分区值等于 |2 - 3| = 1 。
可以证明 1 是所有分区方案的最小值。
示例 2:
输入:nums = [100,1,10]
输出:9
解释:可以将数组 nums 分成 nums1 = [10] 和 nums2 = [100,1] 。
- 数组 nums1 的最大值等于 10 。
- 数组 nums2 的最小值等于 1 。
分区值等于 |10 - 1| = 9 。
可以证明 9 是所有分区方案的最小值。
提示:
2 <= nums.length <= 105
1 <= nums[i] <= 109
题解:
方法:贪心
class Solution { public int findValueOfPartition(int[] nums) { Arrays.sort(nums); int ans = Integer.MAX_VALUE; for (int i = 1; i < nums.length; i++) { ans = Math.min(ans, nums[i] - nums[i - 1]); } return ans; } }
27.满足距离约束且字典序最小的字符串
给你一个字符串 s 和一个整数 k 。
定义函数 distance(s1, s2) ,用于衡量两个长度为 n 的字符串 s1 和 s2 之间的距离,即:
字符 ‘a’ 到 ‘z’ 按 循环 顺序排列,对于区间 [0, n - 1] 中的 i ,计算所有「 s1[i] 和 s2[i] 之间 最小距离」的 和 。
例如,distance(“ab”, “cd”) == 4 ,且 distance(“a”, “z”) == 1 。
你可以对字符串 s 执行 任意次 操作。在每次操作中,可以将 s 中的一个字母 改变 为 任意 其他小写英文字母。
返回一个字符串,表示在执行一些操作后你可以得到的 字典序最小 的字符串 t ,且满足 distance(s, t) <= k 。
示例 1:
输入:s = “zbbz”, k = 3
输出:“aaaz”
解释:在这个例子中,可以执行以下操作:
将 s[0] 改为 ‘a’ ,s 变为 “abbz” 。
将 s[1] 改为 ‘a’ ,s 变为 “aabz” 。
将 s[2] 改为 ‘a’ ,s 变为 “aaaz” 。
“zbbz” 和 “aaaz” 之间的距离等于 k = 3 。
可以证明 “aaaz” 是在任意次操作后能够得到的字典序最小的字符串。
因此,答案是 “aaaz” 。
示例 2:
输入:s = “xaxcd”, k = 4
输出:“aawcd”
解释:在这个例子中,可以执行以下操作:
将 s[0] 改为 ‘a’ ,s 变为 “aaxcd” 。
将 s[2] 改为 ‘w’ ,s 变为 “aawcd” 。
“xaxcd” 和 “aawcd” 之间的距离等于 k = 4 。
可以证明 “aawcd” 是在任意次操作后能够得到的字典序最小的字符串。
因此,答案是 “aawcd” 。
示例 3:
输入:s = “lol”, k = 0
输出:“lol”
解释:在这个例子中,k = 0,更改任何字符都会使得距离大于 0 。
因此,答案是 “lol” 。
提示:
1 <= s.length <= 100
0 <= k <= 2000
s 只包含小写英文字母。
题解:
方法:贪心
class Solution { public String getSmallestString(String s, int k) { char[] t = s.toCharArray(); for (int i = 0; i < t.length; i++) { int dis = Math.min(t[i] - 'a', 'z' - t[i] + 1); if (dis > k) { t[i] -= k; break; } t[i] = 'a'; k -= dis; } return new String(t); } }
28.掉落的方块
题目链接:699. 掉落的方块
在二维平面上的 x 轴上,放置着一些方块。
给你一个二维整数数组 positions ,其中 positions[i] = [lefti, sideLengthi] 表示:第 i 个方块边长为 sideLengthi ,其左侧边与 x 轴上坐标点 lefti 对齐。
每个方块都从一个比目前所有的落地方块更高的高度掉落而下。方块沿 y 轴负方向下落,直到着陆到 另一个正方形的顶边 或者是 x 轴上 。一个方块仅仅是擦过另一个方块的左侧边或右侧边不算着陆。一旦着陆,它就会固定在原地,无法移动。
在每个方块掉落后,你必须记录目前所有已经落稳的 方块堆叠的最高高度 。
返回一个整数数组 ans ,其中 ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。
示例 1:
输入:positions = [[1,2],[2,3],[6,1]]
输出:[2,5,5]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 2 。
第 2 个方块掉落后,最高的堆叠由方块 1 和 2 组成,堆叠的最高高度为 5 。
第 3 个方块掉落后,最高的堆叠仍然由方块 1 和 2 组成,堆叠的最高高度为 5 。
因此,返回 [2, 5, 5] 作为答案。
示例 2:
输入:positions = [[100,100],[200,100]]
输出:[100,100]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 100 。
第 2 个方块掉落后,最高的堆叠可以由方块 1 组成也可以由方块 2 组成,堆叠的最高高度为 100 。 因此,返回 [100, 100]
作为答案。
注意,方块 2 擦过方块 1 的右侧边,但不会算作在方块 1 上着陆。
提示:
1 <= positions.length <= 1000
1 <= lefti <= 108
1 <= sideLengthi <= 106
题解:
方法:线段树
class Node { Node left; Node right; int l; int r; int mid; int v; int add; public Node(int l, int r) { this.l = l; this.r = r; this.mid = (l + r) >> 1; } } class SegmentTree { private Node root = new Node(1, (int) 1e9); public SegmentTree() { } public void modify(int l, int r, int v) { modify(l, r, v, root); } public void modify(int l, int r, int v, Node node) { if (l > r) { return; } if (node.l >= l && node.r <= r) { node.v = v; node.add = v; return; } pushdown(node); if (l <= node.mid) { modify(l, r, v, node.left); } if (r > node.mid) { modify(l, r, v, node.right); } pushup(node); } public int query(int l, int r) { return query(l, r, root); } public int query(int l, int r, Node node) { if (l > r) { return 0; } if (node.l >= l && node.r <= r) { return node.v; } pushdown(node); int v = 0; if (l <= node.mid) { v = Math.max(v, query(l, r, node.left)); } if (r > node.mid) { v = Math.max(v, query(l, r, node.right)); } return v; } public void pushup(Node node) { node.v = Math.max(node.left.v, node.right.v); } public void pushdown(Node node) { if (node.left == null) { node.left = new Node(node.l, node.mid); } if (node.right == null) { node.right = new Node(node.mid + 1, node.r); } if (node.add != 0) { Node left = node.left, right = node.right; left.add = node.add; right.add = node.add; left.v = node.add; right.v = node.add; node.add = 0; } } } class Solution { public List<Integer> fallingSquares(int[][] positions) { List<Integer> ans = new ArrayList<>(); SegmentTree tree = new SegmentTree(); int mx = 0; for (int[] p : positions) { int l = p[0], w = p[1], r = l + w - 1; int h = tree.query(l, r) + w; mx = Math.max(mx, h); ans.add(mx); tree.modify(l, r, h); } return ans; } }