Leetcode 刷题笔记(二十一) ——贪心算法篇之入门题目

系列文章目录

一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的层序遍历及相关题目
十 四、二叉树篇之二叉树的属性相关题目
十 五、 二叉树篇之二叉树的修改与构造
十 六、 二叉树篇之二叉搜索树的属性
十 七、二叉树篇之公共祖先问题
十 八、二叉树篇之二叉搜索树的修改与构造
十 九、回溯算法篇之组合问题
二 十、回溯算法篇之分割、子集、全排列问题
更新中 …


前言

刷题路线来自 :代码随想录
贪心算法: 由局部最优解堆叠成全局最优解,没有固定发套路,就是常识性推导加上举反例。

题录

一、简单题目

455.分发饼干

Leetcode 链接
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

在这里插入图片描述
题解:

class Solution {
    public int findContentChildren(int[] g, int[] s) {
    	// 先排序
        Arrays.sort(g);
        Arrays.sort(s);
        int i = 0; // 孩子下标
        int j = 0; // 胃口下标
        // 还有饼干并且还有孩子
        while (i < g.length && j < s.length) {
            if (s[j] >= g[i]) {
            	// 饼干能满足孩子胃口
                i++;
                j++;
            } else {
            	// 孩子胃口太大,向后找大的饼干
                j++;
            }
        }
        return i;
    }
}

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

Leetcode 链接
给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:
选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。
重复这个过程恰好 k 次。可以多次选择同一个下标 i 。以这种方式修改数组后,返回数组 可能的最大和
在这里插入图片描述
题解:
每次给最小的数取反

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        for (int i = 0; i < k; i++) {
        	// 排序,取反
            Arrays.sort(nums);
            nums[0] = -nums[0];
        }
        // 求和
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return sum;
    }
}

优化:

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
    	// 先排序
        Arrays.sort(nums);
        // 将[0,k)范围的负数取反
        for (int i = 0; i < nums.length; i++) {
            if (k > 0 && nums[i] < 0) {
                nums[i] = -nums[i];
                k--;
            }        
        }
        // 看剩余的k 是偶数还是奇数
        if (k % 2 == 1) {
        	// 奇数,将最小的数取反
            Arrays.sort(nums);
            nums[0] = -nums[0];
        }
        // 偶数不用操作
        // 求和
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return sum;
    }
}

860. 柠檬水找零

Leetcode 链接
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。注意,一开始你手头没有任何零钱。 给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

在这里插入图片描述
题解:
记录 五块和十块的章数,分情况找钱,找钱时为代码简洁不去判断手中有没有,找完根据钱数是正负判断。

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0;
        int ten = 0;
        for (int i = 0; i < bills.length; i++) {
        	// 情况1.收到5块钱
            if (bills[i] == 5) {
                five++;
            }
            // 情况2.收到10块钱,找5快
            if (bills[i] == 10) {
                five--;
                ten++;                   
            }
            // 情况3.收到20,有10快先找快的
            if (bills[i] == 20) {
                if (ten > 0) {
                    ten--;
                    five--;
                } else {
                    five -= 3;
                }
            }
            // 判断
            if (five < 0 || ten < 0) {
                return false;
            }
        }
        return true;
    }
}

二、中等题目

376. 摆动序列

Leetcode 链接
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
在这里插入图片描述
题解:
连续两对差值相反表示摆动
在这里插入图片描述
特殊情况:[2, 2, 5] 的最长摆动子序列为 [2, 5],可是差值是 [0, 5] ,所以判断是需要加上等于

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (nums.length <= 1) return nums.length;
        int preDiff = 0; // 上一对差值
        int curDiff = 0; // 当前差值
        int count = 1; // 记录
        for (int i = 1; i < nums.length; i++) {
            curDiff = nums[i] - nums[i - 1];
            // 连续两对差值相反,注意等于
            if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
                count++;
                preDiff = curDiff;
            }
        }
        return count;
    }
}

738. 单调递增的数字

Leetcode 链接在这里插入图片描述
题解:
局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]–,然后strNum[i]给为9
从后向前遍历,找到开始赋9的位置

