代码随想录算法训练营Day35|贪心part04

140 阅读4分钟

LeetCode 452 用最少数量的箭引爆气球

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

文档讲解:programmercarl.com/0452.用最少数量的…

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

思路

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``startx``end, 且满足  xstart ≤ x ≤ x``end,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

首先为了方便气球重叠,我们对这个二维数组排序。 考虑局部最优策略,直觉上说应该射重叠的地方,以使用更少的箭。可以推出全局最优:射爆全部气球所用的弓箭最少。

我们考虑怎么计算重叠,重叠区间的最右端是这些最右端里的最小值。所以从左至右遍历区间右端点,在出现一个区间左端点大于最小右端之前,把这些有重叠区间的气球全部射爆。

注意溢出:

  1. 排序的时候使用Integer自带的比较法,防止溢出
  2. 循环中需要强制转型为long

解法

class Solution {
	public int findMinArrowShots(int[][] points) {	
		Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));		
		int i = 0;		
		int rightEnd = Integer.MIN_VALUE;		
		int arrows = 0;		
		while (i < points.length) {		
			if (i == 0 || (long)points[i][0] - rightEnd > 0) {			
				arrows++;				
				rightEnd = points[i][1];			
			}			
			while (i < points.length && (long)points[i][0] - rightEnd <= 0) {			
				rightEnd = Math.min(rightEnd, points[i][1]);				
				i++;			
			}			
		}		
		return arrows;	
	}
}

LeetCode 435 无重叠区间

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

文档讲解:programmercarl.com/0435.无重叠区间.…

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

思路

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

首先我们对区间排序,按照左边界排序。然后遍历区间,记录重叠区间的个数。重叠区间指拥有一段长度大于等于1的共同区间的区间集合。此时可以发现这个集合就是LeetCode 452 用最少数量的箭引爆气球中,能用一根箭射爆的气球集合(稍微修改边界条件)。每个集合都可以留下一个区间作为无重叠区间中的一个区间。所以重叠区间的个数就是最后互不重叠的区间个数,用总区间个数减重叠区间个数,就是要移除的区间个数。

按照左边界排序,我们就需要记录重叠区间的最小右端。当区间的左端小于这个最小右端,此区间可以归属于这个重叠区间集合。

解法

class Solution {
	public int eraseOverlapIntervals(int[][] intervals) {	
		Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));		
		int rightEnd = Integer.MIN_VALUE;		
		int cnt = 0;		
		for (int i = 0; i < intervals.length; i++) {		
			if (intervals[i][0] >= rightEnd) {			
				cnt++;				
				rightEnd = intervals[i][1];			
			}			
			else {			
				rightEnd = Math.min(rightEnd, intervals[i][1]);			
			}		
		}		
		return intervals.length - cnt;	
	}
}

LeetCode 763 划分字母区间

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

文档讲解:programmercarl.com/0763.划分字母区间…

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

思路

考虑符合题目要求的划分,同一个字母只能出现在一个区间里,所以我们可以把一个字母在整个字符串上的出现位置抽象成一个区间。区间左端点为第一次出现位置,右端点为最后一次出现位置。那么本题就可以转化为不到26个区间的,求重叠区间集合的个数题目。

在遍历过程中,我们需要更新有重叠区间的字母的最后出现位置作为划分点。故本题可分为如下两步:

  1. 找到每个字母的最后出现位置
  2. 遍历字符串,寻找划分点

解法

class Solution {
	public List<Integer> partitionLabels(String s) {	
		int[] last = new int[26];		
		List<Integer> res = new ArrayList<>();		
		for (int i = 0; i < 26; i++) {		
			// -1标识字母未出现			
			last[i] = -1;		
		}		
		// 寻找最后出现位置		
		for (int i = 0; i < s.length(); i++) {		
			last[s.charAt(i) - 'a'] = i;		
		}		
		int lastEnd = -1;		
		int end = 0;		
		for (int i = 0; i < s.length(); i++) {			
			end = Math.max(last[s.charAt(i) - 'a'], end);			
			if (i == end) {			
				res.add(end - lastEnd);				
				lastEnd = end;			
			}		
		}		
		return res;	
	}
}

今日收获总结

今日学习时长2小时,因为都没思路所以很快看题解而早早完工的。今天的问题主要是思路和溢出,使用自带的compare可以避免溢出。贪心的思路只能常看了,类似题目要灵活运用