贪心入门
概述:
贪心算法是一种在每一步选择中都采取当前最优解的策略,希望最终能够得到全局最优解的算法。简单来说,它会不断地做出局部最优的选择,相信通过这种选择最终能够达到全局最优。
举个例子来说明。假设你要从一个迷宫的起点走到终点,每个格子都有一个代价,你要找到一条路径,使得总代价最小。贪心算法会在每一步选择下一步的格子时,选择代价最小的格子,然后继续向着终点移动。这样每一步都选择当前最优的格子,最终就能够找到一条总代价最小的路径。()
不过需要注意的是,贪心算法并不一定能够得到全局最优解,因为它只考虑当前步骤的最优选择,并没有考虑整体的情况。所以在应用贪心算法时,需要仔细分析问题的特征,确保贪心策略适用,并且通过数学证明或实验验证来证明其正确性。
举个简单的例子
有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?
指定每次拿最大的,最终结果就是拿走最大数额的钱。
即每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。
贪心算法一般分为如下四步:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
(过于理想化)
引入例题:
分发饼干455
https://leetcode.cn/problems/assign-cookies/
若干个子问题就是每个饼淦要怎么分。
最优的是大饼干分给胃口大的,能一口吃饱,或者从小的开始,小饼干喂饱小的,能一口吃饱。
全局最优就是喂饱尽可能多的小孩。
即:
java:
class Solution {
// 思路1:优先考虑饼干,小饼干先喂饱小胃口
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);//从小到大排序
int start = 0;
int count = 0;
//嘴不变,饼干变
for (int i = 0; i < s.length && start < g.length; i++) {//意思是胃口大就换大一点的饼干,小饼干就直接不要了
if (s[i] >= g[start]) {
start++;
count++;
}
}
return count;
}
}
摆动序列376
https://leetcode.cn/problems/wiggle-subsequence/
解析:乍一看直接炸裂,还能自己随意改动数组,这怎么想;
其实可以把数组转化成图像来看待,那么摆动的就是一上一下一上一下,不摆动就是连续上或者连续下;
即:
其实可以有一个中间变量值,每次记录nums[i]减去nums[i-1]的正负,
1.下一个和上一个比较如果是相同,不计数,不同的计数并改变正负,
2.如果,一个正负值,一个为0,记录正负值的那个数不变;相当于直接跳过
3.只有两个点时,直接判断是否相同,不同为2,相同为1;
基本思路都有了,现在就是写代码了;
java
class Solution {
public int wiggleMaxLength(int[] nums) {
int a=1;
int s=-1;//上一个结果:1表示正数,0表示负数,-1表示初始值
int h=-1;//本次结果,和s同理;
int count=1;//初始值,之后记录s改变的次数;
if (nums.length==1)
return 1;
if (nums.length==2) {
if (nums[0] == nums[1])
return 1;
else return 2;
}
//长度为1,2的直接判断;
for (int i = 1; i < nums.length; i++) {
if (nums[i]-nums[i-1]>0)//为正数;
{ h=1;
if(s!=h){
count++;
s=h;
}
}
if (nums[i]-nums[i-1]<0)
{
h=0;
if(s!=h){
count++;
s=h;
}
}
}
return count;
}
}
//不错哟
其实用动态规划也可以写这道题目。并且更容易理解,
先复习下动态规划知识:动态规划是一种通过将原问题分解为相对简单的子问题来解决的方法,并将结果存储起来以避免重复计算。在动态规划中,通常需要定义状态以及状态转移方程。
因为要求摆动次数,可以以摆动次数为动归的结果,摆动为一上一下一上一下这种,使用上一次推出下一次,那么可以使用up[]表示上升次数,down[]表示下降次数;则上升次数=下降次数+1,下降次数=上升次数+1;
只用判断最新的是上升还是下降即num[i]和num[i-1]的关系就行了;
代码:
public class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length < 2) {
return nums.length;
}
int[] up = new int[nums.length];
int[] down = new int[nums.length];
up[0] = 1;
down[0] = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] > nums[i - 1]) {
up[i] = down[i - 1] + 1;
down[i] = down[i - 1];
} else if (nums[i] < nums[i - 1]) {
down[i] = up[i - 1] + 1;
up[i] = up[i - 1];
} else {
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return Math.max(up[nums.length - 1], down[nums.length - 1]);
}
}
最大子数组和53
https://leetcode.cn/problems/maximum-subarray/
有两种方法,其实动态规划很好做,不过这是贪心专题,还是好好讲贪心;
贪心主题思想是:局部最优推出全局最优,那么
想要局部最优即和肯定是正数,为负数时直接从新开始;
即和为正数时就一直往后加和i对应值作比较,并持续记录和最大值,和为负数时就直接从新开始;
代码:
class Solution {
public int maxSubArray(int[] nums) {
int count=0;
int max=-10001;
for (int i = 0; i <nums.length ; i++) {
if (count<0)
count=nums[i];
else count=count+nums[i];
max=Math.max(max,count);
}
return max;
}
}
并不复杂;
买卖股票的最佳时机122
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
使用贪心和动态规划都可以做,不过这道题是典型的贪心解决更容易理解;
首先看懂题目,两天的收入,可以看成第一天买第二天卖,三天的收入能看成。。。
即
假如第 0 天买入,第 3 天卖出,那么利润为:prices[3] - prices[0]。
相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。
看懂了把,完全可以分解成每一天来看待;
那么从第一天到最后一天就能看成
java:
class Solution {
public int maxProfit(int[] prices) {
int a=0;
int max=0;
for (int i = 1; i <prices.length ; i++) {
a=prices[i]-prices[i-1];
if (a>0)
max=max+a;
}
return max;
}
}
所以说,有些时候找对方法很重要;
跳跃游戏55
https://leetcode.cn/problems/jump-game/
思路:
看这个图就懂了,与其说能跳多远,其实就是覆盖范围有多少,即可以写出每个下标对应范围合到一个共同的范围,最后检测范围的最右端,如果刚好是最后一个下标就说明全覆盖,小于就说明覆盖不到
java代码:
class Solution {
public boolean canJump(int[] nums) {
int a=0;//设置a为目前能到达的最远端;
for (int i = 0; i <nums.length ; i++) {
a=Math.max(i+nums[i],a);//不断更新a的值,一直取最大
if (a==i&&i<nums.length-1)//说明此时最远端和下标重合了,即没法往前再走了,此时比较下标和数组长度,小于说明没有到数组边界;
return false;
}
return true;
}
}
跳跃游戏II 45
https://leetcode.cn/problems/jump-game-ii/
做了上一道题之后,其实这个应该会直接有思路,仍然是使用覆盖范围的方式思考,
以上边示例为例,即第一步跳的范围是1到2,选出跳1或2能到达的最大下标其实就是局部最优,一直选跳下一步可以获得的最大覆盖,(选3能覆盖到下标4的位置,选1只能覆盖到下标2的位置),这就是本题思路;
java代码:
class Solution {
public int jump(int[] nums) {
if (nums.length==1||nums.length==0)
{ return 0;}
int jumps = 0; // 跳跃次数
int max ; //在当前能够到达的范围内,使得下一次跳跃最远的位置;
int nextIndex; //下一次跳跃的索引
int i = 0;
while (i < nums.length) {
if (i + nums[i] >= nums.length - 1) {
// 如果下一次跳跃的位置已经超过了或等于最后一个位置,则到达终点
jumps++;
break;
}
max=0;
nextIndex=i;
// 找到当前位置能够到达的范围内,使得下一次跳跃最远的位置
for (int j = i; j <= i + nums[i]; j++) {
if (j + nums[j] > max) {
max = j + nums[j];
nextIndex = j;
}
}
// 更新下一次跳跃的位置和跳跃次数
i = nextIndex;
jumps++;
}
return jumps;
}
}