12-周赛338总结

简介: 12-周赛338总结

12-周赛338总结

前三题做的很快,第三题先超时了一次又因为数据WA了一次,看到第四题,果断放弃,然后恶补了一下拓扑排序

最近事情好多,加油!!

K 件物品的最大和【LC2600】

袋子中装有一些物品,每个物品上都标记着数字 10-1

给你四个非负整数 numOnesnumZerosnumNegOnesk

袋子最初包含:

  • numOnes 件标记为 1 的物品。
  • numZeroes 件标记为 0 的物品。
  • numNegOnes 件标记为 -1 的物品。

现计划从这些物品中恰好选出 k 件物品。返回所有可行方案中,物品上所标记数字之和的最大值。

  • 思路:贪心
    每次优先取数字较大的数字,取完了再取次大的,保证最终数字之和最大
  • 实现
class Solution {
    public int kItemsWithMaximumSum(int numOnes, int numZeros, int numNegOnes, int k) {
        if (k <= numOnes){
            return k;
        }else if (k > numOnes && k <= numOnes + numZeros){
            return numOnes;
        }else{
            return numOnes - (k - numOnes - numZeros);
        }
    }
}

image.png

质数减法运算【LC2601】

给你一个下标从 0 开始的整数数组 nums ,数组长度为 n

你可以执行无限次下述运算:

  • 选择一个之前未选过的下标 i ,并选择一个 严格小于nums[i] 的质数 p ,从 nums[i] 中减去 p

如果你能通过上述运算使得 nums 成为严格递增数组,则返回 true ;否则返回 false

严格递增数组 中的每个元素都严格大于其前面的元素

倒序遍历

  • 思路:贪心+枚举

image.png

class Solution {
    public boolean primeSubOperation(int[] nums) {
        int n = nums.length;
        for (int i = n - 2; i >= 0; i--){
            if (nums[i] >= nums[i + 1]){// nums[i]需要至少减小至 nums[i+1] - 1 nums[i] - (nums[i+1] - 1)
                boolean flag = false;
                for (int j = nums[i] - (nums[i+1] - 1); j < nums[i]; j++){
                    if (isPrime(j)){
                        nums[i] -= j;
                        flag = true;
                        break;
                    }
                }
                if (!flag) return false;
            }
        }
        return true;
    }
    public boolean isPrime(int num){
        if (num <= 1) return false;
        for (int i = 2; i < num; i++){
            if (num % i == 0){
                return false;
            }
        }
        return true;
    }
}

image.png

class Solution {
    private final static int MX = 1000;
    private final static int[] primes = new int[169];
    static {
        var np = new boolean[MX + 1];
        int pi = 1; // primes[0] = 0 避免二分越界
        for (int i = 2; i <= MX; ++i)
            if (!np[i]) {
                primes[pi++] = i; // 预处理质数列表
                for (int j = i; j <= MX / i; ++j)
                    np[i * j] = true;
            }
    }
    public boolean primeSubOperation(int[] nums) {
        int pre = 0;
        for (int x : nums) {
            if (x <= pre) return false;
            int j = lowerBound(primes, x - pre);
            pre = x - primes[j - 1];
        }
        return true;
    }
    // 见 https://www.bilibili.com/video/BV1AP41137w7/
    private int lowerBound(int[] nums, int target) {
        int left = -1, right = nums.length; // 开区间 (left, right)
        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;
    }
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/prime-subtraction-operation/solutions/2191560/jian-ji-xie-fa-shai-zhi-shu-er-fen-cha-z-wj7i/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

image.png

使数组元素全部相等的最少操作次数【LC2602】

给你一个正整数数组 nums

同时给你一个长度为 m 的整数数组 queries 。第 i 个查询中,你需要将 nums 中所有元素变成 queries[i] 。你可以执行以下操作 任意 次:

