【题解】—— LeetCode一周小结30

🌟欢迎来到 我的博客 —— 探索技术的无限可能!


🌟博客的简介(文章目录)


【题解】—— 每日一道题目栏


上接:【题解】—— LeetCode一周小结29

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.满足距离约束且字典序最小的字符串

题目链接:3106. 满足距离约束且字典序最小的字符串

给你一个字符串 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;
    }
}

下接:【题解】—— LeetCode一周小结31


  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZShiJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值