Day30-贪心算法,leetcode452,435,763
- 用最少数量的箭引爆气球
-
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。
-
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
-
给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
- 思路
- 局部最优:当气球出现重叠,一起射,所用弓箭最少。全局最优:把所有气球射爆所用弓箭最少。
- 如果把气球排序之后,从前到后遍历气球,被射过的气球仅仅跳过就行了,没有必要让气球数组remove气球,只要记录一下箭的数量就可以了。重复的气球尽量用一个弓箭来射。
- i的左边界和i-1的右边界是否有重叠,因为需要和前一个进行比较,所以i从1开始,遍历过程中遇到非重叠情况,弓箭数++,若重叠的话,更新右边界,看下一轮和下一个i+1是否有重叠,没有重叠,弓箭数++。
/**
1. 排序:先按左端点排序,方便后续判断区间重叠。
2. 遍历:从第二个气球开始,判断和前一个气球是否重叠。
不重叠(points[i][0] > points[i-1][1]):需要新的一支箭,result++。
重叠:说明可以用同一支箭射穿,更新当前气球的右端点为重叠区间的最小右端点(这样后续判断更精确)。
3. 最终结果:返回箭的数量。
每次遇到不重叠的区间就加一支箭,重叠的区间只用一支箭。
用贪心思想,保证每支箭尽量射穿更多气球。
每遇到不重叠的气球区间就加一支箭,重叠的气球只用一支箭,最终返回箭的最小数量。
*/
var findMinArrowShots = function(points) {
// 按区间左端点升序排序
points.sort((a, b) => {
return a[0] - b[0]
})
// 至少需要一支箭
let result = 1
for(let i = 1; i < points.length; i++) {
// 如果当前气球的左端点大于前一个气球的右端点,说明不重叠,需要新的一支箭
if(points[i][0] > points[i - 1][1]) {
result++
} else {
// 如果有重叠,更新当前气球的右端点为重叠区间的最小右端点
points[i][1] = Math.min(points[i - 1][1], points[i][1])
}
}
return result
};
- 无重叠区间
-
给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
-
注意 只在一点上接触的区间是 不重叠的。例如 [1, 2] 和 [2, 3] 是不重叠的。
- 思路
- 按照左边界排序,遍历,记录重叠区间的个数或者非重叠区间数,总数➖非重叠区间数
/**
1. 排序:先按区间左端点升序排序,方便后续判断重叠。
2. 遍历:从第二个区间开始,判断当前区间和前一个区间是否重叠(即当前区间的左端点 < 前一个区间的右端点)。
3. 重叠处理:
如果重叠,说明必须移除一个区间。这里通过把当前区间的右端点更新为两个区间右端点的较小值,保留右端点小的区间,减少后续重叠的可能性。
count++,记录需要移除的区间数。
4. 返回:最终返回需要移除的区间数量 count。
每遇到重叠就移除一个区间(保留右端点小的),统计总共需要移除多少个区间,使剩下的区间互不重叠。
*/
var eraseOverlapIntervals = function(intervals) {
if (intervals.length === 0) return 0;
// 按左端点升序排序
intervals.sort((a, b) => a[0] - b[0]);
let count = 0; // 记录重叠区间的数量
for (let i = 1; i < intervals.length; i++) {
// 如果当前区间的左端点 < 前一个区间的右端点,说明重叠
if (intervals[i][0] < intervals[i - 1][1]) {
// 合并重叠区间,保留右端点较小的那个,减少后续重叠
// [1, 5] 和 [2, 4],它们重叠。如果保留右端点小的 [2, 4],后面区间更容易不重叠。所以更新为 [2, 4],即 intervals[i][1] = Math.min(5, 4) = 4。
intervals[i][1] = Math.min(intervals[i - 1][1], intervals[i][1]);
count++;
}
}
return count;
};
/**
排序:先按区间左端点升序排序,方便后续判断重叠。
遍历:从第二个区间开始,判断和前一个区间是否重叠。
不重叠:intervals[i][0] >= intervals[i-1][1],计数加一。
重叠:更新当前区间的右端点为两个区间右端点的较小值(Math.min),这样后续更容易不重叠。
结果:用总区间数减去不重叠区间数,得到最少要移除的区间数。
每遇到重叠就“合并”区间,保留右端点小的,统计最多能选的不重叠区间,最后用总数减去就是最少要移除的区间数。
每遇到重叠就保留右端点小的区间,统计最多不重叠区间数,最后用总数减去就是最少要移除的区间数。
*/
var eraseOverlapIntervals = function(intervals) {
intervals.sort((a, b) => a[0] - b[0]); // 1. 按左端点升序排序
let count = 1; // 2. 记录不重叠区间的数量,初始选第一个区间
if (intervals.length === 0) return 0; // 边界处理
for (let i = 1; i < intervals.length; i++) {
// 3. 如果当前区间的左端点 >= 前一个区间的右端点,说明不重叠,可以选上
if (intervals[i][0] >= intervals[i - 1][1]) {
count++;
} else {
// 4. 如果重叠,保留右端点较小的区间,减少后续重叠
intervals[i][1] = Math.min(intervals[i - 1][1], intervals[i][1]);
}
}
// 5. 总区间数 - 不重叠区间数 = 需要移除的区间数
return intervals.length - count;
};
- 划分字母区间
-
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"] 或 ["ab", "ab", "cc"] 的划分是非法的。
-
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
-
返回一个表示每个字符串片段的长度的列表。
- 思路
- 先记录每个元素出现的最远距离,根据最远距离决定区间的分界线
/**
1. hash 记录每个字符最后一次出现的位置
这样可以知道当前区间内所有字符的最远右边界。
2. 遍历字符串,动态更新 right
每遇到一个字符,就把 right 更新为当前字符最后一次出现的位置和当前 right 的较大值。
3. 遇到 right === i 时分割区间
说明从 left 到 i 这个区间内的所有字符都不会再出现在后面,可以安全分割。
4. 记录区间长度,更新 left
把当前区间长度加入结果,并把 left 更新为下一个区间的起点。
每次遇到 right === i 就分割区间,保证每个字母只出现在一个片段中,最终返回所有片段的长度。
*/
var partitionLabels = function(s) {
let hash = {};
// 1. 记录每个字符最后一次出现的位置
for(let i = 0; i < s.length; i++) {
hash[s[i]] = i;
}
let result = [];
let left = 0;
let right = 0;
// 2. 遍历字符串,动态维护当前区间的最远右边界
for(let i = 0; i < s.length; i++) {
right = Math.max(right, hash[s[i]]);
// 3. 如果当前下标等于区间最远右边界,说明可以分割
if(right === i) {
result.push(right - left + 1); // 记录当前区间长度
left = i + 1; // 更新下一个区间的起点
}
}
return result;
};
参考&感谢各路大神
宝剑锋从磨砺出,梅花香自苦寒来。

浙公网安备 33010602011771号