【代码随想录】-贪心专题

基础理论

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?每次拿最大就是局部最优

贪心算法一般分为如下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

上述过于理论化,在实际做题中,很难按照上面四部去逐步思考。

做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。

贪心没有套路,说白了就是常识性推导加上举反例。没有固定模板。

分发饼干

/**
     * leetcode-455. 分发饼干
     * @param g
     * @param s
     * @return
     */
public int findContentChildren(int[] g, int[] s) {
    Arrays.sort(g);
    Arrays.sort(s);
    int ans = 0;
    for (int i = 0;ans < s.length && i < g.length; i++) {
        //如果当前饼干能满足当前孩子的胃口值,ans++,否则就继续查找更大的饼干
        if(g[ans] <= s[i]){
            ans++;
        }
    }
    return ans;
}

摆动序列

/**
         * leetcode-376. 摆动序列
         * @param nums
         * @return
         */
public int wiggleMaxLength(int[] nums) {
    int ans = 1;
    int preDiff = 0; //前一个差值
    int curDiff = 0; //后一个差值
    for (int i = 0; i < nums.length - 1; i++) {
        curDiff = nums[i + 1] - nums[i];
        // 摆动序列可以看作上坡下坡的过程 只有在坡度发生变化时需要更新preDiff
        // 因为可能设计到平坡 即相同数字
        if((preDiff <= 0 && curDiff >0) || (preDiff >=0 || curDiff <0)){
            ans++; //总次数等于坡度变化次数+1
            preDiff = curDiff;
        }

    }
    return ans;
}

最大子数组和

/**
     * 贪心,leetcode 53. 最大子数组和
     * @param nums
     * @return
     */
public int maxSubArray(int[] nums) {
    // 只要连续和出现负数  立马从下一个元素重新开始记录最大值
    int ans = Integer.MIN_VALUE;
    int temp = 0;
    for (int i = 0; i < nums.length; i++) {
        temp += nums[i];
        if (temp > ans) ans = temp;
        if (temp <= 0) temp = 0;
    }
    return ans;
}

跳跃游戏

/**
     * leetcode-55. 跳跃游戏
     * @param nums
     * @return
     */
public boolean canJump(int[] nums) {
    // 3代表最多能跳3个格子
    // 这三个格子作为起跳点都试一下 将能跳的最远距离不断更新
    int cover = 0;
    for (int i = 0; i <= cover; i++) {
        cover = Math.max(cover, i + nums[i]); //这里跳最远指的是下标 不是长度 所以下面-1
        if (cover >= nums.length - 1) return true;
    }
    return false;
}

跳跃游戏II

/**
     * 45. 跳跃游戏 II
     *
     * @param nums
     * @return
     */
public int jump(int[] nums) {
    // 尽量往远了跳
    int begin =0,end = 0;
    int step = 0;
    while (end < nums.length-1){
        int temp = 0;
        for (int i = begin; i <= end; i++) {
            temp = Math.max(temp,i+nums[i]);
        }
        begin = end+1;
        end = temp;
        step++;
    }
    return step;
}

K 次取反后最大化的数组和

/**
     * 1005. K 次取反后最大化的数组和
     *
     * @param nums
     * @param k
     * @return
     */
public int largestSumAfterKNegations2(int[] nums, int k) {
    // 按照实际值大小排序没用  需要按照绝对值大小从大到小排序
    // -2,9,9,8,4 k=5 实际值排序结果是24 正确答案是26
    Integer[] integers = new Integer[nums.length];
    for (int i = 0; i < nums.length; i++) {
        integers[i] = nums[i];
    }
    Arrays.sort(integers, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Math.abs(o2) - Math.abs(o1); //降序
        }
    });
    int ans = 0;
    for (int i = 0; i < integers.length; i++) {
        if (integers[i] < 0 && k > 0) { // 遇到负就变正
            integers[i] = -integers[i];
            k--;
        }
    }
    if (k % 2 == 1) integers[integers.length - 1] *= -1;  // k还没消耗完 就用最小的正值去消耗
    for (int i = 0; i < integers.length; i++) {
        ans += integers[i];
    }
    return ans;
}

