隐藏
Single

贪心(五)区间问题

55. 跳跃游戏

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。


思路:

贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点

代码如下:

class Solution {

public:

    bool canJump(vector<int>& nums) {

        int cover = 0;

        if (nums.size() == 1) return true; // 只有一个元素,就是能达到

        for (int i = 0; i <= cover; i++) { // 注意这里是小于等于cover

            cover = max(i + nums[i], cover);

            if (cover >= nums.size() - 1) return true; // 说明可以覆盖到终点了

        }

        return false;

    }

};

45. 跳跃游戏 II

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i]
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]


思路:

局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最少步数。

那就是贪在每次要走最大距离,那么就需要不断更新在(i到现在可走最大距离的下标的范围内)下一步的最远距离能到哪里。

代码如下:

class Solution {

public:

    int jump(vector<int>& nums) {

        int curDistance = 0;    // 当前覆盖的最远距离下标

        int ans = 0;            // 记录走的最大步数

        int nextDistance = 0;   // 下一步覆盖的最远距离下标

        for (int i = 0; i < nums.size() - 1; i++) { // 注意这里是小于nums.size() - 1,这是关键所在

            nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖的最远距离下标

            if (i == curDistance) {                 // 遇到当前覆盖的最远距离下标

                curDistance = nextDistance;         // 更新当前覆盖的最远距离下标

                ans++;

            }

        }

        return ans;

    }

};

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

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstartxend 且满足  xstart ≤ x ≤ xend则该气球会被 引爆 可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 


思路:

局部最优:当气球出现重叠,一起射,所用弓箭最少。全局最优:把所有气球射爆所用弓箭最少。

为了让气球尽可能的重叠,需要根据气球起始位置对数组进行排序。然后遍历气球,如果当前气球的终点位置大于下一个气球的起始位置,那就是重叠了。

紧接着就要更新遍历过的气球共同的终点位置,要么是下一个气球的终点(当前气球包含了下一个气球),要么是当前气球的终点。

代码如下:

class Solution {

private:

    static bool cmp(const vector<int>& a, const vector<int>& b) {

        return a[0] < b[0];

    }

public:

    int findMinArrowShots(vector<vector<int>>& points) {

        if (points.size() == 0) return 0;

        sort(points.begin(), points.end(), cmp);

        int result = 1; // points 不为空至少需要一支箭

        for (int i = 1; i < points.size(); i++) {

            if (points[i][0] > points[i - 1][1]) {  // 气球i和气球i-1不挨着,注意这里不是>=

                result++; // 需要一支箭

            }

            else {  // 气球i和气球i-1挨着

                points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界

            }

        }

        return result;

    }

};

435. 无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 

注意 只在一点上接触的区间是 不重叠的。例如 [1, 2] 和 [2, 3] 是不重叠的。

 

示例 1:

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

思路:

这道题和上面气球那道题是一样的,局部最优就是尽可能让数组重叠,对其初始位置排序。全局最优是让去除的数组越少越好。

代码如下:

class Solution {

private:

    static bool cmp(const vector<int>& a,const vector<int>& b){

        return a[0]<b[0];

    }

public:

    int eraseOverlapIntervals(vector<vector<int>>& intervals) {

        int res=0;

        sort(intervals.begin(),intervals.end(),cmp);

        for(int i=1;i<intervals.size();i++){

            if(intervals[i][0]<intervals[i-1][1]){

                res++;

                intervals[i][1]=min(intervals[i-1][1],intervals[i][1]);

            }

        }

        return res;

    }

};

763. 划分字母区间

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

 

示例 1:

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

示例 2:

输入:s = "eccbbbbdec"
输出:[10]

思路:

每次遇到一个字符时,贪心地选择当前字符所在的片段的 最远边界

注意rfind的用法。

代码如下:

class Solution {

public:

    vector<int> partitionLabels(string s) {

        vector<int> res;  

        int rboundary = 0;

        int start = 0;      

       

       

        for (int i = 0; i < s.size(); ++i) {

            // 更新右边界为当前字符最后出现的位置

            rboundary = max(rboundary, (int)s.rfind(s[i]));//(int)是把size_t类型转成int

           

            // 如果当前字符是当前片段的右边界,说明这一段可以划分

            if (i == rboundary) {

                res.push_back(i - start + 1);  

                start = i + 1;  

            }

        }

       

        return res;

    }

};

如果不会用rfind来找边界,就先统计每个字母最后出现的位置。

代码如下:

class Solution {

public:

    vector<int> partitionLabels(string S) {

        int hash[27] = {0}; // i为字符,hash[i]为字符出现的最后位置

        for (int i = 0; i < S.size(); i++) { // 统计每一个字符最后出现的位置

            hash[S[i] - 'a'] = i;//遇到相同的字母会更新该字母最后出现的位置

        }

        vector<int> 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;

    }

};

另外,还可以强行按照气球和划分字母区间的思路来做。

就是要自己构造每个字母对应长度的数组。

代码如下:

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;

    }

};

56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

 

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

思路:

和之前思路一样,也是先排序。

要注意的是:erase会改变数组每个数前移一位,所以每次erase完要i–才能继续遍历。

代码如下:

class Solution {

private:

    static bool cmp(const vector<int>& a, const vector<int>& b) {

        return a[0] < b[0];

    }

public:

    vector<vector<int>> merge(vector<vector<int>>& intervals) {

        sort(intervals.begin(), intervals.end(), cmp);

        for (int i = 1; i < intervals.size(); i++) {

            if (intervals[i][0] <= intervals[i - 1][1]) {

                if (intervals[i][1] < intervals[i - 1][1]) {

                    intervals[i][1] = intervals[i - 1][1];

                }

                intervals[i][0] = intervals[i - 1][0];

                intervals.erase(intervals.begin() + i - 1);

                i--;

            }

        }

        return intervals;

    }

};

 

暂无评论

发表评论

HTMLCOPY