代码随想录算法训练营第三十六天_第八章_贪心算法 | 435. 无重叠区间、763.划分字母区间、56. 合并区间

LeetCdoe 435. 无重叠区间

        给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:  区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
文章讲解https://programmercarl.com/0435.%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.html
  • 思路:按左边界从小到大排序,intervals[i] 每跟现有区间重叠一次,就需要移除至少一个区间
    • 局部最优:每遇到一个与现有[..., intervals[i - 1][1]]重叠的区间,就移除右边界大的那个
    • 全局最优:需要移除区间的总数最小
  • 代码:
class Solution {
private:
    static bool cmp(const vector& a, const vector& b) {
        return a[0] < b[0];
    }
public:
    int eraseOverlapIntervals(vector>& intervals) {
        // 按start从小到大排列
        sort(intervals.begin(), intervals.end(), cmp);
        // 统计重叠区间数
        int result = 0;
        for (int i = 1; i < intervals.size(); ++i) {
            if (intervals[i][0] < intervals[i - 1][1]) {    // 重叠
                ++result;    // 移除一个重叠区间
                intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]);    // 保留右边界小的那个重叠区间
            }
        }
        return result;
    }
};

LeetCdoe 763.划分字母区间

        字符串 s 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

​​​​​​​输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
        划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
        每个字母最多出现在一个片段中。
        像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。

文章讲解https://programmercarl.com/0763.%E5%88%92%E5%88%86%E5%AD%97%E6%AF%8D%E5%8C%BA%E9%97%B4.html

代码随想录算法训练营第三十六天_第八章_贪心算法 | 435. 无重叠区间、763.划分字母区间、56. 合并区间_第1张图片

  • 思路:
    • 统计每一个字符最后出现的位置
    • 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点(有点像跳跃游戏每一步的覆盖范围
  • 代码:
// 代码随想录:
class Solution {
public:
    vector partitionLabels(string s) {
        int hash[26] = {0}; // i为字符,hash[i]为字符出现的最后位置
        for (int i = 0; i < s.size(); ++i) { // 统计每一个字符最后出现的位置
            hash[s[i] - 'a'] = i;
        }
        vector result;
        int left = 0;
        int right = 0;
        for (int i = 0; i < s.size(); ++i) {
            right = max(right, hash[s[i] - 'a']); // 找到字符出现的最远边界
            if (i == right) {
                result.push_back(right - left + 1);
                left = i + 1;
            }
        }
        return result;
    }
};
// 时间复杂度:$O(n)$
// 空间复杂度:$O(1)$,使用的hash数组是固定大小
// 自己:
class Solution {
private:
    static bool cmp(const vector& a, const vector& b) {
        return a[0] < b[0];
    }
public:
    vector partitionLabels(string s) {
        vector result;
        if (s.size() == 0) return result;
        // 统计26个小写字母出现下标[start, end]
        // -1 表示未出现
        vector> letters(26, vector(2, -1));
        for (int i = 0; i < s.size(); ++i) {
            int index = s[i] - 'a';
            if (letters[index][0] != -1) {  // 当前字母出现过,更新右边界
                letters[index][1] = i;
            } else {  // 当前字母未出现,更新左右边界
                letters[index][0] = i;
                letters[index][1] = i;
            }
        }
        // 按start从小到大排序
        sort(letters.begin(), letters.end(), cmp);
        int i = 0;
        while (letters[i][0] == -1 && i < 26) {
            ++i;
        }
        if (i == 26) return result;
        // i定位到有效部分
        int left = letters[i][0];
        int right = letters[i][1];
        for (int j = i + 1; j < 26; ++j) {
            if (right < letters[j][0]) {    // 不重叠
                // 收获现有[left, right]区间长度
                result.push_back(right + 1 - left);
                // 更新重叠区间
                left = letters[j][0];
                right = letters[j][1];
            } else {    // 合并重叠区间
                left = min(letters[j][0], left);
                right = max(letters[j][1], right);
            }
        }
        // 收获最后一个[left, right]区间长度
        result.push_back(right + 1 - left);
        return result;
    }
};

LeetCdoe 56. 合并区间

给出一个区间的集合,请合并所有重叠的区间。
  • 输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
  • 输出: [[1,6],[8,10],[15,18]]
  • 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
文章讲解https://programmercarl.com/0056.%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.html
  • 思路:
    • 按左边界从小到大排序
    • 记录当前区间[left, right]
    • 若intervals[i]的左边界 < right出现重叠区间合并(只用更新右边界,取大)

代码随想录算法训练营第三十六天_第八章_贪心算法 | 435. 无重叠区间、763.划分字母区间、56. 合并区间_第2张图片​​​​​​​

  • 代码:
// 代码随想录:
class Solution {
public:
    vector> merge(vector>& intervals) {
        vector> result;
        if (intervals.size() == 0) return result;
        // 排序的参数使用了lambda表达式
        sort(intervals.begin(), intervals.end(), [](const vector& a, const vector& b){return a[0] < b[0];});

        result.push_back(intervals[0]);
        for (int i = 1; i < intervals.size(); i++) {
            if (result.back()[1] >= intervals[i][0]) { // 合并区间
                result.back()[1] = max(result.back()[1], intervals[i][1]);
            } else { // 不重叠,直接收获
                result.push_back(intervals[i]);
            }
        }
        return result;
    }
}
// 时间复杂度:O(nlog n) ,有一个快排
// 空间复杂度:O(n),有一个快排,最差情况(倒序)时,需要n次递归调用。因此确实需要O(n)的栈空间
// 自己:
class Solution {
private:
    static bool cmp(const vector& a, const vector& b) {
        return a[0] < b[0];
    }
public:
    vector> merge(vector>& intervals) {
        vector> result;
        // 按start从小到大排序
        sort(intervals.begin(), intervals.end(), cmp);
        int left = intervals[0][0];
        int right = intervals[0][1];
        for (int i = 1; i < intervals.size(); ++i) {
            if (right < intervals[i][0]) {  // 不重叠
                // [left, right]不用再合并,加入结果集
                result.push_back({left, right});
                // 更新现有区间
                left = intervals[i][0];
                right = intervals[i][1];
            } else {    // 重叠
                // 合并重叠区间
                right = max(intervals[i][1], right);
            }
        }
        // 收获最后一个[left, right]
        result.push_back({left, right});
        return result;
    }
};

你可能感兴趣的:(贪心算法,leetcode)