贪心算法学习

算法说明:要获得最优结果,则要求中间的每步策略都是最优的,即在当前状态下局部最优。

参考书籍:A LeetCode Grinding Guide

一、分配问题

  1. 【Leetcode】455. 分发饼干

先对两个vector进行排序,然后按照顺序来遍历。这一排序后遍历两个vector来依次比较元素大小的方法的思想本身就是一种局部最优的方法。

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int child = 0,cookie = 0;
        while(child < g.size() && cookie < s.size()){
            if(s[cookie] >= g[child]){
                child++;
            }
            cookie++;
        }
        return child;
    }
};
  1. 【Leetcode】 135.分发糖果

注意正向遍历后,还需负向再遍历检查一边,因为第一遍正向的遍历结果元素会受到后续遍历的影响。

class Solution {
public:
    int candy(vector<int>& ratings) {
        int n = ratings.size();
        vector<int> nums(n,1);
        if(n < 2){
            return accumulate(nums.begin(),nums.end(),0);
        }
        for(int i=1; i < n;i++){
            if(ratings[i]>ratings[i-1] && nums[i] <= nums[i-1]){
                nums[i] = nums[i-1]+1;	//直接取nums[i]和nums[i-1]两者的最大值加一的速度比while循环增一的效率高
            }//因为还要再逆序遍历检查一边,所以可以不用判断执行ratings[i]<ratings[i-1]情况
        }
        // 要逆序!再检查一遍
        for(int i=n-1; i>0;i--){
            if(ratings[i]<ratings[i-1] && nums[i] >= nums[i-1]){
                nums[i-1] = nums[i]+1;
            }
        }
        return accumulate(nums.begin(),nums.end(),0); //accumulate带有三个形参:头两个形参指定要累加的元素范围,第三个形参则是累加的初值。
    }
};
  1. 【PTA】 L2-003 月饼 (25 分)
月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量,请你计算可以获得的最大收益是多少。

注意:销售时允许取出一部分库存。样例给出的情形是这样的:假如我们有 3 种月饼,其库存量分别为 18、15、10 万吨,总售价分别为 75、72、45 亿元。如果市场的最大需求量只有 20 万吨,那么我们最大收益策略应该是卖出全部 15 万吨第 2 种月饼、以及 5 万吨第 3 种月饼,获得 72 + 45/2 = 94.5(亿元)。

输入格式:
每个输入包含一个测试用例。每个测试用例先给出一个不超过 1000 的正整数 N 表示月饼的种类数、以及不超过 500(以万吨为单位)的正整数 D 表示市场最大需求量。随后一行给出 N 个正数表示每种月饼的库存量(以万吨为单位);最后一行给出 N 个正数表示每种月饼的总售价(以亿元为单位)。数字间以空格分隔。

输出格式:
对每组测试用例,在一行中输出最大收益,以亿元为单位并精确到小数点后 2 位。

输入样例:
3 20
18 15 10
75 72 45
输出样例:
94.50

这也是一个需要排序解决的贪心问题。注意对需求量总库存量两者大小比较的情况。

#include <bits/stdc++.h>
using namespace std;
struct mooncake{
    double stock;
    double sell;
    double price;
}cake[1003];
bool cmp(mooncake a,mooncake b){
    return a.price > b.price;
}
int main(){
    int n,d;
    double profit=0;
    scanf("%d %d\n",&n,&d);
    for(int i=0;i<n;i++){
        scanf("%lf",&cake[i].stock);
    }
    for(int i=0;i<n;i++){
        scanf("%lf",&cake[i].sell);
        cake[i].price = cake[i].sell / cake[i].stock;
    }
    sort(cake,cake+n,cmp);
    for(int i=0;i<n;i++){
        if(d>=cake[i].stock){
            profit += cake[i].sell;
            d -= cake[i].stock;
        }else{ //即需求量小于总库存量,需求量一定会被满足
            profit += cake[i].price*d;
            break;
        }
    }
    printf("%.2f\n",profit);
    return 0;
}
  1. 【PAT (Basic Level) Practice (中文)】1023 组个最小数 (20 分)

