Day30-贪心算法,leetcode452,435,763

  1. 用最少数量的箭引爆气球
  • 有一些球形气球贴在一堵用 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
};


  1. 无重叠区间
  • 给定一个区间的集合 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;
};


  1. 划分字母区间
  • 给你一个字符串 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;
};



参考&感谢各路大神

posted @ 2025-06-26 09:13  安静的嘶吼  阅读(8)  评论(0)    收藏  举报