【周赛总结】贪心,链表,左右遍历,单调栈,差分数组,优先队列


带来第216,217场双周赛和第40场双周赛的题目解析

1665. 完成所有任务的最少初始能量H 贪心

在这里插入图片描述

核心思路是贪心,有一个特点是贪心的题目都是要求什么任选之类,并且数据大。对于一般的任选可以采用状态压缩dp的方法。对于数据比较大的一般就是采用贪心的思路了。

class Solution {
    public int minimumEffort(int[][] tasks) {
        // 贪心的思路,我们需要首先完成那些门槛值和消耗值差距大的任务。
        // 原因是,
        Arrays.sort(tasks, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return -(a[1]-a[0])+(b[1]-b[0]);
            }
        });

        int need = 0;
        for (int[] t:tasks){
            need += t[0];
        }
        int ans = need; // 最基本的完成任务需要的能量
        int add = 0; // 额外需要补充的能力达到门槛的
        for (int[] t:tasks){
            if (need<t[1]){
                add += t[1]-need;
                need += t[1]-need;
            }
            need -= t[0];
        }
        return ans+add;

    }
}

1669. 合并两个链表M 链表

在这里插入图片描述

java对于链表的题目还是经常的出现错误,这里一定要注意每次的ListNode dummy = list1其实是指向了一个地址。

class Solution {
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode ans=new ListNode();
        b -= a-1; 
        ListNode tmp1 = new ListNode();
        tmp1.next = list1;
        ans = tmp1; // ans这个东西,指向tmp1当前的地址
        while((a--)>0) tmp1 = tmp1.next;  // 找到链表的尾部,这里一会进行删除
        ListNode tmp2 = tmp1;  // temp这个东西,指向tmp1当前的地址
        
        while((b--)>=0) tmp2 = tmp2.next;  // 这个是一会的接在list2后面的
        tmp1.next = list2; // 这一行不能放到空行那里,因为temp2还是会继续使用的
        while(tmp1.next != null) tmp1 = tmp1.next; // 找到list2的尾部
        tmp1.next = tmp2; // list2的尾部拼接上list1
        return ans.next;
    }
}

1671. 得到山形数组的最少删除次数H 左右两次遍历

在这里插入图片描述

左右两边进行遍历的思路,注意最后在选择山峰的时候,不能是第一个和最后一个。

class Solution {
    public int minimumMountainRemovals(int[] nums) {
        int n = nums.length;
        // 从左侧开始遍历
        int[] dp = new int[n];
        for (int i = 0; i<n;i++){
            int cur = nums[i];
            dp[i] = i; // 有一个最小值,是为了考虑如果都是清一色的上升的情况。
            for (int j = i-1;j>=0;j--){
                if(cur>nums[j]){
                    dp[i] = Math.min(dp[j]+i-j-1, dp[i]);
                }
            }
        }
        // 从右侧开始进行遍历
        int[] dp1 = new int[n];
        for (int i = n-1; i>=0;i--){
            int cur = nums[i];
            dp1[i] = n-1-i; // 同样,考虑都是下降的情况
            for (int j = i+1;j<n;j++){
                if(cur>nums[j]){
                    dp1[i] = Math.min(dp1[j]+j-i-1,dp1[i]);
                }
            }
        }
        int ans = n;
        // 这里选择山峰,山峰不可以是第一个,也不可以是最后一个
        for (int i = 1;i<n-1;i++){ // 注意循环范围,不可以是单调的。这里是选择最高的山峰
            if(dp[i]!=i && dp1[i]!=n-i-1) 
                ans = Math.min(ans,dp[i]+dp1[i]);
        }       
        return ans;
    }
}

1673. 找出最具竞争力的子序列M 单调栈

在这里插入图片描述

class Solution {
    public int[] mostCompetitive(int[] nums, int k) {
        // 单调栈
        int[] ans = new int[k];
        int index = -1;
        int n = nums.length;
        for (int i = 0; i<n;i++){ 
            while (index>= 0 && index+n-i >= k && ans[index] > nums[i]){// 栈不空,且后面足够填满,当前的小于栈顶的
                index--;
            }
            if (index<k-1) {
                index++;
                ans[index] = nums[i];
            }
        }
        return ans;

    }
}

1674. 使数组互补的最少操作次数M 差分数组

在这里插入图片描述

class Solution {
    // 差分的思路。
    // 为什么使用差分? 因为差分可以知道对于这个和,每个对子为了实现这个目标需要的次数。
    // 并且这个具有明显的区间分段的性质
    public int minMoves(int[] nums, int limit) {
        // diff 维护了整个数组实现这个i这个加和需要的操作数量
        int[] diff = new int[2+2*limit];
        int n = nums.length;
        for (int i = 0;i<n/2;i++){// 每次枚举两侧的数字
            int l = nums[i];
            int r = nums[n-1-i];
            // 开始讨论: 假设l小,r大
            // 1. 对于[2,l]的显然需要修改两次
            // 2. 对于[l+1, r+limit]需要修改一次
            // 3. 对于[r+limit+1, 2limit] 需要两次
            // 4. 对于r+l,完全不需要调整,0次
            // 因此使用差分数组时候,0->2->1—>0->1->2->0
            diff[2] += 2;
            diff[1+Math.min(l,r)] -= 1;
            diff[l+r] -= 1;
            diff[l+r+1] += 1;
            diff[limit+Math.max(l,r)+1] += 1;
            diff[2*limit+1] -= 2;
        }
        int ans = Integer.MAX_VALUE;
        int now = 0;
        // 最后枚举每种情况,找到最小值。
        for (int i = 2; i<1+2*limit;i++){
            now += diff[i];
            ans = Math.min(ans, now);
        }
        return ans;
    }
}

1675. 数组的最小偏移量 H 优先队列

在这里插入图片描述

其实关键在于理解题目,我们首先把数组退化为一个最基础的形式,然后题目给了我们进化的最大次数,很容易想到用优先队列去维护每次该进化哪一个,并且在这个过程中维护最小偏移量。

class Solution {
    public int minimumDeviation(int[] nums) {
        // 题目有一个特点,奇数只能变大,偶数只能变小。因此我们首先都变为最小然后依次增大最小的数字,每次维护最小偏移量
        int n = nums.length;
        int[][] list = new int[n][2]; // 第一项是底,第二项是可以乘多少次2.如,8->[1,3]
        for (int i = 0; i<nums.length;i++){
            if (nums[i]%2 == 0){
                int cnt =0;
                while (nums[i]%2 == 0){
                    cnt++;
                    nums[i] /= 2;
                }
                list[i] = new int[]{nums[i], cnt};
            }
            else {
                list[i] = new int[]{nums[i], 1};
            }
        }
        // 类似最小区间问题。
        PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() { // Comparator里面的泛型填写比较的泛型
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0]-o2[0];
            }
        });

        int max = 0;
        for (int i = 0;i<n;i++){
            pq.offer(list[i]);
            max = Math.max(max, list[i][0]);
        }
        int ans = Integer.MAX_VALUE;;
        while(true){
            int[] cur = pq.poll();
            ans = Math.min(ans, max-cur[0]);
            if(cur[1]<1) return ans; // 跳出位置,某一个已经是0,无法再增大
            cur[0] *= 2;
            max = Math.max(max, cur[0]);
            cur[1]--;
            pq.offer(cur);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值