此处的技巧就在于“零”位置的放置。处理时先抛开“0”,输出一个非零数之后再将0-9整体按顺序输出。

#include <bits/stdc++.h>
using namespace std;
int main(){
    int a[10];
    for(int i=0;i<10;i++){
        cin >> a[i];
    }
    for(int i=1;i<10;i++){
        if(a[i] != 0){
            cout << i;
            a[i]--;
            break;
        }
    }
    for(int i=0;i<10;i++){
        for(int j=0; j< a[i];j++) cout << i;
    }
    cout << endl;
    return 0;
}

二、区间问题

  1. 【Leetcode】435. 无重叠区间
    解法一:右端点从小按大排序
class Solution {
public:
    static bool cmp(vector<int> a, vector<int> b){ // 注意cmp函数要么定义为类定义里的静态函数要么定义为类外的普通函数
        return a[1] < b[1];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),cmp);
        int total=0,pre = intervals[0][1];  // 每次两个比较区间的前一个区间的比较位置(最右点)和后一个区间的比较位置(最左点)不一样
        for(int i=1; i<intervals.size();i++){
            if(intervals[i][0] < pre){
                total++;
            }else{
                pre = intervals[i][1];  //比较时遇到需要删除区间(都是删除后一个)的情况时,pre不动。遇到不需要删除区间的情况时pre需要往后移动一个区间。
            }
        }
        return total;
    }
};

注意:

  1. 根据实际情况判断区间排序是按照最左点还是最右点排序,使用“极限判断法”。比如[1,10],[1,2],[2,4],[4,6],[5,8]分别按照最左点和最右点排序导致的结果的不同。
  2. cmp函数的定义方法

解法二:左端点从大按小排序

class Solution {
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),[](vector<int> a,vector<int> b){
            if(a[0] != b[0]) return a[0]>b[0];  //左端点从大到小排列
            else return a[1] < b[1];    //左端点相同的情况下,右端点从小到大排列
        });
        int total=0,pre = intervals[0][0];  // 每次两个比较区间的前一个区间的比较位置(最左点)和后一个区间的比较位置(最右点)不一样
        for(int i=1; i<intervals.size();i++){
            if(intervals[i][1] > pre){
                total++;
            }else{
                pre = intervals[i][0];  //比较时遇到需要删除区间(都是删除后一个)的情况时,pre不动。遇到不需要删除区间的情况时pre需要往后移动一个区间。
            } 
        }
        return total;
    }
};

注意:cmp函数的闭包写法

基础练习

  1. 【Leetcode】605. 种花问题

注意:判断条件是否成立的交并组合

class Solution {
public:
    bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        int ans=0;
        for(int i=0;i<flowerbed.size();i++){
            if(flowerbed[i]==0 && (i==0 || flowerbed[i-1]==0)
                               && (i == flowerbed.size()-1 || flowerbed[i+1]==0)
            ){
                flowerbed[i]=1;
                ans++;
            }
        }
        return ans >= n;   
    }
};
  1. 【Leetcode】452. 用最少数量的箭引爆气球

思路:判断两个或多个相邻区间是否可以使用“一箭”击中,只要看第一个区间的右端点是否能大于或等于后面区间的左端点或右端点中的一个即可,若存在表明可以和第一个区间一起被“一箭”击中

class Solution {
public:
    static bool cmp(vector<int> a,vector<int> b){
        if(a[1] != b[1]) return a[1] < b[1];
        else   return a[0] < b[0];
    }
     
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(),points.end(),cmp);
        int ans=0,pre =points[0][1];
        for(int i=1;i<points.size();i++){
            if(pre < points[i][0]){
                ans++;
                pre = points[i][1];
            }
        }
        return ++ans;
    }
};
  1. 【Leetcode】763. 划分字母区间

这个题非常的迷幻,我能在测试用例跑通,但是提交时候就会报错。报错显示a[l]有越界现象,但是经检测后并没有相应现象……泪了……
最后看了下题解,发现解法思想都差不多。

