基础贪心算法集合(更新到20加油站)

目录

2.柠檬水找零

3.将数组和减半的最小次数

4.最大数

5.摆动序列

6.最长的递增子序列

7递增的三元子序列

8.最长连续递增序列 

9.买卖股票的最佳时机

10.买股票的最佳时机2

11.k次取反后的最大化数组和

12.按身高排序

13.优势洗牌

14.最长回文串 

15.增减字符串匹配

16.分发饼干

17.最优除法

18.跳跃游戏

 19.跳跃游戏2

20.加油站


2.柠檬水找零

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) 
    {
        int five = 0;
        int ten = 0;
        for(auto ch : bills)
        {
            if(ch == 5)five++;
            else if(ch == 10)
            {
                if(five < 1)return false;
                five--;
                ten++;
            }
            else
            {
                if(five>=1 && ten >=1)//这里是贪心的点,如果有十元,优先把十元用掉,因为五元的                                                                                            
                                      //用处更大,贪五元
                {
                    five--;
                    ten--;
                }
                else if(five >= 3)
                {
                    five -= 3;
                }
                else return false;
            }
        }
        return true;
    }
};

3.将数组和减半的最小次数

class Solution {
public:
    int halveArray(vector<int>& nums) 
    {
        priority_queue<double> heap;//使用一个大根堆
        double sum = 0;
        int count = 0;
        for(auto ch : nums)
        {
            sum += ch;
            heap.push(ch);
        }
        sum /= 2.0;
        while(sum > 0)
        {
            double tmp =  heap.top()/2;
            sum -= tmp;//每次把最大数的一半减掉,贪心的点在于找最大数
            heap.pop();
            heap.push(tmp);
           count ++;

        }
        return count;
    }
};

4.最大数

class Solution {
public:
    string largestNumber(vector<int>& nums) 
    {
        vector<string>str;
        for(auto ch: nums)
        {
            str.push_back(to_string(ch));//1.把数字转换成字符串,拼接字符串,比较字典序
        }
        sort (str.begin(), str.end(), [](const string& s1, const string & s2)
        {
            return s1 + s2 > s2 + s1;//贪心的点在于每两个字符串都在两种顺序中选择最大的那个
        }
        );
        string n;
        for(auto& ch : str)
        {
            n += ch;
        }
        if(n[0] == '0')return "0";//处理前导零
        return n;
    }
};

这个排序规则为什么能排序

具有传递性

a b   > b a

b c >   c b

那么a c > c a(但是这里需要证明)

5.摆动序列

使用全局变量left,left 最开始设为 0 ,因为右边的点无论值为多少,第一个点都算

right 为右节点值减左节点值

left * right <= 0 计数加一

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) 
    {
        int left = 0;
        int size = nums.size();
        if(size == 1)return 1;
        int count = 0;
        for(int i = 0; i <= size - 2; i++)
        {
            int right = nums[i+1] - nums[i];
            if(right == 0)continue;
            if(left * right <= 0)count++;
            left = right;
        }
        return count + 1;
    }
};

即选取折点,贪心的点在于,无论是选取折点左边或折点右边的点,最终的节点数都可能更少

6.最长的递增子序列

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        vector<int> nums1;
        nums1.push_back(nums[0]);
        for(auto ch: nums)
        {
            if(ch > nums1.back())nums1.push_back(ch);
            int left = 0;
            int right = nums1.size()-1;
            while(left < right)
            {
                int mid = (left + right) / 2;
                if(ch > nums1[mid])left = mid +1;
                else right = mid;
            }
            nums1[left] = ch;//遍历过程中,用ch的值替换数组中大于它的最小值
        }
        return nums1.size();
    }
};

[7,3,8,4,7,2,14,13]
长度为1的序列的末尾值:7(x),3(x),2
长度为2的序列的末尾值:8(x),4,
长度为3的序列的末尾值:7,
长度为4的序列的末尾值:14(x),13

贪心的地方在于,可以接到7后面的肯定能接到3后面,可以接到3后面的肯定可以接到2后面。
(也因此这个数组是严格递增,因此可以使用二分查找)

这个数组的作用是记录最大的序列长度,比如说长度为1的序列末位置为2,4无法接在2后面,但是先前已经储存了接在3后面的序列了,就不需要担心序列长度丢失问题

7递增的三元子序列

