代码随想录算法训练营第26天 | 第八章 贪心算法 part04

第八章 贪心算法 part04

今天的三道题目,都算是 重叠区间 问题,大家可以好好感受一下。都属于那种看起来很复杂,但一看贪心解法,惊呼:这么巧妙!

这种题还是属于那种,做过了也就会了,没做过就很难想出来。

不过大家把如下三题做了之后,重叠区间 基本上差不多了。


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

题目链接

class Solution {
public:
static bool cmp(const vector<int>&a,const vector<int>&b){
            return a[1] < b[1];     
}
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(),points.end(),cmp);
        int right=points[0][1];
        int arrow=1;
        for(int i=1;i<points.size();i++)
        {
            if(points[i][0]>right)
            {
                arrow++;
                right=points[i][1];
            }
        }
        return arrow;
    }
};

在这里插入图片描述
直接把这张图看一遍即可,能看懂基本上就没什么问题了.
最开始看完思路, 我写的代码排序是按左边界排序的,左边相同按右边界排序。然而,正确的贪心策略应该是先排序所有气球的右边界,然后判断下一个气球是否能够被当前的箭头射中。因为,如果按照我的思路,如果按照我的思路,只需要一支箭,实际上,需要四支箭
[1, 10], [2, 3], [4, 5], [6, 7], [8, 9]
所以应该按照右边界排序
[[2, 3], [4, 5], [6, 7], [8, 9], [1, 10]]
按右边界排序确保你每次都选择最小的右边界,即射中当前气球组中结束最早的气球。

435. 无重叠区间

题目链接

class Solution {
public:
static bool cmp(const vector<int>&a,const vector<int>&b){
    if( a[0] == b[0])
    return a[1] > b[1];
    return a[0] < b[0];       
}
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),cmp);
        
        int num=0;
        int start=intervals[0][0];
        int end=intervals[0][1];
        for(int i=1;i<intervals.size();i++)
        {
            if(intervals[i][0]>=start&&intervals[i][1]<=end)
                num++;       
            else if(intervals[i][0]<=end&&intervals[i][1]>end)
            end=intervals[i][1];
            else if(intervals[i][0]>end)
            {
                start=intervals[i][0];
                 end=intervals[i][1];
            }
        }
        return num;
    
    }
};

最开始写的代码如上面所示,结果发现,这不是最小,反而是最大的删除区间.比如下面的数组,删除[1,2],[2,3], 实际上只要删除[1,3]. 这个代码就是尽可能扩展范围,在范围内的统统删掉.确实
[[1,2],[2,3],[3,4],[1,3]].这题最大的难度,还是怎么确保最少,就很头疼.
转换思路,最后用区间总数减去非交叉区间的个数就是需要移除的区间个数了。所以要转换思路.最开始按照左边界排序,按左边界排序在很多情况下能工作,但按右边界排序是一种更好的贪心策略,因为这能够确保选择右边界最早结束的区间,从而避免不必要的重叠,进一步减少删除区间的数量。

class Solution {
public:
static bool cmp(const vector<int>&a,const vector<int>&b){
   
    return a[1] < b[1];
     
}
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),cmp);    
        int num=1;
        int end=intervals[0][1];
        for(int i=1;i<intervals.size();i++)
        {
            if(intervals[i][0]>=end)
               {
                num++;   
               end=intervals[i][1];
               }     
        }
        return intervals.size()-num;
    }
};

真正的按左边界如下所示:最重要的还是这句语法min(end, intervals[i][1]);选择最小范围.

class Solution {
public:
    static bool cmp (const vector<int>& a, const vector<int>& b) {
        return a[0] < b[0]; // 改为左边界排序
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if (intervals.size() == 0) return 0;
        sort(intervals.begin(), intervals.end(), cmp);
        int count = 0; // 注意这里从0开始,因为是记录重叠区间
        int end = intervals[0][1]; // 记录区间分割点
        for (int i = 1; i < intervals.size(); i++) {   
            if (intervals[i][0] >= end)  end = intervals[i][1]; // 无重叠的情况
            else { // 重叠情况 
                end = min(end, intervals[i][1]);
                count++;
            }
        }
        return count;
    }
};

763. 划分字母区间

读题目都脑筋急转弯了一下,就是同一个字母在同一个字符串中,最多可以划分为多少层. 这么一思考,发现下面的思路确实很清晰. 遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了
统计每一个字符最后出现的位置, 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点

class Solution {
public:
    vector<int> partitionLabels(string s) {
        vector<int> hash(26,0);
        for(int i=0;i<s.size();i++)
        {
            hash[s[i]-'a']=i;
        }
        vector<int> result;
        int left = 0;
        int right = 0;
        int length=0;
        for(int i=0;i<s.size();i++)
        {
            length++;
            right = max(right, hash[s[i] - 'a']); 
            if(i==right)
            {
                result.push_back(length);
                length=0;
            } 
        }
        return result;
    }
};

整体代码还是比较简单的, 没有太多弯弯绕绕,就是找自己这个区间最右边界,一直更新,直到追上了.
题目链接
在这里插入图片描述
另一种代码思路:统计字符串中所有字符的起始和结束位置,记录这些区间(实际上也就是435.无重叠区间 (opens new window)题目里的输入),将区间按左边界从小到大排序,找到边界将区间划分成组,互不重叠。找到的边界就是答案。卧槽, 成功把简单问题复杂化了.
确实也是一种思路,第一个函数构造之前题目中的输入的数组. 找到各个字符串所在的区间, 之后的函数就负责找到分割点,一旦发现区间不连续了,就分割. 思路很清晰, 代码就不难,但是确实也是够麻烦.

class Solution {
public:
    static bool cmp(vector<int> &a, vector<int> &b) {
        return a[0] < b[0];
    }
    // 记录每个字母出现的区间
    vector<vector<int>> countLabels(string s) {
        vector<vector<int>> hash(26, vector<int>(2, INT_MIN));
        vector<vector<int>> hash_filter;
        for (int i = 0; i < s.size(); ++i) {
            if (hash[s[i] - 'a'][0] == INT_MIN) {
                hash[s[i] - 'a'][0] = i;
            }
            hash[s[i] - 'a'][1] = i;
        }
        // 去除字符串中未出现的字母所占用区间
        for (int i = 0; i < hash.size(); ++i) {
            if (hash[i][0] != INT_MIN) {
                hash_filter.push_back(hash[i]);
            }
        }
        return hash_filter;
    }
    vector<int> partitionLabels(string s) {
        vector<int> res;
        // 这一步得到的 hash 即为无重叠区间题意中的输入样例格式:区间列表
        // 只不过现在我们要求的是区间分割点
        vector<vector<int>> hash = countLabels(s);
        // 按照左边界从小到大排序
        sort(hash.begin(), hash.end(), cmp);
        // 记录最大右边界
        int rightBoard = hash[0][1];
        int leftBoard = 0;
        for (int i = 1; i < hash.size(); ++i) {
            // 由于字符串一定能分割,因此,
            // 一旦下一区间左边界大于当前右边界,即可认为出现分割点
            if (hash[i][0] > rightBoard) {
                res.push_back(rightBoard - leftBoard + 1);
                leftBoard = hash[i][0];
            }
            rightBoard = max(rightBoard, hash[i][1]);
        }
        // 最右端
        res.push_back(rightBoard - leftBoard + 1);
        return res;
    }
};

代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值