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

文章目录

算法介绍

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

  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. 存哪里:第一个找到大于等于numsi的位置

对于每一个数都要扫描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;
    }
相关推荐
倒头就睡的小比特4 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼4 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光4 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark4 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her14 天前
并查集-听课笔记
笔记·算法·并查集
码流子4 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven4 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark4 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218614 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法