贪心专题

1 (dump game)给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
在这里插入图片描述
(从前往后跳) O(n)
每遍历到一个数,就计算一下从这个数开始跳所能到达的最远距离
如果遍历到i已经超过了前面计算出的最远距离,则无法到达
如果计算出的最远距离超过数组的最后一个元素位置,则可以到达

class Solution {
public:
    bool canJump(vector<int>& nums) {
        for(int i=0,maxdump=0;i<nums.size();i++){
            if(i>maxdump)return false;
            if(maxdump>=nums.size()-1)return true;

            maxdump=max(maxdump,i+nums[i]);
        }
        return true;
    }
};

2.(dump 2) 给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。

(动态规划,贪心优化) O(n)
首先定义两个指针 last 和 i,数组 f[i] 表示到达 i 所需要的最少步数。
定义 last 为第一次到达 i 时上一步的位置,last 从 0 开始。
根据贪心得知,令 f[i] = f[last] + 1 后,f[i] 就会是最优值。
故可以根据 i 来让 last 向后移动,找到最早的可以一步到达 i 的位置,然后根据 f[last] 更新 f[i]。

class Solution {
public:
    int jump(vector<int>& nums) {
        int n=nums.size();
        int f[n];
        memset(f,0,sizeof f);

        f[0]=0;
        int last=0;
        for(int i=1;i<n;i++){
            while(i>nums[last]+last)
                last++;
            f[i]=f[last]+1;
        }

        return f[n-1];
    }
};

3(Lemonade Change) 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false。

在这里插入图片描述

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int five=0,ten=0;
        for(int bill:bills){
            if(bill==5){
                five++;
            }
            if(bill==10){
                if(five>=1){
                    ten++;
                    five--;
                }
                else
                    return false;
            }

            if(bill==20){
                if(ten>=1&&five>=1){
                    ten--;
                    five--;
                }
                else if(five>=3){
                    five-=3;
                }
                else
                    return false;
            }
        }
        return true;
    }
};

4 (Is Subsequence) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

(双指针+贪心) O(n+m)
题解1:双指针做法。使用i索引当前匹配到s[i],j索引当前匹配到t[j]。时间复杂度O(n+m)

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int n=s.size(),m=t.size();
        int i=0;
        for(int j=0;i<n&&j<m;i++,j++){
            while(j<m&&t[j]!=s[i])
            {
                j++;
            }
            if(j==m)return false;
        }
        return i==n;;
    }
};

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

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());

        int res=0;
        for(int i=0,j=0;i<g.size()&&j<s.size();){
            if(g[i]<=s[j]){
                res++;
                i++;
                j++;
            }
            else{
                j++;
            }
        }

        return res;
    }
};

6(Wiggle Subsequence)如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

(贪心) O(n)

  • 首先对nums进行相邻去重,即用stl中的unique函数去除容器中相邻的重复元素(注意它是把重复元素放在容器末尾,返回值为去重之后的尾地址),再用erase函数进行删除
  • 用一个for loop找极值点:如图,在一段连续上升(或下降)的线段中,只保留端点,可用反证法证明端点是最佳选择(如果选其他的,可能会使序列变短)。
    在这里插入图片描述
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int res=2;
        
        nums.erase(unique(nums.begin(),nums.end()),nums.end());
        if(nums.size()<=2)return nums.size();
        
        for(int i=1;i+1<nums.size();i++){
            if(nums[i]>nums[i-1]&&nums[i]>nums[i+1])
                res++;
            else if(nums[i]<nums[i-1]&&nums[i]<nums[i+1])
                res++;
        }
        return res;
    }
};

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

(贪心) O(n2)
思路:身高高的人只会看到比他高的人,所以当身高高的人固定好了位置,前面插入多少个矮的人都不会破坏高的人的条件限制。所以应该先决定高的人的位置,再决定矮的人的位置;高的人限制条件少,矮的人限制条件多。

  • 先按身高从大到小排序,身高一样则按照k排序:身高大或k小意味着限制条件少,应该被优先考虑。
  • 依次插入元素:由上一点,先进入res的元素不会被后进入的元素影响,因此每一次插入只需要考虑自己不需要考虑别人。当遍历到元素[a,b]的时候,比它大的元素已经进组,比它小的元素还没进组,那么它应该插到res的第b位,从而实现0到b-1的数字都比它大。

举例,输入是[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

排序后是[[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]]

插入[7,0], res=[[7,0]]

插入[7,1], res=[[7,0],[7,1]]

插入[6,1], res=[[7,0],[6,1],[7,1]]

插入[5,0], res=[[5,0],[7,0],[6,1],[7,1]]

插入[5,2], res=[[5,0],[7,0],[5,2],[6,1],[7,1]]

插入[4,4], res=[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

bool cmp(const vector<int>&a,const vector<int>&b)
{
    return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
}//注意写在class外
class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        int n=people.size();
        
        sort(people.begin(),people.end(),cmp);
        
        vector<vector<int>> res;
        for(auto p:people)
        {
            res.insert(res.begin()+p[1],p);
        }
        
        return res;
    }
};

8(Minimum Number of Arrows to Burst Balloons) 在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在 104104 个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 x_start,x_end, 且满足 x_start <= x <= x_end,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

(排序贪心) O(nlogn)
此题可以考虑将区间求交集,最后必定是一些不重叠的独立的区间,独立的区间个数就是答案数。具体做法如下:

  • 首先将区间按照左端点从小到大排序,左端点相同的按照右端点从小到大排序。
  • 设立两个值 start 和 end 代表当前飞镖可以放置的范围。
  • 每当遇到一个新区间,若 end 小于新区间的起点,则需要一个新飞镖。否则原飞镖的区间 start 和新区间的起点取最大值,end和新区间的终点取最小值,即求交集。
class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        int n=points.size();
        if(n==0) return 0;
        
        sort(points.begin(),points.end());
        
        int res=1,start=points[0][0],end=points[0][1];
        
        for(int i=1;i<n;i++){
            if(points[i][0]>end){
                res++;
                start=points[i][0];
                end=points[i][1];
            }
            else {
                start=max(start,points[i][0]);
                end=min(end,points[i][1]);
            }
        }
        return res;
    }
};

9(Remove K Digits ) 给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:
num 的长度小于 10002 且 ≥ k。
num 不会包含任何前导零。

在这里插入图片描述
(单调栈,贪心)
如果我们当前遇到的数字比上一个数字要小的话,肯定是删除上一个数字比较划算。我们最多能删除k个字符。所以我们使用一个单调栈来存储每一个字符,如果当前读进来的数字比前一个数字小,我们就将栈顶元素出栈,直至出栈了k个字符或者栈顶元素已经比当前元素还小。这样在我们删除k个元素后,栈中元素就是剩下的数字啦。这时候我们需要考虑的就是删除前导0和空栈的情况啦。字符串有push和pop操作,所以我们可以直接用字符串来模拟栈,效果是一样的。

class Solution {
public:
    string removeKdigits(string num, int k) {
        stack<int> stk;
        int n=num.size();
        for(int i=0;i<n;i++){
            while(stk.size()&&stk.top()>num[i]&&k){
                stk.pop();
                k--;
            }
            
            stk.push(num[i]);
        }
        
        while(k--)
            stk.pop();
         
        string res="";
        while(stk.size()){
            res+=stk.top();
            stk.pop();
        }
        reverse(res.begin(),res.end());
        
        int i=0;
        while(i<res.size()&&res[i]=='0')
            i++;
        
        res=res.substr(i);
        return res==""?"0":res;//可能全零那么字符串变为空,但要输出“0”
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值