【贪心算法】柠檬水找零,将数组和减半的最少操作次数,最大数,摆动序列, 最长递增子序列,递增的三元子序列

文章目录

算法介绍

贪心策略是一种解决问题的策略,通过局部最优解得到总体最优解

  1. 把解决问题的问题分为若干步
  2. 解决每一步都选择当前看起来"最优"的解法,目光短浅,鼠目寸光(🫣)
  3. 希望得到全局最优解

贪心的正确性:贪心策略不一定正确,需要严谨的数据证明

1. 柠檬水找零(LC 860)

柠檬水找零

题目描述

解题思路

分类讨论:

  • 5元:可以直接收下
  • 10元:找零5元
  • 20元:找零5元*3,也可以找零10元+5元

可以看出来5元的作用更大,因此找零的时候要尽量保留5元,找出10元

代码实现

java 复制代码
class Solution {
    public boolean lemonadeChange(int[] bills) {
        int d5 = 0;
        int d10 = 0;
        int d20 = 0;
        int n = bills.length;
        
        for(int i = 0;i<n;i++){
            if(bills[i]==5)
                d5++;
            else if(bills[i]==10){
                if(d5==0)
                    return false;
                else{
                    d5--;
                    d10++;
                }
            }else{
                if(d5>=1 && d10>=1){
                    d20++;
                    d5--;
                    d10--;
                }else if(d5>=3){
                    d20++;
                    d5-=3;
                }else
                    return false;
            }
        }
        return true;
    }
}

2. 将数组和减半的最少操作次数(LC 2208)

将数组和减半的最少操作次数

题目描述

解题思路

利用大根堆,每次把最大的数砍半

注意: double类型不能用减法作比较

java 复制代码
PriorityQueue<Double> heap = new PriorityQueue<>((a,b)->b-a);

这行代码是错的!!因为浮点数涉及精度问题,直接相减得到的结果不准确;另外Comparator返回类应该是int,而b-a的结果是double。应该用Double.compare()

代码实现

java 复制代码
public int halveArray(int[] nums) {
        PriorityQueue<Double> heap = new PriorityQueue<>((a,b)->Double.compare(b, a));
        double sum = 0;
        for(double num:nums){
            sum+=num;
            heap.offer(num);
        }
        double tar = sum/2;
        int ret = 0;
        while(sum>tar){
            double top = heap.poll();
            heap.offer(top/2);
            sum-=top/2;
            ret++;
        }
        return ret ;
    }

3. 最大数(LC 179)

最大数

题目描述

解题思路

利用大根堆,按照字符的ASCII码排序拼接。

需要注意的是:不可以写成以下代码

java 复制代码
PriorityQueue<String> heap = new PriorityQueue<>((a,b)->b.compareTo(a));

假设比较30和3,如果按上面的代码逻辑,30会排在3前面,正确答案应该是330而不是303,因此要修改比较逻辑。改成(b+a).compareTo(a+b),比较这两个字符串拼接以后的ASCII码大小。

代码实现

java 复制代码
public String largestNumber(int[] nums) {
        StringBuilder ret = new StringBuilder();
        PriorityQueue<String> heap = new PriorityQueue<>((a,b)->(b+a).compareTo(a+b));
        for(int a:nums)
            heap.offer(a+"");
        
        while(!heap.isEmpty()){
            ret.append(heap.poll());
        }
        if(ret.charAt(0)=='0')
            return "0";
        return ret.toString();
    }

4. 摆动序列(LC 376)

摆动序列

题目描述

解题思路

找到所有波峰和波谷的个数,串联成摆动序列

确定波峰/波谷:可以对这个数左右分别相减,乘积为负就说明是波峰/波谷。如果为0,说明是水平,继续向后找到乘积为负的数。

代码实现

java 复制代码
   public int wiggleMaxLength(int[] nums) {
        int ret = 0;
        int n = nums.length;
        int left = 0;
        if(n==1)
            return 1;
        

        for(int i = 0;i<n-1;i++){
            int right = nums[i+1]-nums[i];
            if(right==0)
                continue;
                //=0是第一个节点,<0是波峰或波谷
            if(left*right<=0)
                ret++;
            left = right;
        }
        return ret+1;
    }

5. 最长递增子序列(LC 300)

最长递增子序列

题目描述

解题思路

创建一个数组arr,下标对应递增子序列的长度,arr存放这个序列的最后一个元素。

遍历数组时,对每个元素 num:

  • 若 num 大于 arr 中所有元素,直接追加到末尾,说明能形成更长的子序列;
    否则,找到 arr 中第一个大于等于 num 的位置,替换该位置的元素为 num。
  • 最终 arr 的最大下标就是最长递增子序列的长度。

贪心体现在两方面:

  1. 存什么:所有长度为n的递增子序列中,最后一个元素的最小值
  2. 存哪里:第一个找到大于等于nums[i]的位置

对于每一个数都要扫描arr,时间复杂度是O(n2),可以通过二分查找进行优化:按照上面的方法填数,得到的arr数组一定是递增的,只需要找到第一个大于等于 num 的位置。

代码实现

java 复制代码
class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        ArrayList<Integer> ret = new ArrayList<>();
        ret.add(nums[0]);
        for(int i = 1;i<n;i++){
            if(nums[i]>ret.get(ret.size()-1))
                ret.add(nums[i]);
            else{
                int left = 0;
                int right = ret.size();
                while(left<right){
                    int mid = (left+right)/2;
                    if(ret.get(mid)<nums[i])
                        left = mid+1;
                    else
                        right = mid;
                }
                ret.set(left,nums[i]);
            }
        }
        return ret.size();
    }
}

6. 递增的三元子序列(LC 334)

递增的三元子序列

题目描述

解题思路

遇上一题类似,只需要维护一个长度为2的数组,当递增子序列达到3时返回true

注意:

上一题是可变数组,不需要初始化;这题是静态数组,必须都初始化为最大值,保证前两个数都被记录在数组内。

代码实现

java 复制代码
public boolean increasingTriplet(int[] nums) {
        int[] arr = new int[2];
        int n = nums.length;
        arr[0] = arr[1] = Integer.MAX_VALUE;
        for(int i = 0;i<n;i++){
            if(nums[i]<=arr[0])
                arr[0] = nums[i];
            else if(nums[i]<=arr[1])
                arr[1] = nums[i];
            else if(nums[i]>arr[1])
                return true;
        }
        return false;
    }
相关推荐
Godspeed Zhao5 小时前
从零开始学AI17——SVM的数学支撑知识
算法·机器学习·支持向量机
我爱cope5 小时前
【力扣hot100:53. 最大子数组和】
算法·leetcode·职场和发展
Dlrb121114 小时前
C语言-指针三
c语言·算法·指针·const·命令行参数
Tisfy14 小时前
LeetCode 2540.最小公共值:双指针(O(m+n))
算法·leetcode·题解·双指针
IronMurphy15 小时前
【算法四十七】152. 乘积最大子数组
算法
淘矿人16 小时前
Claude辅助DevOps实践
java·大数据·运维·人工智能·算法·bug·devops
Cosolar16 小时前
万字详解:RAG 向量索引算法与向量数据库架构及实战
数据库·人工智能·算法·数据库架构·milvus
落羽的落羽17 小时前
【算法札记】练习 | Week4
linux·服务器·数据结构·c++·人工智能·算法·动态规划