算法:贪心题目总结

本文详细介绍了多个编程题目,运用贪心算法解决涉及分发、数组操作、股票交易、跳跃游戏、取反、找零、队列重构、气球爆炸、字母区间划分、区间合并及二叉树监控等问题,展示了贪心算法在这些问题中的应用和优化思路。
摘要由CSDN通过智能技术生成

贪心问题

*题目1:455. 分发饼干

1)贪心法
思路:

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        //对胃口排序
        Arrays.sort(g);
        //对饼干排序
        Arrays.sort(s);
        int start = 0;
        int count = 0;
        //遍历饼干
        for (int i = 0; i < s.length&&start<g.length; i++) {
            //如果饼干值大于胃口值 证明满足了孩子的胃口
            if (s[i] >= g[start]) {
                start++;
                count++;
            }
        }
        return count;
    }
}
*题目2:376. 摆动序列

1)贪心法
思路:统计波纹 如果序列一直递增 那么down的值一直不变 up也不会变

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int up=1;
        int down=1;
        for(int i=1;i<nums.length;i++){
            if(nums[i]>nums[i-1]){
                up=down+1;
            }
            if(nums[i]<nums[i-1]){
                down=up+1;
            }
        }
        return Math.max(up,down);
    }
}
*题目3:53. 最大子数组和

思路:暴力法挺好想 贪心法不太好想
1)暴力解法

class Solution {
    public int maxSubArray(int[] nums) {
        int result=Integer.MIN_VALUE;
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum=0;
            for(int j=i;j<nums.length;j++){
                sum+=nums[j];
                if(sum>result){
                    result=sum;
                }
            }
        }
        return result;
    }
}

2)贪心法

class Solution {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1){
            return nums[0];
        }
        int sum = Integer.MIN_VALUE;
        int count = 0;
        for (int i = 0; i < nums.length; i++){
            count += nums[i];
            sum = Math.max(sum, count); // 取区间累计的最大值(相当于不断确定最大子序终止位置)
            if (count <= 0){
                count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
            }
        }
       return sum;
    }
}
*题目4:122. 买卖股票的最佳时机 II

1)贪心法
思路:画出利润

class Solution {
    public int maxProfit(int[] prices) {
        int res=0;
        for(int i=1;i<prices.length;i++){
            int sum=prices[i]-prices[i-1];
            res+=Math.max(sum,0);
        }
        return res;
    }
}
*题目5:55. 跳跃游戏

1)贪心法
思路:

class Solution {
    public boolean canJump(int[] nums) {
        int coverRange = 0;
        for(int i=0;i<=coverRange;i++){
           coverRange = Math.max(coverRange, i + nums[i]);
           if(coverRange>=nums.length-1){
               return true;
           }
        }
        return false;
    }
}
*题目6:45. 跳跃游戏 II

1)贪心法
思路:

class Solution {
    public int jump(int[] nums) {
        if(nums.length==0||nums.length == 1) return 0;
        //记录跳跃的次数
        int count=0;
        //当前的覆盖最大区域
        int curDistance = 0;
        //最大的覆盖区域
        int maxDistance = 0;
        for(int i=0;i<nums.length;i++){
            //在可覆盖区域内更新最大的覆盖区域
            maxDistance = Math.max(maxDistance,i+nums[i]);
            //说明当前一步,再跳一步就到达了末尾
            if (maxDistance>=nums.length-1){
                count++;
                break;
            }
            //走到当前覆盖的最大区域时,更新下一步可达的最大区域
            if (i==curDistance){
                curDistance = maxDistance;
                count++;
            }
        }
        return count;
    }
}
题目7:1005. K 次取反后最大化的数组和

1)贪心法
思路:

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        // 将数组从大到小排序,注意要按照绝对值的大小
	    nums = IntStream.of(nums)
		     .boxed()
		     .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
		     .mapToInt(Integer::intValue).toArray();
        int len = nums.length;	    
	    for (int i = 0; i < len; i++) {
	        //从前向后遍历,遇到负数将其变为正数,同时K--
	        if (nums[i] < 0 && k > 0) {
	    	    nums[i] = -nums[i];
	    	    k--;
	        }
	    }
        //奇数 再反转一下最小值
        if (k % 2 == 1) nums[len - 1] = -nums[len - 1];
	    return Arrays.stream(nums).sum();
    }
}
*题目8:134. 加油站

