算法总结归纳(第七天)(贪心算法)

目录

1、贪心算法简单题

①、分发饼干

②、 按k次取法的最大值

③、柠檬水找零

小结

2、贪心算法中等题

①、摆动序列

②、单调递增数字

3、贪心解决股票问题

①、买卖股票最佳时机Ⅱ

②、买卖股票最佳时机含手续费

4、双维度贪心问题

①、分发糖果

②、根据身高重建队列

小结

5、贪心解决区间问题

①、跳跃游戏

题目概述:

解题思路:

代码

②、跳跃游戏Ⅱ

题目概述

解题思路

代码

③、最少的箭引爆气球

题目概述

解题思路

代码

④、无重叠区间

题目概述

解题思路

代码

⑤、划分字母区间

题目概述

解题思路

代码

⑥、合并区间

题目概述

解题思路

代码

小结

7、其它贪心题目

①、最大子序列和

题目概述

解题思路

代码

②、加油站

题目概述

解题思路

代码

小结

总结


1、贪心算法简单题

①、分发饼干

题目链接:分发饼干

该题目就是将两个数组从小到大排序,然后贪心就是尽可能用大的配大的,小的配小的,然后直接进行比较。从后往前遍历。

int findContentChildren(vector& g, vector& s) {
        if(s.size()==0) return 0;
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int j = s.size()-1;int tem = 0;
        for(int i = g.size()-1;i>=0;i--){
            if(s[j]>=g[i]){
                j--;tem++;
            }
            if(j<0)break;
        }
        return tem;
    }

②、 按k次取法的最大值

题目链接:k次取反最大值

该题目就是依旧排序,然后根据绝对值大小进行降序排序,然后有负的就从大到小取反,之后如果k剩奇数个,就对最小的数取反即可。

static bool cmp(int a, int b)
{
    return abs(a) > abs(b);
}

    int largestSumAfterKNegations(vector& nums, int k) {
        sort(nums.begin(), nums.end(), cmp);
        int res = 0;
        for(int i = 0; i 0){
                k--;
                nums[i] *= -1;
            }
        }
        if(k % 2 == 1) nums[nums.size() - 1] *= -1;
        for(int i = 0; i

③、柠檬水找零

题目链接:柠檬水找零

该题目主要就20的处理逻辑稍微麻烦一些,

5块钱。直接five+1就可以。

10块钱,直接five-1,ten + 1就可以。

20块钱,就分为找三个5块和一个五块一个10块。(这里也体现我们的贪心思路)

如果有10肯定优先使用10,因为10比起5块,通用性更差,我们贪心就是局部最优,优先使用通用性更差的。

 bool lemonadeChange(vector& bills) {
        int five = 0, ten = 0, twelve = 0;
        for(int i = 0; i 0)//优先使用面值大的,面值小的价值更高
                {
                    if(five > 0){
                        five --; ten --;
                    }
                    else return false;
                }
                else{
                    if(five >=3){
                        five -= 3; 
                    }
                    else return false;
                }
                twelve ++;
            }
        }
        return true;//所有情况都遍历了,均满足
    }

小结

 一般碰到无序的贪心类题目,先从小到大(从大到小)进行排序,然后通过局部最优求解全局最优,想①和②都是这种类型的。

2、贪心算法中等题

①、摆动序列

题目链接:摆动序列

需要讨论两种情况:1、两个数算不算有坡度,这里根据测试数据,算有。因此为二,但是因为两个数据只能比一次,因此res初始化成1。

2、单调序列有坡度,就是平的,此时,我们就pre存储住,平坡左边的状态,然后跟右边状态对比。

int wiggleMaxLength(vector& nums) {
        if(nums.size() < 2) return nums.size();
        int cur = 0, pre = 0, res = 1;
        for(int i = 1; i 0 && pre <= 0) || (cur < 0 && pre >= 0)){
                res++;
                pre = cur;
            }
        }
        return res;
    }

②、单调递增数字

题目链接:单调递增数字

我们先将数字各个位置按照从小到大(低位 -> 高位)存储,不满足条件的,就低位的变成9,高一位的减一,之后再重新加出来。

int monotoneIncreasingDigits(int n) {
        if(n < 10) return n ;
        vector res;
        int tem = n;
        while(n){
            res.push_back(n % 10);
            n /= 10;
        }
        bool ct = false; int index = 0;
        for(int i = 0; i < res.size() - 1; i++){
            if(res[i] < res[i + 1]){
                res[i + 1] --;
                ct = true;
                index = i;
            }
        }
        for(; index >= 0; index--){
            res[index] = 9;
        }
        if(!ct) return tem;
        tem = 0;
        for(int i = 0; i < res.size(); i++){
            tem += res[i] * pow(10, i);
        }
        return tem;
    }

