贪心策略解决柠檬水找零问题和将数组和减半的最少操作次数问题
力扣原题链接:
将数组和减半的最少操作次数
柠檬水找零力扣链接
本篇基础:三个场景入门贪心算法
学而时习之:数据结构之优先级队列(堆)
编程语言:java
一:柠檬水找零问题
题目
你卖柠檬水,最开始你一分钱都没有。
顾客买你的柠檬水。顾客只能付5元,10元,20元。
分情况讨论:
顾客付5元时,你收下5元。
顾客付10元时,你有5元,给顾客找5元,收下10元;你没有5元,返回false。
顾客付20元时,你有找零的钱,你给顾客找三张五元,或者你给顾客找一张十元和一张五元,收下20元。你没有找零的钱,返回false。
贪心策略解题
此题的贪心策略体现在这里:
给付给你20元的顾客找零钱,就是一个该问题的一个子步骤,
这时有三种情况,
- 你有一张十元和两张五元,
- 你有三张五元,
- 你没有足够找零的五元十元,返回false
此时根据根据贪心策略,当情况1和2都满足时,优先选择情况1。
该贪心策略是正确的策略(证明在后面),所以如果你在代码中先选择情况2在选择情况1,代码是不能跑过的。1
思考如何从题解到代码
- 最开始你一分钱都没有
- bills[i]代表顾客付的钱,bills[i]只能是5,10,20。一步一步处理每一位顾客付的钱,即遍历数组bills。
- 其中收钱和找钱分别就是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;
}
}
二:将数组和减半的最少操作次数
题目
贪心策略解题
操作最少次,将数组和减半的贪心策略:
- 每次操作的时候,都选择当前数组中最大的那个数,减半。(贪法)
- 直到当前数组和小于原来数组和一半,操作停下。
- 最后希望此解是该题的最优解。
思考如何从题解到代码
- 每次操作的时候,为了快速挑出数组中最大的数,数组传过来以后,先建立大根堆,把所有元素丢进堆中,并计算数组和一半是多少,后面的每次减半操作同样建立在堆上进行。
- 先思考一下:如果是你:每次操作选择最大数进行减半,减半后如何判断是否继续减半操作呢?2
上一步已经求出了数组和一半是多少sum,每次操作取出堆顶元素减半后,用sum减去堆顶元素的一半,sum>0时,把堆顶元素一半放回堆中继续减半操作。当sum<=0时,即停止操作。
- 创建变量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调到后面即可。
只要贪心解和最优解在对应步骤中,选择的数不一样,都可以通过这两种调整方法进行调整。最终让最优解等价于贪心解。
证明完毕。
本篇完结~