1)贪心法
思路:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum = 0;
        int totalSum = 0;
        int index = 0;
        for (int i = 0; i < gas.length; i++) {
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if (curSum < 0) {
                index=i+1;
                curSum = 0;
            }
        }
        if (totalSum < 0) return -1;
        return index;
    }
}
*题目9:135. 分发糖果

1)贪心法
思路:

class Solution {
    public int candy(int[] ratings) {
        int len = ratings.length;
        int[] candyVec = new int[len];
        candyVec[0] = 1;
        for (int i = 1; i < len; i++) {
            candyVec[i] = (ratings[i] > ratings[i - 1]) ? candyVec[i - 1] + 1 : 1;
        }
        for (int i = len - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]) {
                candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1);
            }
        }
        return  Arrays.stream(candyVec).sum();
    }
}
题目10:860. 柠檬水找零

1)贪心法
思路:分 五、十、二十的情况来看

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0;
        int ten = 0;
        for (int i = 0; i < bills.length; i++) {
            if (bills[i] == 5) {
                five++;
            } else if (bills[i] == 10) {
                five--;
                ten++;
            } else if (bills[i] == 20) {
                if (ten > 0 && five>0) {
                    ten--;
                    five--;
                } else {
                    five -= 3;
                }
            }
            if (five < 0 || ten < 0) return false;
        } 
        return true;
    }
}

区间问题

*题目11:406. 根据身高重建队列

1)贪心法
思路:1.排序规则:按照先H高度降序,K个数升序排序
2.遍历排序后的数组,根据K插入到K的位置上

class Solution {
    public int[][] reconstructQueue(int[][] people) {
            // 身高从大到小排(身高相同k小的站前面)
            Arrays.sort(people, (a, b) -> {
                if (a[0] == b[0]) return a[1] - b[1];   // a - b 是升序排列
                return b[0] - a[0];   //b - a 是降序排列
            });
            LinkedList<int[]> que = new LinkedList<>();
            for (int[] p : people) {
                que.add(p[1],p);
            }
            return que.toArray(new int[people.length][]);
    }
}
题目12:452. 用最少数量的箭引爆气球

1)贪心法
思路:当气球出现重叠,一起射,所用弓箭最少

class Solution {
    public int findMinArrowShots(int[][] points) {
        // 根据气球直径的开始坐标从小到大排序
        Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));
        int count = 1;  // points 不为空至少需要一支箭
        for (int i = 1; i < points.length; i++) {
            // 气球i和气球i-1不挨着,注意这里不是>=
            if (points[i][0] > points[i - 1][1]) {  
                // 需要一支箭
                count++; 
            } else {  
                // 气球i和气球i-1挨着
                // 更新重叠气球最小右边界
                points[i][1] = Math.min(points[i][1], points[i - 1][1]);
            }
        }
        return count;
    }
}
题目13:435. 无重叠区间

1)贪心法
思路:代码以左边界排序 用总区间数-重叠区间数=未重叠区间数

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a,b)-> {
            return Integer.compare(a[0],b[0]);
        });
        int count = 1;
        for(int i = 1;i < intervals.length;i++){
            if(intervals[i][0] < intervals[i-1][1]){
                intervals[i][1] = Math.min(intervals[i - 1][1], intervals[i][1]);
                continue;
            }else{
                count++;
            }    
        }
        return intervals.length - count;
    }
}
题目14:763. 划分字母区间

1)贪心法
思路:如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。
1)统计每一个字符最后出现的位置
2)从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点

class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> list = new LinkedList<>();
        int[] edge = new int[26];
        char[] chars = s.toCharArray();
        //找到每个字符最后出现的位置
        for (int i = 0; i < chars.length; i++) {
            edge[chars[i] - 'a'] = i;
        }
        int right = 0;
        int left = 0;
        for (int i = 0; i < chars.length; i++) {
            //找到字符出现的最远边界
            right = Math.max(right,edge[chars[i] - 'a']);
            //找到分割处了
            if (i == right) {
                list.add(right - left + 1);
                left = i+1;
            }
        }
        return list;
    }
}
*题目15:56. 合并区间