3、贪心解决股票问题

①、买卖股票最佳时机Ⅱ

题目链接:买卖股票最佳时机Ⅱ

这个其实非常简单,将两天之间的利润拆分开来,将其中正的相加。然后输出最终结果。

int maxProfit(vector& prices) {
        int pre = 0;int cur = 0;
        int sum = 0;
        for(int i = 0;i0){
                sum+=(pre-cur);
            }
        }
        return sum;
    }

②、买卖股票最佳时机含手续费

题目链接:买卖股票含手续费

int maxProfit(vector& prices, int fee) {
        int result = 0;
        int minPrice = prices[0]; // 记录最低价格
        for (int i = 1; i < prices.size(); i++) {
            // 情况二:相当于买入
            if (prices[i] < minPrice) minPrice = prices[i];

            // 情况三:保持原有状态(因为此时买则不便宜,卖则亏本)
            if (prices[i] >= minPrice && prices[i] <= minPrice + fee) {
                continue;
            }

            // 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出
            if (prices[i] > minPrice + fee) {
                result += prices[i] - minPrice - fee;
                minPrice = prices[i] - fee; // 情况一,这一步很关键,避免重复扣手续费
            }
        }
        return result;
    }

4、双维度贪心问题

①、分发糖果

题目链接:分发糖果

本题就是先从左到右进行比较,然后再从右往左比较,最后就是最终的结果。

 int candy(vector& ratings) {
        int res = 0; 
        vector nums(ratings.size(), 1);
        for(int i = 0; i ratings[i]){
                nums[i + 1] = nums[i] + 1;
            }
        }
        for(int i = ratings.size() - 1; i >0; i--){
            if(ratings[i - 1] > ratings[i] && nums[i-1] <= nums[i]){
                nums[i - 1] = nums[i] + 1;
            }
        }
        for(int i = 0 ;i < nums.size(); i++) res += nums[i];
        return res;
    }

②、根据身高重建队列

题目链接:根据身高重建队列

本题就是重点是排序,排序将身高大的排前面,然后,他们数值相同的posion按从小到大排序,然后,插入的时候就按照posion一次插入即可。

static bool cmp(vector& a,vector& b)
{
    if(a[0]==b[0]) return a[1]b[0];
}

    vector> reconstructQueue(vector>& people) {
        sort(people.begin(),people.end(),cmp);
        list> que;
        for(int i = 0;i>::iterator it = que.begin();
            while(posion--){
                it++;
            }
            que.insert(it,people[i]);
        }
        return vector> (que.begin(),que.end());
    }

小结

双维度问题,通常需要从左边考虑一次,从右边考虑一次,通常就能解的题目,贪心就是要多尝试和假设。

5、贪心解决区间问题

题目链接:跳跃游戏

①、跳跃游戏

题目概述:

给定一个数组,然后数组中数字表示能跳的格子数,如果能跳到最后一个格子,就true,否则就false。

解题思路:

本题没有数跳的次数,因此跳次数不重要,重要的是覆盖范围,每个数,都有覆盖范围,我们再有效范围内记录这些范围,然后遍历,直到找到到达甚至超越边界的数,如果遍历完都没有,就返回false。

代码

bool canJump(vector& nums) {
        int mcover = 0;
        for(int i = 0; i <= mcover; i++){
            mcover = max(mcover, i + nums[i]);
            if(mcover >= nums.size() - 1) return true;
        }
        return false;
    }

②、跳跃游戏Ⅱ

题目链接:跳跃游戏Ⅱ

题目概述

该题目就是在上一题目基础上添加了 移动最少步数 的限定。

解题思路

本题依然化用贪心的思路,我们定义一个cur变量表示:当前元素能到达的最远处,再定义一个pre变量表示:下一个元素能到达的最远处,如果i == cur,步数加一,如果cur >= nums.size() - 1。说明找到了结果

代码

 int jump(vector& nums) {
        if(nums.size() == 1) return 0;
        int cur = 0; int pre = 0; int ans = 0;
        for(int i = 0; i= nums.size() - 1) break;
        }
        return ans;
    }

③、最少的箭引爆气球

题目链接:最少的箭引爆气球

题目概述

给了一堆气球在x轴的左右坐标(相当于气球的宽度),然后有的气球是重叠在一起的,因此使用箭引爆,从而求最少的箭数。