思路同5,只需要判断三个就可以了

class Solution {
public:
    bool increasingTriplet(vector<int>& nums)
    {
        int a,b;
        a = nums[0];
        b = INT_MAX;
        for(int i = 0; i < nums.size();i++)
        {
            if(nums[i] < a)a = nums[i];
            if(nums[i] > a && nums[i] < b)b = nums[i];
            else if (nums[i] > b)return true;
        }
        return false;
    }
};

8.最长连续递增序列 

1 2 3 0 4
贪心的点在于,从1开始,到0处序列断开,那么我们不需要从2开始,因为从开始的序列长度一定小于从1开始。所以我们只需要记录最大值,然后从0再开始比较

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int counttmp = 1;
        int countmax = 1;
        for(int j = 1; j < nums.size();j++)
        {
            if(nums[j] > nums[j-1]){
              counttmp++;
              countmax = max(counttmp,countmax);
            }
            else
                counttmp = 1;
        }
        return countmax;
    }
};

9.买卖股票的最佳时机

贪心的点在于,我们每次寻找最小值只需要和下一个节点比较一次就可以了,因为我们已经储存了之前节点的最小值

class Solution {
public:
    int maxProfit(vector<int>& pr) {
        int pmin = INT_MAX;
        int money = 0;
        for(int i = 1; i < pr.size(); i++)
        {
            pmin = min(pmin, pr[i-1]);
            money = max(money, pr[i]-pmin);
        }
        return money;
    }
};

10.买股票的最佳时机2

贪心的点在于,只要出现正收益,我就交易 

1.分成一节一节

class Solution {
public:
    int maxProfit(vector<int>& pr) {
        int money = 0;
        for(int i = 0,j = 1; j < pr.size(); i++, j++)
        {
            if(pr[j]-pr[i]>0)money += pr[j]-pr[i];
        }
        return money;
    }
};

 2.双指针,整段求

class Solution {
public:
    int maxProfit(vector<int>& pr) {
        int money = 0;
        int n = pr.size();
        for(int i = 0;i < n;i++)
        {
            int j = i;
            while(j+1 < n && pr[j+1] > pr[j])//这里要注意判断j+1要小于n,不能越界
            {
                j++;
            }
            money += pr[j]-pr[i];
            i = j;
        }
        return money;
    }
};

11.k次取反后的最大化数组和

假设数组中负数为n个

1.n >= k 把数组中前k个小的负数转化成正数,再相加

2.n < k 先把数组中全部负数转换成正数,然后再计算k - n的值,如果为偶数,那么取正与取反抵消,如果为奇数,那么我们将最小的那个数取反即可 

class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        int n = 0;
        int ret = 0;
        int MIN = INT_MAX;
        for(auto ch : nums)
        {
            if(ch < 0)n++;
            if(abs(ch) < MIN)MIN =abs(ch);
        }
        if(n >= k)
        {
            sort(nums.begin(), nums.end());
            for(int i = 0; i < k;i++)
            {
                ret += -nums[i];
            }
            for(int i = k; i < nums.size();i++)
            {
                ret+= nums[i];
            }
            return ret;
        }
        else
        {
            for(int i = 0;i < nums.size();i++)
            {
                ret += abs(nums[i]);
            }
            if((k-n)%2==0)return ret;
            else return ret-2*MIN;
        }
    }
};

12.按身高排序

. - 力扣(LeetCode)

class Solution {
public:
    vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
        int size = names.size();
        vector<int>arr(size);
        for(int i = 0; i < size;i++)
        {
            arr[i] = i;
        }
        sort(arr.begin(),arr.end(),[&](int i,int j)
        {
            return heights[i]>heights[j];
        });
        vector<string>ret(size);
        for(int i = 0; i < size; i++)
        {
            ret[i] = names[arr[i]];
        }
        return ret;
    }
};

是13题的前置提示,具体就是创建一个下标数组,然后根据身高的高低重新排序这个数组,最后将人名再填充到一个新的数组中

13.优势洗牌

. - 力扣(LeetCode)

