代码随想录算法训练营Day32|贪心part01

123 阅读5分钟

贪心算法理论基础

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

什么时候用贪心

如果局部最优👉整体最优 验证方法:

  1. 举反例
  2. 反证法
  3. 数学归纳法

贪心解题步骤(可能没用

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

LeetCode 455 分发饼干

题目链接:leetcode.cn/problems/as…

文档讲解:programmercarl.com/0455.分发饼干.h…

视频讲解:www.bilibili.com/video/BV1MM…

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是满足尽可能多的孩子,并输出这个最大数值。

思路

为了满足尽可能多的孩子,我们应该优先给胃口小的孩子饼干,并且使用尽可能小的饼干

算法

  1. 对孩子的胃口g和饼干尺寸s非递减排序,得到满足的孩子个数count=0
  2. 对g和s用指针i和j遍历
    1. 对每个g[i],不断向后移动j直到出现一个s[j] >= g[i],count+1

解法

class Solution {	
	public int findContentChildren(int[] g, int[] s) {	
		Arrays.sort(g);		
		Arrays.sort(s);		
		int i = 0;		
		int j = 0;		
		int count = 0;		
		for (; i < g.length && j < s.length; i++) {		
			while (j < s.length && s[j] < g[i]) {			
				j++;			
			}			
			if (j >= s.length) {			
				break;			
			}			
			else {			
				count++;			
			}			
			j++;		
		}		
		return count;	
	}
}

LeetCode 376 摆动序列

题目链接:leetcode.cn/problems/wi…

文档讲解:programmercarl.com/0376.摆动序列.h…

视频讲解:www.bilibili.com/video/BV17M…

思路

需要删除一些元素得到摆动序列,我们考虑删除何种元素:

  • 整体最优:序列上有最多的局部峰值
  • 局部最优:只保留单调坡度两端的节点

如何判断局部峰值? 字面意义上,在递减区出现的最小值和在递增区出现的最大值

序列两端如何处理? 由于仅有一个元素或者含两个不等元素的序列也视作摆动序列,长度分别为1和2,我们可以单独处理。 且我们考虑任意一个长度大于2的序列,其长度必定大于等于2.因为根据我们局部峰值的判断,两端都是局部峰值。

平坡如何处理?

  1. 对于一个单调坡➕平坡,等同于一个单调坡。但这个单调坡必须在平坡之前,这是为了确定单调坡的方向,提前知道这个合并后的单调坡是什么类型,避免一个纯平坡被判断为单调坡
  2. 对于纯平坡,视为一个数字,所以只需要移动指针,不需要处理别的。直到遇到下一个单调坡或者索引不合法。
    1. 如果遇到下一个单调坡,此时指针的位置就是单调坡的起点,这和直接进入单调坡+平坡情况相同
    2. 如果索引不合法,由于平坡的最后一个数字不能加入结果,所以也不需要处理
  3. 举例说明,如果序列为[1,2,2,1],我们看作一个递增区加[1,2,2]一个递减区[2,1]。如果序列为[1,2,2,2]我们看作一个递增区,如果序列为[2,2,2,1],我们看作一个平坡➕一个递减区

算法

  1. 结果result初始化为1
  2. 如果序列长度为1或长度为2且元素相等,返回
  3. 如果长度等于2,返回2
  4. 初始化指针i为1,当i合法
    1. 如果nums[i]>nums[i-1]说明在递增区
      1. while nums[i]>=nums[i-1],且i合法,i+1
      2. 退出while说明进入了递减区或到达序列右端,不论哪种情况我们都找到了局部峰值,result+1
    2. 如果i不合法,返回result
    3. 如果nums[i]==nums[i-1],且i合法,说明在平区,i+1
    4. 如果i不合法,返回result
    5. 如果nums[i]<nums[i-1],说明在递减区
      1. while nums[i]=<nums[i-1],且i合法,i+1
      2. 退出while说明进入了递增区或到达序列右端,不论哪种情况我们都找到了局部峰值,result+1

解法

class Solution {
	public int wiggleMaxLength(int[] nums) {	
		int result = 1;		
		if (nums.length == 1) {		
			return 1;		
		}		
		if (nums.length == 2) {		
			if (nums[1] == nums[0]) {			
				return 1;			
			}			
			else {			
				return 2;			
			}		
		}		
		int i = 1;		
		while (i < nums.length) {		
			if (nums[i] > nums[i-1]) {			
				while (i < nums.length && nums[i] >= nums[i-1]) {				
					i++;				
				}				
				result++;			
			}			
			if (i >= nums.length) {			
				break;			
			}			
			while (i < nums.length && nums[i]==nums[i-1]) {			
				i++;			
			}			
			if (i >= nums.length) {			
				break;			
			}			
			if (nums[i] < nums[i-1]) {			
				while (i < nums.length && nums[i] <= nums[i-1]) {				
					i++;				
				}				
				result++;			
			}		
		}		
		return result;	
	}
}

解释

这个代码结构可以理解为,当区间类型改变时,选择一个while循环进入,不同的循环分别代表递增区(含递增区+平坡),纯平坡,递减区(含递减区+平坡)

LeetCode 53 最大子序和

题目链接:leetcode.cn/problems/ma…

文档讲解:programmercarl.com/0053.最大子序和.…

视频讲解:www.bilibili.com/video/BV1aY…

思路

  • 局部最优:抛弃和为负的子序列,解决子序列开始的问题。为了得到最大子序和,我们还要记录下历史最大和
  • 全局最优:尽量少选择负数,多选择正数

算法

  1. sum=0,maxSum=0
  2. 遍历每个元素nums[i]
    1. 如果sum<0,重置sum为0
    2. sum+=nums[i]
    3. 更新maxSum

解法

class Solution {
	public int maxSubArray(int[] nums) {
		int sum = 0;	
		int maxSum = Integer.MIN_VALUE;	
		for (int i = 0; i < nums.length; i++) {	
			if (sum < 0) {		
				sum = 0;		
			}		
			sum += nums[i];		
			if (sum > maxSum) {		
				maxSum = sum;		
			}	
		}	
		return maxSum;	
	}
}

今日收获总结

今日学习时长2小时,贪心算法真的挺没规律的,多练题!