解题思路

这种题我们一般选定气球的左半边或者右半边进行排序,定义一个气球坐标为[x1, x2]。

如果下一个气球的x1 > 现在气球的 x2,那我们就应该res + 1。如果,小于等于,说明重叠,然后,我们右边界就更新成两个气球中最小的。

代码

static bool cmp(vectora, vector b)
{
    return a[0] < b[0];
}

    int findMinArrowShots(vector>& points) {
        int res = 1;
        sort(points.begin(), points.end(), cmp);
        for(int i = 0; i < points.size() - 1; i++){
            if(points[i + 1][0] > points[i][1]){
                res ++;
            }
            else {
                points[i + 1][1] = min(points[i][1], points[i + 1][1]);
            }
        }
        return res;
    }

④、无重叠区间

题目链接:无重叠区间

题目概述

给点数个区间,每个区间有两个坐标(闭区间),然后我们进行移除,使得所有区间没有交际,要求移除个数最少。

解题思路

依然是先排序,排序有两种方法。

1、右排序,记录非交叉区间,然后总区间减去非交叉区间。

2、左排序,记录交叉区间,然后直接记录重叠区间。

代码

左排序代码

static bool cmp(vector a, vector& b)
{
    return a[0] < b[0];
}

    int eraseOverlapIntervals(vector>& intervals) {
        if(intervals.size() == 0) return 0;
        int res = 0;
        sort(intervals.begin(), intervals.end(), cmp);
        for(int i = 0; i

右排序代码

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

⑤、划分字母区间

题目链接:划分字母区间

题目概述

给定一串字母,然后将字母划分成好几段,然后每一段中都只有出现过的数字,最后返回每一段的字母个数。

解题思路

本题既然跟出现次数有关,我们就用哈希表记录,然后记录该字母最后一次出现的最远坐标在哪里。最终用left和right记录区间,用两者差(+1是因为包含起点)表示区间长度。

代码

vector partitionLabels(string s) {
        int hash[27];
        vector res;
        for(int i = 0; i

⑥、合并区间

题目链接:合并区间

题目概述

给定好几个区间,将其中重叠的区间合并,并且返回最终没有重叠区域的集合。

解题思路

首先碰到这种给了好几个区间的题目,一定要排序。我们直接左边排序。

然后我们直接将一个区间放入res中,然后每次判断res.back()[1]和当前数组关系,重叠直接合并,不重叠再收纳。

代码

vector> merge(vector>& intervals) {
        if(intervals.size() == 0) return intervals;
        vector> res;
        sort(intervals.begin(), intervals.end(), cmp);
        res.push_back(intervals[0]);
        for(int i = 1; i

小结

区间类型的题目,很多都是涉及两个坐标,一左一右,而一般我们都是对区间先排序,然后再看下一步,同时判断重叠区间的时候,一般涉及到更新边界,只要将边界更新好,就可以接触这类型的题目。

7、其它贪心题目

①、最大子序列和

题目概述

给定一个数组,找到其中拥有最大和的子数组。

解题思路

我们用贪心的思路,只要保证每次相加,前面的数是正的,那么对于后面的数来说就是正反馈,是可以试着往下加。

为甚不看后面的数的正负,是因为有可能后面的后面会更大,但是前面的数已经成定局,因此前面的正负一般能推出后面是否是最大值。

代码

int maxSubArray(vector& nums) {
        int tem = 0;
        int res = - 1e9 + 10;
        for(int i = 0; i res) res = tem;
           if(tem < 0) tem = 0;
        }
        return res;
    }

②、加油站

题目概述

给定一个gas数组和cost数组,gas表示该加油站能获得的汽油数量,cost表示前往紧邻的加油站所消耗的汽油升数。

解题思路

整理以下,其实数组用gas - cost数组构建的新数组就是汽油的净变化量。

然后我们记录每次相加的值,以及记录下标。

代码

int canCompleteCircuit(vector& gas, vector& cost) {
        int cur = 0; int res = 0;
        int sum = 0;
        for(int i = 0; i

小结

其它的题目就要根据题意,仔细分析给出的数据作用,然后按照思路慢慢求解。

总结

谈心类的题目,很多都没有规律,但是我们做多了又会发现贪心有时候真的很好用,我们一般贪心可以试试排序,试着画一下折线图,找规律,因为贪心就类似脑筋急转弯,有时候说不定就思路涌现。

你可能感兴趣的:(算法,贪心算法)