1)贪心法
思路:判断区间重叠

class Solution {
    public int[][] merge(int[][] intervals) {
        List<int[]> res = new LinkedList<>();
        //按照左边界排序
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
        //initial start 是最小左边界
        int start = intervals[0][0];
        int rightmostRightBound = intervals[0][1];
        //从第二个元素开始
        for (int i = 1; i < intervals.length; i++) {
            //如果第2个左边界大于第1个最大右边界,说明无重叠
            if (intervals[i][0] > rightmostRightBound) {
                //把第一个元素加入区间 并且更新start
                res.add(new int[]{start, rightmostRightBound});
                start = intervals[i][0];
                rightmostRightBound = intervals[i][1];
            } else {
                //说明有重叠
                //更新最大右边界
                rightmostRightBound = Math.max(rightmostRightBound, intervals[i][1]);
            }
        }
        res.add(new int[]{start, rightmostRightBound});
        return res.toArray(new int[res.size()][]);
    }
}
*题目16:738. 单调递增的数字

1)贪心法
思路:例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]–,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数,从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299

class Solution {
    public int monotoneIncreasingDigits(int n) {
        String s = String.valueOf(n);
        char[] chars = s.toCharArray();
        int start = s.length();
        //从后向前遍历
        for (int i = s.length() - 1; i > 0; i--) {
            if (chars[i-1] > chars[i]) {
                chars[i-1]--;
                start = i;
            }
        }
        //把strNum[i]变为9
        for (int i = start; i < s.length(); i++) {
            chars[i] = '9';
        }
        return Integer.parseInt(String.valueOf(chars));
    }
}
*题目17:968. 监控二叉树

1)贪心法
思路:从低到上,先给叶子节点的父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点
1)可以使用后序遍历也就是左右中的顺序,这样就可以在回溯的过程中从下到上进行推导了
2)情况1:左右节点都有覆盖:左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了。
情况2:左右节点至少有一个无覆盖的情况:
如果是以下情况,则中间节点(父节点)应该放摄像头:
left == 0 && right == 0 左右节点无覆盖
left == 1 && right == 0 左节点有摄像头,右节点无覆盖
left == 0 && right == 1 左节点有无覆盖,右节点摄像头
left == 0 && right == 2 左节点无覆盖,右节点覆盖
left == 2 && right == 0 左节点覆盖,右节点无覆盖
情况3:左右节点至少有一个有摄像头
如果是以下情况,其实就是 左右孩子节点有一个有摄像头了,那么其父节点就应该是2(覆盖的状态)
left == 1 && right == 2 左节点有摄像头,右节点有覆盖
left == 2 && right == 1 左节点有覆盖,右节点有摄像头
left == 1 && right == 1 左右节点都有摄像头
情况4:头结点没有覆盖

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    int  res=0;
    public int minCameraCover(TreeNode root) {
    // 对根节点的状态做检验,防止根节点是无覆盖状态 .
        if(minCame(root)==0){
            res++;
        }
        return res;
    }
    /**
     节点的状态值:
       0 表示无覆盖
       1 表示 有摄像头
       2 表示有覆盖
    后序遍历,根据左右节点的情况,来判读 自己的状态
     */
    public int minCame(TreeNode root){
        if(root==null){
            // 空节点默认为 有覆盖状态,避免在叶子节点上放摄像头
            return 2;
        }
        int left=minCame(root.left);
        int  right=minCame(root.right);

        // 如果左右节点都覆盖了的话, 那么本节点的状态就应该是无覆盖,没有摄像头
        if(left==2&&right==2){
            //(2,2)
            return 0;
        }else if(left==0||right==0){
            // 左右节点都是无覆盖状态,那 根节点此时应该放一个摄像头
            // (0,0) (0,1) (0,2) (1,0) (2,0)
            // 状态值为 1 摄像头数 ++;
            res++;
            return 1;
        }else{
            // 左右节点的 状态为 (1,1) (1,2) (2,1) 也就是左右节点至少存在 1个摄像头,
            // 那么本节点就是处于被覆盖状态
            return 2;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值