LeetCode-贪心算法

1. 题号860. 柠檬水找零

本题贪心之处在于面对20块钱优先找10快钱付
这个代码真简洁,只要进行每次的找零操作,把判断放在了最后,只要5块钱个数小于零(说明不够用了)返回false,没有那么多冗余代码

bool lemonadeChange(vector<int>& bills) {
        int n=bills.size(),count5=0,count10=0;
        for(int m:bills){
            if(m==5){
                count5++;
            }else if(m==10){
                count5--;
                count10++;
            }else if(count10>0){
                count10--;
                count5--;
            }else{
                count5-=3;
            }
            if(count5<0){
                return false;
            }
        }
        return true;
    }

2. 题号455. 分发饼干

本题贪心之处在于用最小的饼干满足需求最小的孩子,所以要先排序
别人的代码真简洁,用孩子索引表示满足的孩子个数,当有一个数组遍历完就跳出循环
孩子只有在满足条件时候动,保证是需求最小的孩子,不断遍历饼干元素给孩子

int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int i=0,j=0,n=g.size(),m=s.size();
        while(i<n && j<m){
            if(g[i]<=s[j]){
                i++;
            }
            j++;
        }
        return i;
    }

3. 题号1217. 玩筹码

奇数到奇数不消耗,偶数到偶数不消耗,把偶数都移动到0,奇数都移动到1,此时奇偶数中小的数量就是消耗的代价(需要整体移动1)

int minCostToMoveChips(vector<int>& position) {
        int j=0,o=0;
        for(int i:position){
            if(i%2==0){
                o++;
            }else{
                j++;
            }
        }
        return j>o ? o:j;
    }

4. 题号1046. 最后一块石头的重量

用优先队列,当剩余元素大于等于2时继续,出队列两个元素,如果粉碎结果不唯一就放结果到队列
出循环时元素数量小于等于1,没有元素时返回0,有元素返回top元素即可

int lastStoneWeight(vector<int>& stones) {
        priority_queue<int> q;
        for(int i:stones){
            q.push(i);
        }
        while(q.size()>1){
            int a=q.top();
            q.pop();
            int b=q.top();
            q.pop();
            if(a-b!=0){
                q.push(a-b);
            }
        }
        return q.empty() ? 0:q.top();
    }

5. 题号23. 合并K个升序链表

第一道手打的困难题,没想到一次就通过了,开心
需要自定义比较函数,小顶堆要用大于号
dummy哑结点,p用于遍历
把每个链表头结点都放入优先队列(注意判空),弹出堆顶元素接入p后面,将堆顶元素的后继节点加入队列(注意判空),直到队列空为止

	struct cmp{		比较函数,小顶堆要用大于号
        bool operator()(ListNode* a,ListNode* b){
            return a->val > b->val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*,vector<ListNode*>,cmp> q;
        ListNode* dummy=new ListNode(-1);
        ListNode* p=dummy;
        for(auto p:lists){
            if(p){
                q.push(p);
            }
        }
        while(!q.empty()){
            ListNode* cur=q.top();	获取堆顶元素
            q.pop();		弹出
            if(cur->next){		如果下一个元素不为空就加入队列
                q.push(cur->next);
            }
            p->next=cur;	接入答案链表
            p=cur;
        }
        return dummy->next;
    }

6. 题号605. 种花问题

贪心在尽可能多种花

bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        int length=flowerbed.size(),i=0;
        while(i<length && n>0){		超范围和不符合条件进循环
            if(flowerbed[i]==1){	
            这个位置已经有花了,所以他的右边也种不了,直接+2
                i+=2;
            }else if(i==length-1 || flowerbed[i+1]==0){
            这个位置没种花,他的右边也没有花(包含花在右边界的位置)
                n--;
                i+=2;
            }else{	这个位置没花,同时他的右边也有花,直接+3
                i+=3;
            }
        }
        return n==0;
    }

7. 题号944. 删列造序

就是找存在降序的列(就是需要删除的),找到count++就好

int minDeletionSize(vector<string>& A) {
        if(!A.size()) return 0;
        int count = 0;
        for(int i=0;i<A[0].size();i++){ //列,由第一个字符串长度决定
            for(int j=0;j<A.size()-1;j++){  //行,由数组内元素个数决定
                if(A[j][i] > A[j+1][i]){
                    count++;
                    break;
                }
            }
        }
        return count;
    }