public int largestSumAfterKNegations(int[] nums, int k) {
    Arrays.sort(nums);
    int ans = 0;
    for (int i = 0; i < nums.length && k >0; i++) { //负数先抵消
        if (nums[i] < 0) {
            nums[i] = -nums[i];
            k--;
        }
    }
    // 负数可能抵消不完
    Arrays.sort(nums);
    //如果是奇数,选择排序后最小的那个数字,取反
    if (k % 2 == 1) nums[0] *= -1;
    for (int num : nums) {
        ans += num;
    }
    return ans;
}

加油站

/**
 * leetcode-134. 加油站
 *
 * @param gas
 * @param cost
 * @return
 */
public int canCompleteCircuit(int[] gas, int[] cost) {
    int curRest = 0;
    int totalRest = 0;
    int ans = 0;
    for (int i = 0; i < gas.length; i++) {
        curRest += gas[i] - cost[i];
        totalRest += gas[i] - cost[i];
        if (curRest < 0) {
            curRest = 0;
            ans = (i + 1) % gas.length;
        }
    }
    if (totalRest < 0)
        return -1;
    return ans;
}

分发糖果

/**
     * leetcode-135. 分发糖果
     * @param ratings
     * @return
     */
public int candy(int[] ratings) {
    // 测试数据 1 2 2  5 4 3 2
    int[] ans = new int[ratings.length];
    Arrays.fill(ans, 1);
    for (int i = 1; i < ans.length; i++) {  // 从前往后,确认右>左 符合规则
        if (ratings[i] > ratings[i - 1]) {
            ans[i] = ans[i - 1] + 1;
        }
    }
    for (int i = ans.length - 2; i >= 0; i--) { // 从后往前 ,确认左>右 符合规则
        if (ratings[i] > ratings[i + 1]) {
            // 此时有两种选择 一种是其本身 一种是 ratings[i + 1]+1
            // 取大的 这样既保证了大于左边(前面解决)也保证大于右边
            Math.max( ans[i + 1] + 1,ans[i]);
        }
    }
    int ret = 0;
    for (int i = 0; i < ans.length; i++) {
        ret += ans[i];
    }
    return ret;
}

柠檬水找零

/**
     * leetcode-860. 柠檬水找零
     *
     * @param bills
     * @return
     */
public boolean lemonadeChange(int[] bills) {
    int five = 0;
    int ten = 0;
    if (bills[0] > 5) return false;
    for (int i = 0; i < bills.length; i++) {
        if (bills[i] == 5) five++;
        else if (bills[i] == 10) {
            ten++;
            five--;
        } else {
            if (ten == 0) five -= 2; // 优先消耗10元
            else ten--;
            five--;
        }
        if (five < 0 || ten < 0) {
            return false;
        }
    }
    return true;
}

根据身高重建队列

/**
 * leetcode-406. 根据身高重建队列
 *
 * @param people	
 * @return
 */
public int[][] reconstructQueue(int[][] people) {
    Arrays.sort(people, new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) { // 身高从大到小排(身高相同k小的站前面)
            if(o1[0] == o2[0]) return o1[1] - o2[1];
            return o1[0] - o2[0];
        }
    });
    LinkedList<int[]> que = new LinkedList<>();
    for (int[] person : people) {
        que.add(person[1],person);
    }
    return que.toArray(new int[que.size()][]);
}

用最少数量的箭引爆气球

/**
     * 452. 用最少数量的箭引爆气球
     * @param points
     * @return
     */
public int findMinArrowShots(int[][] points) {
    if(points.length == 0) return 0;
    Arrays.sort(points, new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return Integer.compare(o1[0],o2[0]); // 使用Integer内置方法 不会溢出
        }
    });
    int ans = 1; // points 不为空至少需要一枝箭
    for (int i = 1; i < points.length; i++) {
        if(points[i][0] > points[i-1][1]){
            // 第二只气球的左边界大于第一只气球的右边界 两气球不挨着
            ans++;
        }else {
            // 更新重叠气球最小右边界
            points[i][1] = Math.min(points[i][1],points[i-1][1]);
        }
    }
    return ans;
}

无重叠区间

/**
     * 435. 无重叠区间
     *
     * @param intervals
     * @return
     */
public int eraseOverlapIntervals(int[][] intervals) {
    // 本质上是一道预定会议问题
    Arrays.sort(intervals, new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return o1[1] - o2[1]; // 按照结束时间排序
        }
    });
    int right = intervals[0][1]; //有边界
    int ans = 1;
    for (int i = 1; i < intervals.length; i++) {
        if (intervals[i][0] > right) {
            ans++;
            right = intervals[i][1];
        }
    }
    return intervals.length - ans;
}

