贪心策略解决柠檬水找零问题和将数组和减半的最少操作次数问题

力扣原题链接:
将数组和减半的最少操作次数
柠檬水找零力扣链接
本篇基础:三个场景入门贪心算法
学而时习之:数据结构之优先级队列(堆)
编程语言:java

一:柠檬水找零问题

题目

在这里插入图片描述在这里插入图片描述

你卖柠檬水,最开始你一分钱都没有。
顾客买你的柠檬水。顾客只能付5元,10元,20元。
分情况讨论:
顾客付5元时,你收下5元。
顾客付10元时,你有5元,给顾客找5元,收下10元;你没有5元,返回false。
顾客付20元时,你有找零的钱,你给顾客找三张五元,或者你给顾客找一张十元和一张五元,收下20元。你没有找零的钱,返回false。

贪心策略解题

此题的贪心策略体现在这里:
在这里插入图片描述
给付给你20元的顾客找零钱,就是一个该问题的一个子步骤
这时有三种情况,

  1. 你有一张十元和两张五元,
  2. 你有三张五元,
  3. 你没有足够找零的五元十元,返回false

此时根据根据贪心策略,当情况1和2都满足时,优先选择情况1。
该贪心策略是正确的策略(证明在后面),所以如果你在代码中先选择情况2在选择情况1,代码是不能跑过的。1

思考如何从题解到代码

  1. 最开始你一分钱都没有
    在这里插入图片描述
  2. bills[i]代表顾客付的钱,bills[i]只能是5,10,20。一步一步处理每一位顾客付的钱,即遍历数组bills。
    在这里插入图片描述
  3. 其中收钱和找钱分别就是five++或–,ten++或–,twelve++或–。
    在这里插入图片描述

java实现代码

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

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

题目

在这里插入图片描述在这里插入图片描述

贪心策略解题

操作最少次,将数组和减半的贪心策略:

  1. 每次操作的时候,都选择当前数组中最大的那个数,减半。(贪法)
  2. 直到当前数组和小于原来数组和一半,操作停下。
  3. 最后希望此解是该题的最优解。

思考如何从题解到代码

  1. 每次操作的时候,为了快速挑出数组中最大的数,数组传过来以后,先建立大根堆,把所有元素丢进堆中,并计算数组和一半是多少,后面的每次减半操作同样建立在堆上进行。
    在这里插入图片描述
  2. 先思考一下:如果是你:每次操作选择最大数进行减半,减半后如何判断是否继续减半操作呢?2
    上一步已经求出了数组和一半是多少sum,每次操作取出堆顶元素减半后,用sum减去堆顶元素的一半,sum>0时,把堆顶元素一半放回堆中继续减半操作。当sum<=0时,即停止操作。
    在这里插入图片描述
  1. 创建变量count记录操作次数
    在这里插入图片描述

java实现代码

import java.util.PriorityQueue;
class Solution {
    public int halveArray(int[] nums) {
        PriorityQueue<Double> heap = new PriorityQueue<>((a,b) ->b.compareTo(a));
        double sum = 0.0;
        for(int x:nums){
            heap.offer((double)x);
            //这里记得强制类型转换:堆中元素类型double,nums中元素类型int
            sum += x;
        }
        sum /= 2;//数组和一半
        int count = 0;
        while(sum > 0){
            double t = heap.poll() / 2.0;
            sum-=t;
            count++;
            heap.offer(t);
        }
        return count;
    }
}

三:交换论证法分别证明以上两个问题中的贪心策略是正确的

交换论证法:

在不破坏最优解的“最优性质”的前提下,能将最优解调整成贪心解(最要是看最优解和贪心解不一样的地方),则可以说明该贪心策略是正确的。

用贪心算法解决问题的时候,我们会把解决问题的过程分成若干步,每一步都会采用当前最优算法。

证明柠檬水找零问题贪心法是正确的

在柠檬水找零问题中,每一步代表的是:给每一位顾客找零钱。

假设:
在贪心解中,每一步的最优方法分别是:a,b,c,d,e,f。
在最优解中,每一步的最优方法分别是:e,b,c,d,a,f。
此时,只要我们能在不破坏最优解的“最优性质”前提下,把e和a交换,说明最优解和贪心解等价,即可证明柠檬水找零问题中我们的贪心策略是正确的
在这里插入图片描述

当给付5元,或10元的顾客找零钱时,显然无论是最优解还是贪心解中,这一步采取的方法都是一样的。即
在这里插入图片描述
在这里插入图片描述

当给付20元的顾客找零钱时,当你只能找给顾客一张十元和一张五元的时候,或你没有找零的钱的时候,此时贪心解和最优解选择的方法也是一样的。
在这里插入图片描述

但当你既能找给顾客三张五元,又能找给顾客一张十元和一张五元的时候,

  • 贪心解中必定优先选择后者,
  • 最优解中可能选择前者,也可能选择后者。

此时当最优解中选择前者时,我们能否在不破坏最优解的优质前提下,调整最优解,使其和贪心解等价呢?当然可以。

  • 调整情况1:最优解中,如果最优解后面都没用过10元,这里直接用10元代替两张五元。
    很明显调整成功,因为后面的最优解中根本没用过十元(说明这十元与最优解的最优性质无关),所以此刻我拿来用,既没破坏最优解,也达到我调整的目的,一举双得。
    在这里插入图片描述
  • 调整情况2:最优解中,如果最优解后面操作中用过十元,先把后面用的那十元拿过来代替两张五元。
    到后面用那张十元的时候再用两张五元(说明后面直接和贪心法吻合),同样调整成功。

只要贪心解和最优解在对应步骤中,选择的找钱方法不一样,都可以通过这两种调整方法进行调整。最终让最优解等价于贪心解。

证明完成。

证明数组和减半****问题中的贪心策略是正确的

在将数组和减半的最少操作次数问题中,每一步代表的是:选择当前数组中最大的那个数进行减半。

假设:
贪心解中选择的数依次是:a,b,c,d,e,f。
最优解中选择的数依次是:e,b,c,d,a,f。
就在假设中,第一步,贪心解和最优解选择的数不一样,但是因为贪心解的每一步都是选择当前最优解,所以我们可知:a一定大于e。
我们尝试调整最优解:在不影响最优性质的情况下,把e调整成a。

  • 调整情况1:如果最优解的后面没有出现a,此时直接把e换成a(a没出现说明a与最优解的性质无关),达成不影响** ,等价于**两个目的。
  • 调整情况2:如果最优解的后面出现a,直接把后面的a和e交换位置。因为在最优解中,先对a减半操作还是先对e减半操作,对问题最终求的操作次数并没有影响。如果在最优解中存在e/2,e/4等的减半操作,只要把e/2,e/4等一起和e调到后面即可。

只要贪心解和最优解在对应步骤中,选择的数不一样,都可以通过这两种调整方法进行调整。最终让最优解等价于贪心解。

证明完毕。

本篇完结~


  1. 在这里插入图片描述 ↩︎

  2. 我第一次想到的是:数组中最大数减半后,把减半后的数放入堆中,此时再计算此时堆中所有元素和,看这次和是否小于原数组和的一半。但如何计算堆中所有元素的和呢???在这里插入图片描述 ↩︎

  • 12
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值