8. 题号435. 无重叠区间

贪心在尽可能排列最多的活动数量
就是排列活动问题,最少取消多少活动,就是求总数-最多能举办的活动
先根据右区间排序,这样所剩时间最多,count=1
第一个活动肯定能举办,记录下右区间,遍历数组,如果元素左区间大于right,说明不重复,count++,更新right为元素右区间。最后返回n-count就是需要删除的区间数

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

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

一支箭可以社保所有重叠的区间的气球,所以问题还是找有几个不重叠的区间,即举办最多的活动

bool static cmp(vector<int> a,vector<int> b){
        return a[1]<b[1];
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        int n=points.size();
        if(n==0){
            return 0;
        }
        sort(points.begin(),points.end(),cmp);
        int temp=points[0][1],count = 1;
        for(int i=1;i<n;i++){
            if(points[i][0]>temp){
                count++;
                temp=points[i][1];
            }
        }
        return count;
    }

10. 题目121. 买卖股票的最佳时机

数组元素小于等于1直接返回0
遍历数组,更新价格最小值,与收益最大值

int maxProfit(vector<int>& prices) {
        if(prices.size()<=1) return 0;
        int pmax=0,pmin=prices[0],n=prices.size();
        for(int i=1;i<n;i++){
            pmin=min(pmin,prices[i]);
            pmax=max(pmax,prices[i]-pmin);
        }
        return pmax;
    }

11. 题号122. 买卖股票的最佳时机 II

只要今天比昨天价高就卖,即使以后又增值了,也可以接着卖
如137不需要等到最高值再卖,因为3时相当于又买了
把握每一次机会就好

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

12. 题号1005. K 次取反后最大化的数组和

有负数先转负数,没有负数转最小的正数
先按绝对值从大到小排序,为了最小的正数会出现在末尾,这样只需要排序一次就好
遍历数组找负数反转,出循环如果k为偶数,不用动(反转同一个数偶数次,还为本身,到这步数组里已经没有负数或者次数不够用了),奇数反转最小正数就好(在末尾)

	static bool cmp(const int a,const int b){
        return abs(a)>abs(b);
    }
    int largestSumAfterKNegations(vector<int>& A, int K) {
        sort(A.begin(),A.end(),cmp);
        for(int i=0;i<A.size();i++){
            if(A[i]<0 && K){
                A[i]=-A[i];
                K--;
            }
        }
        if(K%2==1){
            A[A.size()-1]=-A[A.size()-1];
        }
        int sum=0;
        for(int i:A){
            sum+=i;
        }
        return sum;
    }

13. 1518. 换酒问题

方法一:
一瓶一瓶换,一次减3个空瓶,加一个空瓶,多喝一瓶
注意初值要为初始酒数

int numWaterBottles(int numBottles, int numExchange) {
        int ans = numBottles;
        while(numBottles >= numExchange){
            ans++;
            numBottles -= numExchange;
            numBottles++;
        }
        return ans;
    }

方法二:
一批一批换,注意初值也要为初始酒数

int numWaterBottles(int numBottles, int numExchange) {
        int ans = numBottles,empty_num = numBottles;
        while(empty_num >= numExchange){
            ans += empty_num / numExchange;
            empty_num = empty_num % numExchange + empty_num / numExchange;
        }
        return ans;
    }

14. 1578. 避免重复字母的最小删除成本

保留删除代价大的,删除代价小的
因为只比较前后两个元素,所以保存代价大的值

int minCost(string s, vector<int>& cost) {
        int ans = 0;
        for(int i = 1; i < s.size(); i++){
            if(s[i] == s[i - 1]){
                if(cost[i] > cost[i - 1]){
                    ans += cost[i - 1];
                }
                else{
                    ans += cost[i];
                    cost[i] = cost[i - 1];
                }
            }
        }
        return ans;
    }

15. 874. 模拟行走机器人

unordered_set不能装pair类型,所以用set
用两个方向数组表示方向

