【leetcode学习笔记】周赛338

K件物品的最大和

https://leetcode.cn/problems/k-items-with-the-maximum-sum/

解析:这是简单的分类讨论问题,分情况讨论即可:

  • 如果,那么答案为k

  • 如果,那么答案为nums1

  • 如果,那么答案为nums1 - (k - nums1 - nums0)

代码:

class Solution {
    public int kItemsWithMaximumSum(int numOnes, int numZeros, int numNegOnes, int k) {
        return (k <= numOnes) ? k : ((k <= numOnes + numZeros) ? numOnes : 2 * numOnes + numZeros - k);
    }
}

质数减法运算

https://leetcode.cn/problems/prime-subtraction-operation/

解析:考虑贪心方法,从后往前逐个元素实现严格递增,并确保每一个元素如有需要,尽可能减掉更小的质数。可以证明,“是否存在解”当且仅当“以上贪心方法是否可构造出解”。

从右到左遍历。当遍历到第k个元素时,nums[k+1: n]已是严格单调递增状态。如果,说明该元素需要执行减操作,且至少需要减掉,于是我们找到比大的第一个质数,如果该质数大于等于nums[k],说明无解。否则执行in-place的减操作后,继续向左遍历。

如果整个数组遍历完均未触发“无解”条件,那么就返回“有解”。

于是,该问题转化为“如何以最低的代价找到比k大的最小质数”?因为题目限定了数字范围为1000以内,所以我们提前生成1000以内的所有质数供二分查找即可。方法见https://zhuanlan.zhihu.com/p/100051075

代码:

class Solution {
    private final static int UPPER = 1000;
    private final static int[] primes = new int[168];

    // 线性筛实现
    static {
        boolean[] is_prime = new boolean[UPPER + 1];
        List<Integer> prime_list = new ArrayList<>();
        int p = 0;
        for(int i = 2; i <= UPPER; i++){
            if(!is_prime[i]){primes[p++]=i;}
            for(int j = 0; j <= p; j++){
                if(i * primes[j] <= 1000){
                    is_prime[i * primes[j]] = true;
                }
                if(i % primes[j] == 0){
                    break;
                }
            }
        }
    }

    public boolean primeSubOperation(int[] nums) {
        for(int i = nums.length - 2; i >= 0; i--){
            if(nums[i] >= nums[i+1]){
                int sub = find(nums[i] - nums[i+1]);
                if(sub == -1 || sub >= nums[i]){
                    return false;
                }
                nums[i] -= sub;
            }
        }
        return true;
    }

    private int find(int target){
        /*
            找到比target大的最小质数,如果没有则返回-1
        */
        int i = 0;
        int j = primes.length - 1;
        if(target < primes[i]){return primes[0];}
        if(target >= primes[j]){return -1;}
        while(i < j - 1){
            int mid = (i + j) / 2;
            if(primes[mid] > target){
                j = mid;
            }
            else{
                i = mid;
            }
        }
        return primes[j];
    }
}

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

https://leetcode.cn/problems/minimum-operations-to-make-all-array-elements-equal/

解析:假设nums长度为n,queries长度为q,那么结果为

此方法的时间复杂度为O(n*q),为了减少重复运算,我们采用以下步骤:

  • 首先排序nums,时间复杂度为O(n*log(n))

  • 然后计算nums的所有元素的前缀和,时间复杂度为O(n)

  • 对于queries中的每一个元素queries[i],用二分法将nums分割为两部分,左半边都比queries[i]小,右半边都比queries[i]小。假设分割点为k,那么

其中queries部分的求和可以转化为乘法,nums部分的求和可以通过前缀和得到,时间复杂度为O(q*log(n))。

总的时间复杂度是O((n+q)*log(n))。

class Solution {
    public List<Long> minOperations(int[] nums, int[] queries) {
        Arrays.sort(nums);
        long[] cumsum = new long[nums.length];
        cumsum[0] = nums[0];
        for(int i = 1; i < nums.length; i++){cumsum[i] = cumsum[i-1] + nums[i];}
        List<Long> res = new ArrayList<>();
        for(int query: queries){
            if(query <= nums[0]){
                res.add(cumsum[nums.length - 1] - (long)query * nums.length);
            }
            else if(query >= nums[nums.length - 1]){
                res.add((long)query * nums.length - cumsum[nums.length - 1]);
            }
            else{
                int ind = find(nums, query);
                res.add((long)query * (ind+1) - cumsum[ind] + (cumsum[nums.length - 1] - cumsum[ind]) - (long)query * (nums.length-ind-1));
            }
        }
        return res;
    }

    private int find(int[] nums, int query){
        int i = 0;
        int j = nums.length - 1;
        while(i < j - 1){
            int mid = (i + j) / 2;
            if(nums[mid] == query){return mid;}
            else if(nums[mid] < query){i = mid;}
            else{j = mid;}
        }
        return i;
    }
}

收集树中金币

https://leetcode.cn/problems/collect-coins-in-a-tree/submissions/

解析:考虑一个分步剔除的过程,一步步的剔除不需要遍历的节点:

第一步:去除所有不含金币的子树。具体做法为,通过拓扑排序,循环去除不含金币的叶节点,直至图中所有叶节点均含金币。

第二步:此时,非叶节点是否有金币已经无需关注,因为遍历途中顺路就能收集到。在剩下的点中,去除所有叶节点和它们的紧邻节点。

遍历剩下的节点需要的边数乘2即是答案。

class Solution {
    public int collectTheCoins(int[] coins, int[][] edges) {
        // 建图,用于拓扑排序
        int n = coins.length;
        List<Integer> graphArr[] = new ArrayList[n]; // 存储每个点的临近点
        Arrays.setAll(graphArr, g -> new ArrayList<>());
        int[] degree = new int[n]; // 存储每个点的度数
        for(int i = 0; i < n - 1; i++){
            graphArr[edges[i][0]].add(edges[i][1]);
            graphArr[edges[i][1]].add(edges[i][0]);
            degree[edges[i][0]]++;
            degree[edges[i][1]]++;
        }

        // 循环去除无金币的叶子节点,直至图中所有叶子均有金币
        Queue<Integer> q = new LinkedList<>();
        for(int i = 0; i < n; i++){
            if(degree[i] == 1 && coins[i] == 0){q.add(i);}
        }
        while(!q.isEmpty()){
            for(int y: graphArr[q.remove()]){
                if(--degree[y] == 1 && coins[y] == 0){
                    q.add(y);
                }
            }
        }

        //找到剩余节点距离最近的有金币叶节点的距离
        int[] dist = new int[n];
        for(int i = 0; i < n; i++){
            if(degree[i] == 1 && coins[i] == 1){
                q.add(i);
            }
        }
        while(!q.isEmpty()){
            int x = q.remove();
            for(int y: graphArr[x]){
                if(--degree[y] == 1){
                    dist[y] = dist[x] + 1;
                    q.add(y);
                }
            }
        }

        //计算多少条边的两个端点距离不少于2,乘2即是答案
        int res = 0;
        for(int i = 0; i < n - 1; i++){
            if(dist[edges[i][0]] >= 2 && dist[edges[i][1]] >= 2){res += 2;}
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值