LeetCode之用C++写贪心算法

目录

题名:分发饼干

描述:

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

说明:

你可以假设胃口值为正。

一个小朋友最多只能拥有一块饼干。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/assign-cookies/

方法:贪心

贪心规律

1.某个饼干如果不能满足某个孩子,则该饼干也一定不能满足需求因子更大的孩子

2.某个孩子可以用更小的饼干满足,则没必要用更大饼干满足,因为可以保留更大的饼干

3.孩子的需求因子更小则其更容易被满足,故优先从需求因子小的孩子尝试

4.用某个饼干,满足一个较大需求因子的孩子,或满足一个较小需求因子的孩子,效果是一样的(最终满足的总量不变)

代码如下:


int findContentChildren(vector<int>& g, vector<int>& s) {
        int ans = 0;
        //对孩子的胃口和饼干大小按升序排序,然后依据贪心规律进行模拟
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int p = 0, q = 0;
        while(q < s.size()){
            if(p >= g.size())   break;//p >= g.size() 表示孩子都被满足了但饼干还有的多,此时要退出循环了
            if(s[q] >= g[p]){
                ans++;
                p++;
            }
            q++;
        }
        return ans;
    }

Leetcode #435 无重叠区间

题名:无重叠区间

描述:

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

说明:

可以认为区间的终点总是大于它的起点。

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


输入: [ [1,2], [2,3], [3,4], [1,3] ]

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/non-overlapping-intervals/

方法:贪心

正确的思路其实很简单,可以分为以下三步:

1.在区间集合 intervals 中选择一个区间 x,这个 x 是在当前所有区间中结束最早的(区间右边界最小),因此可以对 intervals 按照单个区间的右边界从小到大排序,例如 [ [1,2], [2,3], [3,4], [1,3] ],经过排序后成为 [ [1,2], [2,3], [1,3], [3,4] ]

2.把所有与 x 区间相交的区间视为有重叠的区间,那么怎么判断相交呢?可以通过区间的左边界,如果后一个区间的左边界值小于前一个区间的右边界值,则视为相交;用

noOverlapCount 用于记录不重叠区间的数量 。

3.重复步骤 1 和 2,直到将 intervals 遍历完为止。然后用用 intervals 中的区间总数减去 noOverlapCount 即是答案;

代码如下:


static bool cmp(vector<int> a, vector<int> b){
        return a[1] < b[1];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if(!intervals.size())   return 0;//如果intervals为空啥都不干,返回0即可
        sort(intervals.begin(), intervals.end(), cmp);//步骤1
        int noOverlapCount = 1, index = 0, p = 1;
        while(index + p < intervals.size()){
            if(intervals[index + p][0] >= intervals[index][1]){//步骤2
                noOverlapCount++;
                index += p;
                p = 1;
            }  
            else    p++;
        }
        return intervals.size() - noOverlapCount;//步骤3
    }

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

题名:用最少数量的箭引爆气球

描述:

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

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

Example


输入:
[[10,16], [2,8], [1,6], [7,12]]

输出:
2

解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/

方法:贪心

这一题与上面一题 Leetcode #435 无重叠区间 类似,直接上代码:


    static bool cmp(vector<int> a, vector<int> b){
        return a[1] < b[1];
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        if(!points.size())  return 0;
        int ans = 1, p = 1, anchor = 0;//anchor 用于锚定,p用于移动
        sort(points.begin(), points.end(), cmp);
        while(anchor + p < points.size()){
            if(points[anchor + p][0] <= points[anchor][1]){//将多个重叠的气球视为一个气球,只需要一支箭
                p++;
            }
            else{
                ans++;
                anchor += p;
                p = 1;
            }
        }
        return ans;
    }

Leetcode #406 根据身高重建队列

题名:根据身高重建队列

描述:

假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对 (h, k) 表示,其中 h 是这个人的身高, k 是排在这个人前面且身高大于或等于 h 的人数。 编写一个算法来重建这个队列。

注意:

总人数少于1100人。

Example


输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/queue-reconstruction-by-height/

方法:贪心

对于身高比较高的人来说,比他矮的人他是‘看不见的’, 所以我们可以先对同一身高的人群按k值从小到大排序;再按身高从大到小按k值插入ans中。

代码如下:


    static bool cmp(vector<int> a, vector<int> b){//排序规则
        if(a[0] != b[0])    return a[0] > b[0];
        return a[1] < b[1];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(), people.end(), cmp);
        vector<vector<int> > ans;
        int index = 0;
        while(index < people.size()){
            ans.insert(ans.begin() + people[index][1], people[index]);
            index++;
        }
        return ans;
    }

Leetcode #121 买卖股票的最佳时机

题名:买卖股票的最佳时机

描述:

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

Example1


输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

Example2


输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

方法:贪心

我总是期望在股票价格最低的时候入手,在最高的时候抛出。


    int maxProfit(vector<int>& prices) {
        if(!prices.size())  return 0;
        int shareVal = prices[0], maxP = 0;
        for(int i = 0;i < prices.size();i++){
            if(shareVal > prices[i]){//如果有价值更低的股票,我就入手
                shareVal = prices[i];
            }
            else if(shareVal < prices[i]){//如果有股票价格变高了我就抛出
                int tmp = prices[i] - shareVal;
                if(tmp > maxP)  maxP = tmp;//记录下最大利益
            }
        }
        return maxP;
    }