  • 将数组里一个元素 增大 或者 减小1

请你返回一个长度为 m 的数组 answer ,其中 answer[i]是将 nums 中所有元素变成 queries[i]最少 操作次数。

注意,每次查询后,数组变回最开始的值。

image.png

class Solution {
    public List<Long> minOperations(int[] nums, int[] queries) {
        List<Long> res = new ArrayList<>();
        Long sum = 0L;
        int n = nums.length;
        long[] preSum = new long[n + 1];
        Arrays.sort(nums);
        for (int i = 0; i < n; i++){
            preSum[i + 1] = preSum[i] + nums[i];
        }
        for (int q : queries){
            int index = binarySearch(nums, q);
            long low = preSum[index + 1];
            long high = preSum[n] - preSum[index + 1];
            long count1 = (long)q * (index + 1) - low;
            long count2 = high - (long)q * (n - index - 1);
            res.add(count1 + count2);
        }
        return res;
    }
    public int binarySearch(int[] nums, int target){
        int l = 0, r = nums.length - 1;
        while (l <= r){
            int mid = (l + r) >> 1;
            if (nums[mid] <= target){
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        return r;
    }
}

image.png

收集树中金币【LC2603】

给你一个 n 个节点的无向无根树,节点编号从 0n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点aibi 之间有一条边。再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 11 表示节点 i 处有一个金币。

一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:

  • 收集距离当前节点距离为 2 以内的所有金币,或者
  • 移动到树中一个相邻节点。


你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。

如果你多次经过一条边,每一次经过都会给答案加一。

思路:拓扑排序

  • 首先,我们可以去掉不包含金币的子树,因为访问其中任何一个点都毫无意义。
  • 如果所有在叶子上的金币全部都能收集到,那么我们可以收集到树上所有金币。因此可以去除两轮叶子,剩余的结点即为必须经过的结点【两轮拓扑排序】
  • 从距离叶子为2的节点处出发【局部最优】,收集树中所有的金币,并且回到出发节点时经过的边数最少【全局最优】。
  • 实现
  • 无向图,节点度为1时为叶子
class Solution {
    public int collectTheCoins(int[] coins, int[][] edges) {
        int n = coins.length;
        List<Integer> g[] = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        var deg = new int[n];
        for (var e : edges) {
            int x = e[0], y = e[1];
            g[x].add(y);
            g[y].add(x); // 建图
            ++deg[x];
            ++deg[y];
        }
        // 用拓扑排序「剪枝」:去掉没有金币的子树
        var q = new ArrayDeque<Integer>();
        for (int i = 0; i < n; ++i)
            if (deg[i] == 1 && coins[i] == 0) // 无金币叶子
                q.add(i);
        while (!q.isEmpty()) {
            int x = q.peek();
            q.pop();
            for (int y : g[x])
                if (--deg[y] == 1 && coins[y] == 0)
                    q.add(y);
        }
        // 再次拓扑排序
        for (int i = 0; i < n; ++i)
            if (deg[i] == 1 && coins[i] == 1) // 有金币叶子
                q.add(i);
        if (q.size() <= 1) return 0; // 至多一个有金币的叶子,直接收集
        var time = new int[n];
        while (!q.isEmpty()) {
            int x = q.peek();
            q.pop();
            for (int y : g[x])
                if (--deg[y] == 1) {
                    time[y] = time[x] + 1; // 记录入队时间
                    q.add(y);
                }
        }
        // 统计答案
        int ans = 0;
        for (var e : edges)
            if (time[e[0]] >= 2 && time[e[1]] >= 2)
                ans += 2;
        return ans;
    }
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/collect-coins-in-a-tree/solutions/2191371/tuo-bu-pai-xu-ji-lu-ru-dui-shi-jian-pyth-6uli/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

image.png

  • 升级:如果把题目中的 2 换成 0,1,2,3,⋯ ,n−1,你能把这些情况对应的答案全部算出来吗?
    遍历所有的边,如果两个节点的入队时间均大于q(最大距离),那么表示这条边必须要经过,结果+2


目录
相关文章
|
19天前
【周赛总结】周赛354
【周赛总结】周赛354
28 0
|
19天前
13-周赛347总结
13-周赛347总结
31 0
|
19天前
14-周赛348总结
14-周赛348总结
27 0
|
19天前
|
存储
10-周赛336总结
10-周赛336总结
42 0
|
19天前
9-周赛335总结
9-周赛335总结
32 0
|
19天前
【周赛总结】周赛356
【周赛总结】周赛356
35 0
|
19天前
|
算法 Windows
【周赛总结】周赛360
【周赛总结】周赛360
29 0
|
19天前
【周赛总结】18-周赛353
【周赛总结】18-周赛353
37 0
|
19天前
|
存储
7-周赛333总结
7-周赛333总结
36 0
|
19天前
|
存储 移动开发
6-周赛332总结
6-周赛332总结
34 0