class Solution {
    public int monotoneIncreasingDigits(int n) {
        if (n == 0) return 0;
        // 转为 char 数组
        char[] chars = String.valueOf(n).toCharArray();
        int start = 10;  // 最大值
        // 从后向前遍历
        for (int i = chars.length - 1; i > 0; i--) {
            if (chars[i] < chars[i - 1]) {
                chars[i - 1]--;
                start = i;
            }
        }
        // 拼接
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < chars.length; i++) {
        	// 10 -> 09,第一个 0 不拼接
            if (i == 0 && chars[i] == '0') continue;
            if (i >= start) {
                sb.append('9');
            } else {
                sb.append(chars[i]);
            }
        }
        return Integer.valueOf(sb.toString());
    }
}

三、买股票问题

122.买卖股票的最佳时机II

Leetcode 链接
给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。返回 你能获得的 最大 利润 。
在这里插入图片描述
题解:
局部最优:收集每天的正利润,只要股票价格大于大于前一天的就算入盈利
全局最优:求得最大利润。

class Solution {
    public int maxProfit(int[] prices) {
        int res = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) {
                res += prices[i] - prices[i - 1];
            }
        }
        return res;
    }
}

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

Leetcode 链接
给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。
在这里插入图片描述
题解:
买入价格加上手续费,作为前一天购入价格。
不同上题,不能无脑的计算每天利润。如:[1, 4, 8], fee = 2 时,按 1+2 = 3的价格购入,第二天还不能卖,再第三 3 天再卖只用一次手续费。怎么计算呢,可以假设第一天3元买入,第二天4元卖出,记录卖出价格,因为第三天价格大于4,所以第三天按 (8 - 4)算,少算手续费
那什么时候是真正的卖出呢? [1, 6, 3, 8] 时,6-(1+2) + 8-(3+2) > 8-(1+2),因为 3 + 2 < 6,第三天算上手续费买入价格为5元 小于前一天的卖出价格 6元。

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int sum = 0;
        // 初始化为第一天的买入价格
        int buy = prices[0] + fee;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > buy) {
            	// 能盈利就计算利润,并记录本次虚假卖出的价格,作为下次买入价格
                sum += prices[i] - buy;
                buy = prices[i];
            } else if (prices[i] + fee < buy) {
            	// 真正的买入价格
                buy = prices[i] + fee;
            }
        }
        return sum;
    }
}

四、两个维度权衡问题

135. 分发糖果

Leetcode 链接
在这里插入图片描述
题解:
局部最优:如果两边一起考虑一定会顾此失彼,一次只考虑一边。

  1. 从左向右遍历,只要右边评分比左边大,右边的孩子就多一个糖果
  2. 从右向左遍历,只要左边评分比右边大,左边的孩子就多一个糖果
  3. 从新遍历,每个位置取前两次遍历的最大值
class Solution {
    public int candy(int[] ratings) {
        int[] candyArr = new int[ratings.length];
        candyArr[0] = 1;
        // 从前向后遍历时第一个孩子糖果个数默认为 1,从第二个孩子开始
        for (int i = 1; i < ratings.length; i++) {
            candyArr[i] = ratings[i] > ratings[i - 1] ? candyArr[i - 1] + 1 : 1;
        }
        // 从后向前遍历是从倒数第 2 个孩子开始,最后一个位置以上轮遍历作为结果
        for (int i = ratings.length - 2; i >= 0; i--) {
            int temp = ratings[i] > ratings[i + 1] ? candyArr[i + 1] + 1 : 1;
            candyArr[i] = Math.max(temp, candyArr[i]);
        }
        int res = 0;
        for (int i = 0; i < candyArr.length; i++) {
            res += candyArr[i];
        }
        return res;
    }
}

406. 根据身高重建队列

Leetcode 链接
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
在这里插入图片描述
题解:
先根据身高排序,再进行插入

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, (p1, p2) -> {
        	// 相同按第二个元素升序
            if (p1[0] == p2[0]) return p1[1] - p2[1];
            // 默认降序
            return p2[0] - p1[0];
        });
        List<int[]> List = new LinkedList<>();
        for (int[] arr : people) {
            List.add(arr[1], arr);
        }
        return List.toArray(new int[people.length][]);
    }
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值