代码随想录算法训练营Day33|贪心part02

92 阅读4分钟

LeetCode 122 买卖股票的最佳时机II

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

文档讲解:programmercarl.com/0122.买卖股票的最…

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

思路

  • 局部最优:只要明天比今天股价高,今天就买入,明天就卖出
  • 全局最优:考虑一个买入点buy和一个卖出点sell,如果两点之间单调非递减,那么利润是sell-buy。如果两点之间存在波动,有递减区间,那么区间可以划分为多个单调非递减区间,且区间端点之差的和大于sell-buy。所以我们可以推导出只需要在每个局部最长的单调非递减区间的左端买入,右端卖出,就可以得到最高利润。

解法

class Solution {	
	public int maxProfit(int[] prices) {	
		int profit = 0;		
		if (prices.length == 1) {		
			return 0;		
		}		
		int i = 0;		
		int buy = 0;		
		int sell = 0;		
		while (i < prices.length - 1) {		
			if (i < prices.length - 1 && prices[i+1] > prices[i]) {			
				buy = prices[i];				
				while (i < prices.length - 1 && prices[i+1] >= prices[i]) {				
					i++;				
				}				
				sell = prices[i];				
				profit += sell - buy;				
				buy = 0;				
				sell = 0;			
			}			
			else {			
				i++;			
			}		
		}		
		return profit;	
	}
}

LeetCode 55 跳跃游戏

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

文档讲解:programmercarl.com/0055.跳跃游戏.h…

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

思路

本题的关键是把题目转化为求解跳跃范围是否能覆盖到终点的问题。

  • 贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围)
  • 整体最优解:最后得到整体最大覆盖范围,看是否能到终点。

算法

  1. 初始化最大覆盖索引cover=0
  2. 遍历每个数字nums[i]
    1. 如果cover小于i,说明前面的数字覆盖的范围不包含当前索引,退出循环
    2. 如果i+nums[i]大于cover,更新cover
    3. 此时可以判断一下cover是否覆盖了终点,覆盖了就退出循环因为已经知道结果。也可以不判断一直遍历到最后一个数字。
  3. 如果cover大于等于nums.length-1,则可以到终点,否则不可以

解法

class Solution {
	public boolean canJump(int[] nums) {	
		int cover = 0;		
		for (int i = 0; i < nums.length; i++) {		
			if (cover < i) {			
				break;			
			}			
			cover = Math.max(cover, i + nums[i]);			
			if (cover >= nums.length-1) {			
				break;			
			}		
		}		
		if (cover >= nums.length-1) {		
			return true;		
		}		
		return false;	
	}
}

LeetCode 45 跳跃游戏 II

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

文档讲解:programmercarl.com/0045.跳跃游戏II…

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

思路

为了能计算最小步数,我们需要统计每一步的覆盖范围,而不是数组整体的覆盖范围。 需要维护两个变量,一个是索引i当前步数的覆盖范围curr,一个是下一步的覆盖范围next。当i超过curr,说明走到i必须要多走一步,此时步数要+1。

具体来说,在遍历i时,如果i处于curr中,i+nums[i]都属于下一步的覆盖范围next 当i越过了curr,当前步数覆盖范围curr需要更新为next,而下一步覆盖范围next需要更新为i+nums[i]

考虑初始化,第0步的覆盖范围是0

算法

  1. 初始化steps=0,curr=0,next=0
  2. 对每个索引i遍历
    1. 如果i小于等于curr,说明还在当前步数,next更新为next和i+nums[i]的最大值
    2. 如果i大于curr,steps+1,curr=next,next=i+nums[i]
    3. 如果curr大于等于nums.length-1,说明当前步数覆盖了终点,返回steps

解法

class Solution {
	public int jump(int[] nums) {	
		int steps = 0;		
		int curr = 0;		
		int next = 0;		
		for (int i = 0; i < nums.length; i++) {		
			if (i <= curr) {			
				next = Math.max(next, i+nums[i]);			
			}			
			else {			
				steps++;				
				curr = next;				
				next = i+nums[i];			
			}			
			if (curr >= nums.length) {			
				return steps;			
			}		
		}		
		return steps;	
	}
}

LeetCode 1005 K次取反后最大化的数组和

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

文档讲解:programmercarl.com/1005.K次取反后最…

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

思路

由于我们只需要返回最大和,所以可以对数组排序。 考虑排序后的数组,负数是绝对值大的靠前,正数绝对值小的靠前。

考虑k次取反的三种情况:

  1. 数组负数个数大于等于k:为了制造出更多绝对值较大的正数,取前k个负数取反
  2. 数组全为正数:由于可以选择同一数字,我们选择最小的数字k次取反。因为如果k为偶数,数组仍然全为正;如果k为奇数,我们只制造出最大的负数
  3. 数组负数个数小于k:假设有m个负数,先使用m次取反得到全正数组后,转为情况2处理

解法

class Solution {
	public int largestSumAfterKNegations(int[] nums, int k) {	
		int left = k;		
		Arrays.sort(nums);		
		int i = 0;
		
		while (i < nums.length && nums[i] < 0 && left > 0) {		
			nums[i] = -nums[i];			
			left--;			
			i++;		
		}		
		if (left == 0) {		
			return computeSum(nums);		
		}		
		else if (left < k) {		
			// 有负数变正数			
			Arrays.sort(nums);		
		}		
		if (left % 2 == 0) {		
			return computeSum(nums);		
		}		
		else {		
			return computeSum(nums) - 2 * nums[0];		
		}	
	}		  
	
	public int computeSum(int[] nums) {	
		int sum = 0;		
		for (int j : nums) {		
			sum += j;		
		}		
		return sum;	
	}
}

今日收获总结

今日学习2小时,跳跃游戏系列要把问题转化为求覆盖范围