代码随想录36——贪心:435无重叠区间、763划分字母区间、56合并区间

文章目录

  • 1.435无重叠区间
    • 1.1.题目
    • 1.2.解答
  • 2.763划分字母区间
    • 2.1.题目
    • 2.2.解答
  • 3.56合并区间 ==排序区间之后,从前向后 or 从后向前的遍历顺序很重要==
    • 3.1.题目
    • 3.2.解答
      • 3.2.1.错误解法:排序右边界
      • 3.2.2.正确解法:排序左边界

1.435无重叠区间

参考:代码随想录,435无重叠区间,力扣题目链接

1.1.题目

代码随想录36——贪心:435无重叠区间、763划分字母区间、56合并区间_第1张图片

1.2.解答

这道题目和昨天的用箭射气球的问题是很相似的, 其实都是把所有区间进行排列,让区间之间没有相互重叠。

由于区间有首尾两个数值,所以不让区间重叠的方法需要保证首尾都满足要求:

  • 先按照头或者尾把区间进行排列,这样首先已经满足了一个区间边界不重叠了。比如按照尾巴进行排列,那么可以保证排列后的每一个区间,后面的区间的尾巴一定是在前面的区间的尾巴之后的,这样就保证了不重叠
  • 然后遍历每一个区间,保证当前区间的头要在上一个区间的尾巴之后,这样保证当前区间和前一个区间没有重叠。如果不满足,说明有重叠,则直接把当前区间删除;如果没有重叠,则保留当前区间,更新当前区间的尾巴为新的尾巴,再继续遍历下一个区间。

上述过程的图示如下:

代码随想录36——贪心:435无重叠区间、763划分字母区间、56合并区间_第2张图片
注意:本题只是求要移除区间的数量,并没有要真的把区间删除。所以我们只需要统计不重叠的区间的个数,然后把区间总个数 减去 不重叠区间个数,就是最后要移除的区间个数。

代码如下,非常简单:

class Solution
{
private:
    static bool compare(const vector<int> &a, const vector<int> &b)
    {
        return a[1] < b[1]; // 按照区间终点开始排序
    }

public:
    int eraseOverlapIntervals(vector<vector<int>> &intervals)
    {
        // 1.如果是空,则不用移除。这里必须先判断,防止下面sort出错
        if(intervals.empty())
            return 0;   
        // 2.按照区间终点进行排序
        sort(intervals.begin(), intervals.end(), compare);
        int count = 1;   // 最终不重叠的区间个数
        int end = intervals[0][1];   // 上一个区间的尾巴

        for(int i = 1; i < intervals.size(); i++)
        {
            // 如果当前区间的头 >= 上一个区间的尾,则不重叠,保留当前区间
            if(intervals[i][0] >= end)
            {
                end = intervals[i][1];   // 更新当前区间的尾巴为新的尾巴
                count++;  // 累加当前区间,是保留下来的不重叠的区间
            }
        }

        return (intervals.size() - count);  // 总区间 - 保留下的区间 = 要删除的区间
    }
};

2.763划分字母区间

参考:代码随想录,763划分字母区间,力扣题目链接

2.1.题目

代码随想录36——贪心:435无重叠区间、763划分字母区间、56合并区间_第3张图片

2.2.解答

这道题目首先要统计出来每一个字符出现在所给的字符串中的最远位置。

然后遍历每一个字符去寻找一个子串,在遍历这个子串中的每一个字符的过程中,会不断更新这个子串的最远位置。如果遍历到的位置恰好等于最远位置了,说明已经找到这个子串了。

上述过程如下图所示,其实也很简单。

代码随想录36——贪心:435无重叠区间、763划分字母区间、56合并区间_第4张图片

注意:这道题目和之前做过的一道题比较想,也是在遍历数组的过程中不断更新边界的范围。

代码如下:

vector<int> partitionLabels(string s)
{
    int hash[26] = {0};  // 26个字符,出现的末尾位置先都初始化成0
    for(int i = 0; i < s.size(); i++)
        // 直接赋值为i就行,因为如果有重复字符,由于i越来越大,结果就是我们要统计的最远位置
        hash[s[i] - 'a'] = i;  
    
    int left = 0;  // 子串左边界
    int right = 0; // 子串右边界
    vector<int> result;  // 最终结果
    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;
}

3.56合并区间 排序区间之后,从前向后 or 从后向前的遍历顺序很重要

参考:代码随想录,56合并区间;力扣题目链接

3.1.题目

代码随想录36——贪心:435无重叠区间、763划分字母区间、56合并区间_第5张图片

3.2.解答

注意:这道题目里面有坑!前面435无重叠区间的题目使用排序右边界、然后去掉重复的左边界的解法,但是这道题里面就不能这么用!

3.2.1.错误解法:排序右边界

class Solution
{
private:
    static bool compare(const vector<int>& a, const vector<int>& b)
    {
        return a[1] < b[1];
    }

public:
    vector<vector<int>> merge(vector<vector<int>> &intervals)
    {
        vector<vector<int>> result;   // 最终结果

        // 1.如果是空,直接返回结果
        if(intervals.empty())
            return result;
        
        // 2.对区间按照终点进行排序
        sort(intervals.begin(), intervals.end(), compare);

        int left = intervals[0][0];   // 左区间端点
        int right = intervals[0][1];  // 右区间端点

        // 3.遍历所有子区间,寻找不重叠的区间
        for(int i = 1; i < intervals.size(); i++)
        {
            if(intervals[i][0] <= right)  // 当前区间和上一个区间重叠
            {
                left = min(left, intervals[i][0]);   // 注意左边界有可能本次的更小
                right = intervals[i][1];   // 更新终点为当前区间终点
            }
            else   // 当前区间和上一个区间不重叠
            {
                vector<int> ans = {left, right};  // 上一个合并的区间
                result.push_back(ans);

                // 更新新的子区间的左右端点
                left = intervals[i][0];   
                right = intervals[i][1];
            }
        }

        // 出了for循环,要把最后一个子区间加到result中
        vector<int> ans = {left, right};  // 上一个合并的区间
        result.push_back(ans);

        return result;
    }
};

在力扣上跑一下这个代码就会发现错误在哪里,例子是当最后的一个区间是最大的区间的时候,合并结果应该就是这个最大的区间。

注意:目前刚想到,问题应该是出在排列右边界时应该从后往前遍历,而上面自己排列右边界是从前向后遍历。

3.2.2.正确解法:排序左边界

class Solution
{
private:
    static bool compare(const vector<int>& a, const vector<int>& b)
    {
        return a[0] < b[0];
    }

public:
    vector<vector<int>> merge(vector<vector<int>> &intervals)
    {
        vector<vector<int>> result;   // 最终结果

        // 1.如果是空,直接返回结果
        if(intervals.empty())
            return result;
        
        // 2.对区间按照终点进行排序
        sort(intervals.begin(), intervals.end(), compare);

        int left = intervals[0][0];   // 左区间端点
        int right = intervals[0][1];  // 右区间端点

        // 3.遍历所有子区间,寻找不重叠的区间
        for(int i = 1; i < intervals.size(); i++)
        {
            if(intervals[i][0] <= right)  // 当前区间和上一个区间重叠
            {
                right = max(right, intervals[i][1]);  // 更新右边界
            }
            else   // 当前区间和上一个区间不重叠
            {
                result.push_back({left, right});  // 上一个合并的区间
                // 更新新的子区间的左右端点
                left = intervals[i][0];   
                right = intervals[i][1];
            }
        }

        // 出了for循环,要把最后一个子区间加到result中
        result.push_back({left, right});

        return result;
    }
};

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