class Solution {
public:
    vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
        int size = nums1.size();
        sort(nums1.begin(),nums1.end());//重新排序nums1
        vector<int>arr(size);
        vector<int>ret(size);
        for(int i = 0; i < size; i++)//同上题,进行下标排列
        {
            arr[i] = i;
        }
        sort(arr.begin(),arr.end(),[&](int i, int j)
        {
            return nums2[i] < nums2[j];
        });
        int left = 0;
        int right = size-1; //Left  与 right 用来指向当前arr最左边与最右边的元素
        for(int i = 0; i < size; i++)定义一个i遍历新的nums1数组
        {
            if(nums1[i] > nums2[arr[left]])
            {
                ret[arr[left]] = nums1[i];
                left++;
            }
            else 
            {
                ret[arr[right]] = nums1[i];//Arr[left]  与arr[right]找到该数字相对原数组的 
                right--;                  //下标,再将nums1中的值放进ret中
               
            }
        }
        return ret;
    }
};

我们的贪心策略是从左往右比较,有两种情况

第一种例如上面8小于11,那么我们把8与最大的32配对,把最大的抵消掉

第二种例如 9   12

                   6    11

这里9比6大,直接把9与6配对,因为如果我们不这样做,而是把12与6配对,这样的话9比11小,我们就损失了一对优势

但是我们需要把8放到原数组中32对应的位置,因此需要使用arr[left] arr[right]取得这个数原数组的下标

14.最长回文串 

. - 力扣(LeetCode)

class Solution {
public:
    int longestPalindrome(string s) {
        vector<int>s1(128);//因为这里只有52个字母,那么直接开128个
        int size = s.size();
        int ret = 0;
        for(int i = 0; i < size; i++)//这个字母的阿斯克码值就对应新数组的下表,进行计数
        {
            s1[s[i]]++;
        }
        for(int i = 0; i < 128; i++)
        {
            ret+=s1[i]/2*2;//这里是一个化简操作,我们需要将偶数个排列在两边对称,奇数时会多一个, 
                           //使用/2向下取整
        }
        if(ret < size)return ret+1;//如果全为偶数ret= size,存在至少一个奇数时ret<size,此时我们 
                                    //可以在对称轴再放置一个字母
        else return ret;
    }
};

贪心策略是,假设有一条对称轴,两边能放几个就放几个,如果是偶数那么全放完,如果是奇数只能放偶数个,最后如果还有剩下的字母,还可以再对称轴再放一个 

15.增减字符串匹配

. - 力扣(LeetCode)

class Solution {
public:
    vector<int> diStringMatch(string s) {
    int size = s.size();
    vector<int>ret(size+1);
    int minnum = 0;
    int maxnum = size;
    for(int i = 0 ;i < size;i++)
    {
        if(s[i]=='I')
        {
            ret[i]= minnum;
            minnum++;
        }
        else
        {
            ret[i]= maxnum;
            maxnum--;
        }
    }
    ret[size]= minnum;
    return ret;
    }
};

贪心的点在于,如果是I,即这个数到下个数要增长,那么我们选取当前最小的那个数,可以使得出现增长的情况数目最多,同样,当我们判断到D,我们选取最大的那个数,可以使得出现减少的情况数目最多。

严格地说当我们采取最大或者最小的那个数,那么后面的排序一定是下降或者上升趋势的。

最后一个位置需要我们手动排,这时候minnum=maxnum,任意选一个加入即可

16.分发饼干

. - 力扣(LeetCode)

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int sizeg = g.size();
        int sizes = s.size();
        int ret = 0;
        int i = 0;
        int j = 0;
        while(i < sizeg &&j < sizes)
        {
            if(s[j]>=g[i])
            {
                i++;
                j++;
                ret++;
            }
            else j++;
        }
        return ret;
    }
};

原理类似与13优势洗牌,我们把两个数组排序好,然后使用双指针依次同时向右即可,当饼干不能满足胃口时,直接丢弃饼干 

17.最优除法

. - 力扣(LeetCode)

class Solution {
public:
    string optimalDivision(vector<int>& nums) {
        int size = nums.size();
        string ret;
        if(size == 1)return to_string(nums[0]);
        else if(size == 2)
        {
            ret += to_string(nums[0]);
            ret += '/';
            ret += to_string(nums[1]);
        }
        else
        {
            ret += to_string(nums[0]);
            ret += '/';
            ret += '(';
            for(int i = 1; i < size -1; i++)
            {
                ret += to_string(nums[i]);
                ret += '/';
            }
             ret += to_string(nums[size - 1]);
             ret += ')';
        }
        return ret;
    }
};

