文章目录
- 算法介绍
- [1. 柠檬水找零(LC 860)](#1. 柠檬水找零(LC 860))
- [2. 将数组和减半的最少操作次数(LC 2208)](#2. 将数组和减半的最少操作次数(LC 2208))
- [3. 最大数(LC 179)](#3. 最大数(LC 179))
- [4. 摆动序列(LC 376)](#4. 摆动序列(LC 376))
- [5. 最长递增子序列(LC 300)](#5. 最长递增子序列(LC 300))
- [6. 递增的三元子序列(LC 334)](#6. 递增的三元子序列(LC 334))
算法介绍
贪心策略是一种解决问题的策略,通过局部最优解得到总体最优解
- 把解决问题的问题分为若干步
- 解决每一步都选择当前看起来"最优"的解法,目光短浅,鼠目寸光(🫣)
- 希望得到全局最优解
贪心的正确性:贪心策略不一定正确,需要严谨的数据证明
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 的最大下标就是最长递增子序列的长度。
贪心体现在两方面:
- 存什么:所有长度为n的递增子序列中,最后一个元素的最小值
- 存哪里:第一个找到大于等于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;
}