int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        set<pair<int,int>> obset;
        int dirx[] = {0,1,0,-1};
        int diry[] = {1,0,-1,0};
        int curx = 0, cury = 0, dir = 0, ans = 0;
        for(auto vec : obstacles){
            obset.insert(make_pair(vec[0],vec[1]));
        }
        for(int c : commands){
            if(c == -1){
                dir = (dir + 1) % 4;
            }
            else if(c == -2){
                dir = (dir + 3) % 4;
            }
            else{
                for(int j = 0; j < c; j++){
                    int dx = curx + dirx[dir];
                    int dy = cury + diry[dir];
                    if(!obset.count(make_pair(dx,dy))){
                        curx = dx;
                        cury = dy;
                        ans = max(ans,curx * curx + cury * cury);
                    }
                    else{
                        break;
                    }
                }
            }
        }
        return ans;
    }

16. 1403. 非递增顺序的最小子序列

桶记录元素个数,从后向前遍历桶,就可以实现优先找最大值,大于sum/2返回

vector<int> minSubsequence(vector<int>& nums) {
        vector<int> bucket(100,0),ans;
        int sum = 0;
        for(int num : nums){
            sum += num;
            bucket[num - 1]++;
        }
        int ans_sum = 0;
        for(int i = 99; i >= 0; i--){
            while(bucket[i] != 0){
                ans.push_back(i + 1);
                ans_sum += (i + 1);
                bucket[i]--;
                if(ans_sum > sum / 2){
                    return ans;
                }
            }
        }
        return {};
    }

17. 406. 根据身高重建队列

根据身高排序,然后插入相应位置即可,此时他前面的人都比他高
因为要频繁插入,所以用list,list底层用I链表实现,效率高

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];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        list<vector<int>> que;
        for(auto vec : people){
            int idx = vec[1];
            auto it = que.begin();
            while(idx--){
                it++;
            }
            que.insert(it,vec);
        }
        return vector<vector<int>>(que.begin(),que.end());
    }

18. 134. 加油站

遍历每个站点,判断能否绕一圈,在此基础上进行优化
如果1到达不了4,那么2、3也到达不了4,1到3汽油大于等于0,比直接从3出发汽油只多不少,所以如果1到达不了4,那么2、3也到达不了4,直接跳到1能到达的最远站点即可(即 i = j)
注意:如果 j < i 直接返回-1,因为这样证明 i 之后的点已经都饶不了一圈了,
如果不直接返会造成死循环(1到3,3到1,永远无法退出循环)
由于相当于一个圈,所以要(j + 1)% n,不能直接++

int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size();
        for(int i = 0; i < n; i++){
            int j = i;
            int num = gas[i];
            while(num >= cost[j]){
                num = num - cost[j] + gas[(j + 1) % n];
                j = (j + 1) % n;
                if(j == i) return i;
            }
            if(j < i) return -1;
            i = j;
        }
        return -1;
    }

19. 881. 救生艇

方法一:
最大值与最小值能一个船就走,如果最小值也与最大值走不了,那么最大值只能自己走了
1,2,5 limit = 7,2,5走显然是最合适的,但太麻烦,1,5走不耽误2,2如果能和5走,那他能和任何一个人走(其他人都比5轻),所以和最小值可以得到最大值
用 i<=j 是因为,奇数时总有个人自己一条船

int numRescueBoats(vector<int>& people, int limit) {
        sort(people.begin(),people.end());
        int ans =0, i = 0, j = people.size() - 1;
        while(i <= j){
            ans++;
            if(people[i] + people[j] <= limit){
                i++;
            }
            j--;
        }
        return ans;
    }

方法二:
用桶记录每个元素出现个数,双指针遍历桶,空间换时间
注意:当 i > j 时要退出循环,否则会多加一次

int numRescueBoats(vector<int>& people, int limit) {
        vector<int> bucket(limit + 1, 0);
        int ans = 0, i = 1, j = limit;
        for(int p : people){
            bucket[p]++;
        }
        while(i <= j){
            while(i <= j && bucket[i] <= 0){
                i++;
            }
            while(i <= j && bucket[j] <= 0){
                j--;
            }
            if(i > j) break;
            if(i + j <= limit){  
                bucket[i]--;
            }
            bucket[j]--;
            ans++;
        }
        return ans;
    }

收获与体会

  1. 优先队列可以当大/小顶堆来用,每次从优先队列中取出元素需要花费O(logn) 的时间
  2. 只需要排序一次用sort,每次都需要排序用优先队列
  3. 自定义比较函数时,小顶堆要用大于号
  4. 排序想每次找最大最小值可以考虑用桶代替,左端为最小值,右端为最大值
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值