Leetcode #122 买卖股票的最佳时机 II

题名:买卖股票的最佳时机 II

描述:

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)

Example1


输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

Example2


输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/

方法:贪心

股票买卖策略:

1.单独交易日:设今天价格p1, 明天价格p2,则今天买入,明天卖出可赚取金额 p2 - p1 (负值代表亏损)。

2.连续上涨交易日:设此上涨交易日股票价格分别为 'p1, p2, p3,..., pn',则第一天买最后一天麦受益最大。即 `pn - p1 = (p2 - p1) + (p3 - p2) +...+(pn - pn-1)。

3.连续下降交易日:则不买卖收益最大,即不会亏钱。

代码如下:


    int maxProfit(vector<int>& prices) {
        int res = 0;
        for(int i = 1;i < prices.size();i++){
            int profit = prices[i] - prices[i - 1];
            if(profit > 0)  res += profit;
        }
        return res;
    }

Leetcode #392 判断子序列

题名:判断子序列

描述:

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

Example1

s = "abc", t = "ahbgdc"

返回 true.

Example2

s = "axc", t = "ahbgdc"

返回 false.

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/is-subsequence/

方法:从头到尾遍历

双指针,用sp指向段字符串,tp指向长字符串。tp不断向后移,匹配到相等的sp向后移,因为不是严格意义上的子串,只要顺序一致就可以,所以sp不需要回溯。用sp是否超出s字符串长度来判断是否找到子串。代码如下:


    bool isSubsequence(string s, string t) {
        string::iterator sp ,tp;
        sp = s.begin(); tp = t.begin();
        while(sp != s.end() && tp != t.end()){
            if(*sp == *tp){
                sp++;
            }
            tp++;
        }
        return sp == s.end();
    }

Leetcode #665 非递减数列

题名:非递减数列

描述:

给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中所有的 i (1 <= i < n),总满足 array[i] <= array[i + 1]。

方法:贪心

这种问题首先需要对特殊数据进行分析,从特殊到一般,总结出规律来,在进行编程!

数据一: 4, 2, 3

1.对于这串数据,当比较到4, 2时,出现了4 > 2的情况,那么是把4改成2还是把2改成4呢?我们人类很好判断出只要把4改成<=2的数即可;因为后面出现了3,把2改成4的话,后面的数就不符合了!这里就总结出了一条规律:> 当nums[i - 1] > nums[i]时,并且i - 1是第一个数时,令nums[i - 1] = nums[i];

2.并且总结处到出现nums[i - 1] > nums[i]的情况时,需要考虑到底是令nums[i - 1] = nums[i],还是nums[i] = nums[i - 1]

数据二: 3, 4, 2, 4

1.当我们比较到4, 2时,出现了nums[i - 1] > nums[i]的情况,如果令nums[i - 1] = nums[i],就变成了3, 2, 2, 4明显不对;如果令nums[i] = nums[i - 1],就变成了3, 4, 4, 4就对了;从这里我们发现nums[i] 不仅要与 nums[i - 1]比较还要跟nums[i - 2]比较;

从以上的归纳得出的结论:

1.出现nums[i - 1] > nums[i]需要将nums[i]nums[i - 1]nums[i - 2]进行比较;不然的话直接令i++;

2.当nums[i - 2] > nums[i - 1]时,令nums[i] = nums[i - 1];反之令令nums[i - 1] = nums[i]

代码如下:


    bool checkPossibility(vector<int>& nums) {
        int count = 1, end = nums.size();
        for(int i= 1;i < end;i++){
            if(nums[i - 1] <= nums[i])  continue;
            count--;
            if(i - 2 >= 0 && nums[i - 2] > nums[i])
                nums[i] = nums[i - 1];
            else  nums[i - 1] = nums[i];
            if(count < 0)   return false;
        }
        return true;
    }

Leetcode #763 划分字母区间

题名:划分字母区间

描述:

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

Example1

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

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/partition-labels/

方法:贪心 + STL库的利用

要想划分的片段最多,那么就是利用贪心的思想,当划分出的字符串s1, s1里面的字符不再次在S中出现时,立马就将其从整个S中划分出来。

利用STL中的set和string容器。

代码如下:


    vector<int> partitionLabels(string S) {
        vector<int> ans;
        set<char> st;//set是集合,在这个容器里面没有重复的元素
        string str;//利用好string容器的find方法;
        int index = 0;
        while(index < S.size()){
            bool flag = true;
            str += S[index];
            st.insert(S[index]);
            for(auto a : st){
                if(index + 1 < S.size() && S.find(a, index + 1) != string::npos){  //size_t string::find(const char c, size_t pos) const noexcept;
                    flag = false;
                    break; 
                }
            }
            if(flag){
                ans.push_back(str.size());
                str.clear();
            }
            index++;
        }
        return ans;
    }