划分字母区间

/**
 * 763. 划分字母区间
 *
 * @param s
 * @return
 */
public List<Integer> partitionLabels(String s) {
    int[] map = new int[27];
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < s.length(); i++) { // 统计每一个字符最后出现的位置
        map[s.charAt(i) - 'a'] = i;
    }
    int left = 0;
    int right = 0; // 右边界
    for (int i = 0; i < s.length(); i++) {
        // 找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了
        right = Math.max(right, map[s.charAt(i) - 'a']);
        if (i == right) {
            list.add(right - left + 1);
            left = right + 1;
        }
    }
    return list;
}

合并区间

/**
 * 56. 合并区间
 * @param intervals
 * @return
 */
public int[][] merge(int[][] intervals) {
    Arrays.sort(intervals, new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return o1[0] - o2[0];
        }
    });
    int n = intervals.length;
    // int[][] merge = new int[n][n];
    List<int[]> list = new ArrayList<>();
    int left = intervals[0][0];
    int right = intervals[0][1];

    for (int i = 1; i < n; i++) {
        if (intervals[i][0] <= right) {
            // 只需要更新右边界  左边界不需要更新
            right = Math.max(right, intervals[i][1]);
        } else {
            list.add(new int[]{left, right});
            left = intervals[i][0];
            right = intervals[i][1];
        }
    }
    list.add(new int[]{left, right});
    return list.toArray(new int[list.size()][]);
}

单调递增的数字

/**
 * 738. 单调递增的数字
 *
 * @param n
 * @return
 */
public int monotoneIncreasingDigits(int n) {
    char[] charArray = String.valueOf(n).toCharArray();
    int start = charArray.length;
    for (int i = charArray.length - 1; i > 0; i--) {
        if (charArray[i] < charArray[i-1] ) {
            charArray[i-1]-=1;
            start = i+1;
        }
    }
    for (int i = start; i < charArray.length; i++) {
        charArray[i] = '9';
    }
    return Integer.parseInt(String.valueOf(charArray));
}


public int monotoneIncreasingDigits2(int n) {
    int ones = 111111111; //本题中的n最大是9次方
    int ans = 0; //累加最多不超过9次
    for (int i = 0; i < 9; i++) {
        while (ans + ones > n) {
            ones /= 10;
        }
        ans += ones;
        System.out.println(ones);
        if (ones == 0) break;
    }
    return ans;
}

买卖股票的最佳时机含手续费

/**
 * 714. 买卖股票的最佳时机含手续费
 * @param prices
 * @param fee
 * @return
 */
public int maxProfit(int[] prices, int fee) {
    int buy = prices[0] + fee;
    int ans = 0;
    for (int price : prices) { // 今天比前一天购买花费更少 更新
        if (price + fee < buy) {
            buy = price + fee;
        }
        if (price > buy) { //有利润可以获得 卖出
            ans += price - buy;
            buy = price;
            //相当于一个后悔机制 后面遇到了比今天卖的价格更高的时候  选择价格高的卖出
            // 减去buy相当于之前的price没有起作用,并且在之前的price中已经减去了成本(价格+交易费)
            // 价格=前一天的buy(价格+交易费) 不做任何操作
        }
    }
    return ans;
}

监控二叉树

int ans = 0;

/**
 * leetcode-968. 监控二叉树
 * @param root
 * @return
 */
public int minCameraCover(TreeNode root) {

    if (minCame(root) == 0){//若根节点未被覆盖
        ans++;
    }
    return ans;
}

public int minCame(TreeNode root) {
    // 定义三个状态
    //0:该节点未被覆盖
    //1:该节点有摄像头
    //2:该节点被覆盖
    // 后序遍历,根据左右节点的情况,来判读 自己的状态
    if (root == null) {
        // 空节点默认为 有覆盖状态,避免在叶子节点上放摄像头
        return 2;
    }
    int left = minCame(root.left);
    int right = minCame(root.right);
    if (left == 2 && right == 2) {
        // 如果左右节点都覆盖了的话, 那么本节点的状态就是无覆盖
        return 0;
    } else if (left == 0 || right == 0) { // 左右节点有一个未被覆盖
        ans++;
        return 1;
    } else { // 左右节点至少有一个摄像头  处于被覆盖状态
        return 2;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值