我的解法:

struct alphabet{
    int startPos=1000;
    int endPos=-1;
    int fre=0;
}a[30];

class Solution {
public:
    vector<int> partitionLabels(string s) {
        vector<int> ans;
        if(s.length() < 2){
            ans.push_back(s.length());
            return ans;
        }
        for(int i=0;i<s.length();i++){
            int l = s[i]-'a';
            a[l].fre++;
            if(a[l].startPos > i){
                a[l].startPos = i;
            }
            if(a[l].endPos < i){
                a[l].endPos = i;
            }
        }
        int j = 0;
        while(j < s.length()){
            int start = a[s[j]-'a'].startPos;
            int end = a[s[j]-'a'].endPos;
            int endPos=end;
            for(int i=start;i<end;i++){
                int l = s[i]-'a';
                cout << "l为" << l << endl; //做测试用,正式提交时并没有该语句
                if(a[l].endPos > endPos){
                    endPos = a[l].endPos;
                }
            }
            ans.push_back(endPos-start+1);
            j = endPos + 1;
        }
        return ans;
    }
};

在这里插入图片描述
4. 【Leetcode】121. 买卖股票的最佳时机(简单)

只要按顺序考虑列表中前面元素和后面元素最大差值即可

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int profit = 0,buy = prices[0];
        for(int i=0;i<prices.size();i++){
            buy = min(prices[i],buy);
            profit = max(prices[i]-buy,profit);
        }
        return profit;
    }
};
  1. 【Leetcode】122. 买卖股票的最佳时机 II

思路:将后面所有可能的profit(买入<卖出)全部相加

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int buy = prices[0],profit=0;
        for(int i=0;i<prices.size();i++){
            buy = min(buy,prices[i]);
            profit += max(0,prices[i]-buy);
            buy = prices[i];
        }
        return profit;
    }
};
进阶难度
  1. 【Leetcode】406. 根据身高重建队列

参考官方题解:从高到低考虑,先将原列表排好序,再按照每个人的ki大小来插入到队列中

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),[](vector<int> &a,vector<int> &b){
            return a[0]>b[0] || (a[0]==b[0] && a[1]<b[1]);
        });
        vector<vector<int> > ans;
        for(vector<int>& person:people){
            ans.insert(ans.begin()+person[1],person);
        }
        return ans;
    }
};
  1. 【Leetcode】665. 非递减数列

思路:这道题属于遍历时要对特定位置元素进行修正,然后再遍历判断的形式。
我的思路就是,顺序依次两两进行比较,分析什么时候需要修改哪个位置的元素并且其修正值是多少。
请添加图片描述

class Solution {
public:
    bool checkPossibility(vector<int>& nums) {
        int cnt=0, i=1;
        while(i<nums.size()){
            if(nums[i] < nums[i-1]){
                cnt++;
                if(i != nums.size()-1){
                    if(nums[i-1] < nums[i+1]){
                        nums[i] = nums[i-1];
                    }else if(nums[i-1] > nums[i+1])   {
                        nums[i-1] = nums[i];
                        if(i >= 2) i-=2;
                    }
                }
            }
            if(cnt > 1) return false;
            i++;
        }
        return true;
    }
};

最后看了看官方题解,发现自己是“缝缝补补再三例”😂,

官方题解就是很典型的找出特殊情况,就是当nums[0]>nums[1]时可以不用对nums[0]做修正,然后其他的情况都一律按照修改 k i k_{i} ki k i − 1 k_{i-1} ki1(也就是两两比较中把值小的改成大的)

在此附上代码:

class Solution {
public:
    bool checkPossibility(vector<int> &nums) {
        int n = nums.size(), cnt = 0;
        for (int i = 0; i < n - 1; ++i) {
            int x = nums[i], y = nums[i + 1];
            if (x > y) {
                cnt++;
                if (cnt > 1) {
                    return false;
                }
                if (i > 0 && y < nums[i - 1]) {
                    nums[i + 1] = x;
                }
            }
        }
        return true;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值