这题主要是一个思路的问题,只要找到贪心的策略就比较简单了,只需要把数字填进去。

首先这么多数字相除a/b/c/d/e/f....

最后肯定会化成x/y,我们只需要使x最大,y最小即可,再观察式子,我们可以知道a肯定在分子上,而b无论括号怎么加,肯定在分母中。因此我们贪心地想,只需要把cdef....放在分子上即可,那么我们只需要在这样添加括号a/(b/c/d/e/f...),最后cdef....都会翻到分子上

18.跳跃游戏

 . - 力扣(LeetCode)

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int left = 0;
        int right = 0;
        int maxpos = 0;
        int ret = 0;
        int size = nums.size();
        while(left <= right)
        {
            if(maxpos >= size -1)
            {
                return true;
            }
            for(int i = left; i <= right; i++)
            {
                maxpos = max(maxpos,i+nums[i]);
            }
            ret++;
            left = right + 1;
            right = maxpos;
        }
        return false;
    }
};

这题实际上并不是严格使用贪心的而是使用层序遍历的思想

例如一个数组[2,3,1,1,4]

它开始时的左下标 =右下标 =0

然后从左下标开始遍历到右下标,i+nums[i]中的最大下标就是最远距离,也就是下一次遍历的右端点

下一次的左端点假设是right + 1。但是有可能跳不到,比如0 1 2 3第一次跳跃最远距离是0.

所以 2 3 1 1 4 被我们分成了

2 | 3 1| 1 4他们分别是第一次第二次第三次可以跳跃到的范围

 19.跳跃游戏2

. - 力扣(LeetCode)

代码思路类似上一题

class Solution {
public:
    int jump(vector<int>& nums) {
        int left = 0;
        int right = 0;
        int maxpos = 0;
        int ret = 0;
        int size = nums.size();
        while(1)
        {
            if(maxpos >= size -1)
            {
                return ret;
            }
            for(int i = left; i <= right; i++)
            {
                maxpos = max(maxpos,i+nums[i]);
            }
            ret++;
            left = right + 1;
            right = maxpos;
        }
    }
};

20.加油站

. - 力扣(LeetCode)

我们先把gas-cos,得到从这个加油站到下个加油站的净收益ret,如果小于0那么无法到下一个位置,首先我们会想到使用暴力解法,即从第一个位置开始遍历,在这个位置到向后size个位置

将每一个位置的ret累加进入count中,当count小于0时说明无法完成一周,我们break,在跳出后我们有两种情况,一种是count<0跳出,另一种是完成循环,count >= 0,因此进行一次判断,如果成功直接返回i的值

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int size = gas.size();
        vector<int>ret(size);
        for(int i = 0; i < size;i++)
        {
            ret[i] = gas[i]-cost[i];
        }
        for(int i = 0; i < size;i++)
        {
            int count = 0;
            for(int step = 0; step < size; step++)
            {
                int index =(i+step)%size; 
                count += ret[index%size];
                if(count < 0)break;
            }
            if(count >= 0)return i;
        }
        return -1;
    }
};

但是上面的方法时间复杂度太高了

我们可以知道

如果我们跑到e

a + b + c +d +e < 0

因为我们已经成功跑完abcd了所以

a >=0 

a+b >= 0

a+b+c >= 0

a +b +c +d >=0

所以我们有

b+c+d+e < 0

c+d+e <0

d+e <0

e <0

因此从b到e我们都无法成功走完。

因此我们从f开始继续遍历

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int size = gas.size();
        vector<int>ret(size);
        for(int i = 0; i < size;i++)
        {
            ret[i] = gas[i]-cost[i];
        }
        for(int i = 0; i < size;i++)
        {
            int count = 0;
            int step;
            for(step = 0; step < size; step++)
            {
                int index =(i+step)%size; 
                count += ret[index%size];
                if(count < 0)break;
            }
            if(count >= 0)return i;
            i = i + step;
        }
        return -1;
    }
};

 a  b  c  d e f g

关于时间复杂度

当站点为a,我们仅遍历一次数组,复杂度N

当站点为b,我们先遍历了a-a,,再遍历了一次数组复杂度N+1

当站点为c,我们遍历了a-b,再遍历了一次数组复杂度N+2

最差的情况是g为开始的站点,时间复杂度为2N-1,即o(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值