Leetcode贪婪算法相关题目总结(更新中)

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

(来源:链接:https://leetcode-cn.com/tag/greedy/)


贪心算法一般用来解决需要 “找到要做某事的最小数量” 或 “找到在某些情况下适合的最大物品数量” 的问题,且提供的是无序的输入。

贪心算法的思想是每一步都选择最佳解决方案,最终获得全局最佳的解决方案

标准解决方案具有 O(NlogN) 的时间复杂度且由以下两部分组成:

  1. 思考如何排序输入数据(O(NlogN) 的时间复杂度)。
  2. 思考如何解析排序后的数据(O(N) 的时间复杂度)

如果输入数据本身有序,则我们不需要进行排序,那么该贪心算法具有 O(N) 的时间复杂度。

如何证明你的贪心思想具有全局最优的效果:可以使用反证法来证明。

(来源:作者:LeetCode链接:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/solution/yong-zui-shao-shu-liang-de-jian-yin-bao-qi-qiu-b-2/)

目录

跳跃问题

55. 跳跃游戏

45. 跳跃游戏 II

其他问题

605. 种花问题

376. 摆动序列

402. 移掉K位数字

面试题66. 构建乘积数组


常见问题

455. 分发饼干

https://leetcode-cn.com/problems/assign-cookies/

贪心的问题大都没有套路可循,而且形式上贴近DP和回溯
唯一区别就是不需要列举所有的情况,只需要找到每步的最优解,即可完成任务
本题目要求:满足越多的孩子越好,那么我们直接将孩子胃口和饼干尺寸升序排列

static int cmp (const void *s1, const void *s2) {
    return *(int *)s1 > *(int *)s2 ? 1 : -1;
}
int findContentChildren(int* g, int gSize, int* s, int sSize){
    // 贪心的问题大都没有套路可循,而且形式上贴近DP和回溯
    // 唯一区别就是不需要列举所有的情况,只需要找到每步的最优解,即可完成任务
    // 本题目要求:满足越多的孩子越好,那么我们直接将孩子胃口和饼干尺寸升序排列
    qsort(g, gSize, sizeof(g[0]), cmp);
    qsort(s, sSize, sizeof(s[0]), cmp);
    int count = 0;
    int gIndex = 0;
    int sIndex = 0;
    while (gIndex < gSize && sIndex < sSize) {
        // 找到合适孩子胃口的饼干尺寸, 然后增加满足孩子的个数
        while (sIndex < sSize && s[sIndex] < g[gIndex]) {
            sIndex++;
        }
        // 一旦超出范围,直接返回,说明以目前孩子的胃口,没有饼干的尺寸可以满足
        if (sIndex >= sSize) {
            break;
        }
        count++;
        sIndex++;
        gIndex++;
    }
    return count;
}

860. 柠檬水找零

https://leetcode-cn.com/problems/lemonade-change/

每次都需要找零钱,金额有三种,5,10,20,而且在找零过程中,20的面额根本用不上,那么我只需要记录5和10的面额即可



bool lemonadeChange(int* bills, int billsSize){
    if (bills == NULL) {
        return false;
    }

    int five = 0;
    int ten = 0;
    for (int i = 0; i< billsSize; ++i) {
        if (bills[i] == 5) {
            five++; // 5元刚好是饮料的价格,无需找钱
        } else if (bills[i] == 10) {
            if (five > 0) {
                // 能找钱
                ten++;
                five--;
            } else {
                // 无钱可找,那么直接返回false
                return false;
            } 
        } else {
            if (five > 0 && ten > 0) {
                five--;
                ten--;
            } else if (five >= 3) {
                five -= 3;
            } else {
                return false;
            }
        }
    }
    return true;
}

区间类问题(定一动一)

【begin,end】贪心算法中的区间类问题,精髓就是定一个点动一个点,想办法让begin固定,移动end来比较判断,或者想办法让end固定,依靠begin来判断

合集:https://mp.weixin.qq.com/s/ioUlNa4ZToCrun3qb4y4Ow

435. 无重叠区间 

https://leetcode-cn.com/problems/non-overlapping-intervals/

区间问题:通用做法就是按照begin或者end进行升序排序,然后更加end或begin进行后续判断
题目要求去除区间的个数要尽可能的少,我们以end作为排序标准,升序排列,可以理解成你要在begin~end这个区间内开会
做多能参加多少个会议,那么显然,我们需要尽可能的参考结束早的会议(end小)


static int cmp (const void *s1, const void *s2) {
    int *arg1 = *(int **)s1;
    int *arg2 = *(int **)s2;
    if (arg1[1] != arg2[1]) {
        return arg1[1] > arg2[1] ? 1 : -1;
    } else {
        return arg1[0] > arg2[0] ? 1 : -1;
    }
}
int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize){
    // 题目要求移除区间的个数,本质还是找重叠区间的个数
    if (intervals == NULL) {
        return 0;
    }

    qsort(intervals, intervalsSize, sizeof(intervals[0]), cmp);
    int rear = intervals[0][1];
    int count = 1;
    for (int i = 1; i < intervalsSize; ++i) {
        if (rear <= intervals[i][0]) {
            rear = intervals[i][1];
            count++; // 记录最大不重叠的区间(最多能参加的会议,)
        }
    }
    return intervalsSize - count;
}

完成上面的题目后,下面的252就很好理解了 

252. 会议室

https://leetcode-cn.com/problems/meeting-rooms/

static int cmp (const void *s1, const void *s2) {
    int *arg1 = *(int **)s1;
    int *arg2 = *(int **)s2;
    if (arg1[1] != arg2[1]) {
        return arg1[1] > arg2[1] ? 1 : -1;
    } else {
        return arg1[0] > arg2[0] ? 1 : -1;
    }
}
bool canAttendMeetings(int** intervals, int intervalsSize, int* intervalsColSize){
    if (intervals == NULL || intervalsSize == 0) {
        return true;
    }
    qsort(intervals, intervalsSize, sizeof(intervals), cmp);
    int count = 1;
    int rear = intervals[0][1];
    for (int i = 1; i < intervalsSize; ++i) {
        if (rear <= intervals[i][0]) {
            rear = intervals[i][1];
            count++;
        }
    }
    return count == intervalsSize ? true : false;
}

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

https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/

从直觉来看,本题是在找重叠区间的个数,还是定一个动一个,问题在于我们选择哪儿个定,哪儿个动
比起大气球(区间),我们更加关注小气球(区间)
所以那end点升序,那begin点判断,如果重叠,那么就是一根弓箭如果不重叠,就是两根

<https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/solution/gua-he-xin-shou-peng-you-de-shi-pin-ti-jie-by-shel/>较好的题解


static int cmp (const void *s1, const void *s2) {
    int *arg1 = *(int **)s1;
    int *arg2 = *(int **)s2;
    if (arg1[1] != arg2[1]) {
        return arg1[1] > arg2[1] ? 1 : -1;
    }
    return arg1[0] > arg2[0] ? 1 : -1;
}
int findMinArrowShots(int** points, int pointsSize, int* pointsColSize){
    if (points == NULL) {
        return 0;
    }

    qsort(points, pointsSize, sizeof(points), cmp);
    int rear = points[0][1];
    int count = 1;
    for (int i = 1; i < pointsSize; ++i) {
        if (rear < points[i][0]) {
            rear = points[i][1];
            count++;
        }
    }
    return count;
}

56. 合并区间

https://leetcode-cn.com/problems/merge-intervals/

区间类问题:定一个点动一个点
如果固定end点,那么begin的位置没办法保证,我们在合并区间的时候,如果出现了end0 >= begin1的情况,那么我们还要比较begin1和begin0的大小
选择小的那个作为区间的begin位置,begin和end两个位置都在参加比较,不合适
如果选择固定begin点,那么过程就会轻松很多,每次比endi和begini+1即可,因为是按照begin进行顺序排列的,不用担心begin是不是最小,一定是最小

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
static int cmp (const void *s1, const void *s2) {
    int *arg1 = *(int **)s1;
    int *arg2 = *(int **)s2;
    if (arg1[0] != arg2[0]) {
        return arg1[0] > arg2[0] ? 1 : -1;
    } 
    return arg1[1] > arg2[1] ? 1 : -1;
}
int** merge(int** intervals, int intervalsSize, int* intervalsColSize, int* returnSize, int** returnColumnSizes){

    if (intervals == NULL) {
        return NULL;
    }
    qsort(intervals, intervalsSize, sizeof(intervals[0]), cmp);
    int **res = (int **)malloc(sizeof(int *) * intervalsSize);
    for (int i = 0;i < intervalsSize; ++i) {
        res[i] = (int *)malloc(sizeof(int) * intervalsColSize[0]);
        memset(res[i], 0, sizeof(int) * intervalsColSize[0]);
    }
    // 注意合并区间的技巧,先放一个元素进入区间,然后比较end0和begin1,如果有需要,那么直接更新end即可,比较有技巧的一种写法
    int index = 0;
    res[index][0] = intervals[0][0];
    res[index][1] = intervals[0][1];
    int rear = intervals[0][1];
    for (int i = 1; i < intervalsSize; ++i) {
        if (rear >= intervals[i][0]) {
            rear = fmax(rear, intervals[i][1]);
            res[index][1] = rear; // 随时需要更新
        } else {
            rear = intervals[i][1]; // 随时需要更新 
            res[++index][0] = intervals[i][0];
            res[index][1] = intervals[i][1];
        }
    }
    returnSize[0] = index + 1; // 次数给的是区间的个数 需要下标加1,这也和index自增的位置有很大的关系
    returnColumnSizes[0] = (int *)malloc(sizeof(int) * returnSize[0]);
    for (int i = 0;i < returnSize[0]; ++i) {
        returnColumnSizes[0][i] = intervalsColSize[0];
    }
    return res;
}

57. 插入区间

https://leetcode-cn.com/problems/insert-interval/

本题需要固定起点,拿终点去比较才行,不断在循环中更新区间的终点

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
 #define SIZE 2
 static int cmp (const void *s1, const void *s2) {
    int *arg1 = *(int **)s1;
    int *arg2 = *(int **)s2;
    if (arg1[0] != arg2[0]) {
        return arg1[0] > arg2[0] ? 1 : -1;
    }
    return arg1[1] > arg2[1] ? 1 : -1;
 }
int** insert(int** intervals, int intervalsSize, int* intervalsColSize, int* newInterval, int newIntervalSize, int* returnSize, int** returnColumnSizes){
    (*returnSize) = 0;
    int **res = (int **)malloc(sizeof(int *) * (intervalsSize + 1));
    for (int i = 0; i < intervalsSize + 1; ++i) {
        res[i] = (int *)malloc(sizeof(int) * SIZE);
        memset(res[i], 0, sizeof(int) * SIZE);
    }
    
    for (int i = 0; i < intervalsSize; ++i) {
        res[(*returnSize)][0] = intervals[i][0];
        res[(*returnSize)++][1] = intervals[i][1];        
    }
    res[(*returnSize)][0] = newInterval[0];
    res[(*returnSize)++][1] = newInterval[1];
    qsort(res, intervalsSize + 1, sizeof(res[0]), cmp);
    
    int index = 0;
    int rear = res[0][1];
    int **add = (int **)malloc(sizeof(int *) * (*returnSize));
    add[0] = (int *)malloc(sizeof(int) * SIZE);
    add[0][0] = res[0][0];
    add[0][1] = res[0][1];
    
    for (int i = 1; i < (*returnSize);++i) {
        if (rear < res[i][0]) {
            // 插入当前区间
            rear = res[i][1];
            index++;
            add[index] = (int *)malloc(sizeof(int) * SIZE);
            add[index][0] = res[i][0];
            add[index][1] = res[i][1];
        } else {
            rear = fmax(rear, res[i][1]);
            add[index][1] = rear;
        }
    }
    
    returnSize[0] = index == 0 ? 1 : index + 1;
    returnColumnSizes[0] = (int *)malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < (*returnSize); ++i) {
        returnColumnSizes[0][i] = SIZE;
    }
    return add;
}

跳跃问题

55. 跳跃游戏

https://leetcode-cn.com/problems/jump-game/

class Solution {
public:
    bool canJump(vector<int>& nums) {
        if(nums.empty()) return true;
        int size = nums.size();
        if(size == 1) return true;
        
        int begin = 0,end = 0;
        while(begin<size-1)//不能等于最后一个
        {
            if(nums[begin] == 0&&end<=begin)//不能跳
            return false;
            end = max(end,begin+nums[begin]);
            begin++;
        }
        return end>=size-1?true:false;
    }
};

45. 跳跃游戏 II

https://leetcode-cn.com/problems/jump-game-ii/

https://leetcode-cn.com/problems/jump-game-ii/solution/45-by-ikaruga/

class Solution {
public:
    int jump(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        int begin = 0,end = 1,MAXpos = 0,step = 0;
        while(end<size)
        {
            for(int i = begin;i<end;++i)
            {
                MAXpos = max(MAXpos,i+nums[i]);
            }
            begin = end;
            end = MAXpos+1;
            step++;
        }
        return step;
        
    }
};

版本2:

class Solution {
public:
    int jump(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        int begin = 0,end = 1,step= 0;
        while(end<size)
        {
            int MAX = 0;            
            for(int j = begin+1;j<size&&j<end;++j)
            {
                if(j+nums[j]>=MAX) {MAX = j+nums[j];begin = j;}
            }
            step++;
            int length = nums[begin];//目前能达到最长的位置
            end = begin+length+1;
        }
        return step;
        
    }
};

本题难度不在于想法,而在于怎么落实。

每次能走都远走多远,尽量去走。这就是核心思想,但是代码落实就会出现很多的问题。

其他问题

605. 种花问题

https://leetcode-cn.com/problems/can-place-flowers/

class Solution {
public:
    bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        int size = flowerbed.size();
        if(size == 0) return 0;
        int Pre = 0;
        int Left = 0,right = Pre+1;
        while(Pre<size)
        {  
            if(flowerbed[Pre] == 1)//当前是1
            Pre+=2;
            else 
            {
                // cout<<Pre<<endl;
                if(right>size-1&&flowerbed[Left] == 0)
                {
                    n--;break;
                }
                else if(right>size-1&&flowerbed[Left] == 1) break;
                else if(right<=size-1&&flowerbed[Left] == 0&&flowerbed[right] == 0) 
                {
                    n--;
                    Pre+=2;
                }
                else if(flowerbed[Left] == 1) Pre++;
                else if(right<=size-1&&flowerbed[right] == 1) Pre = right+2;
                
            }

            Left = Pre-1,right = Pre+1;
            
        }
        if(n <= 0) return true;
        else return false;

    }
};

376. 摆动序列

https://leetcode-cn.com/problems/wiggle-subsequence/

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return 1;
        // if(size == 2&&nums[0] == nums[1]) return 1;
        vector<int> Sample;
        for(int i = 0;i<size-1;++i)
        {
            int temp = nums[i]-nums[i+1];
            if(temp<0)
            Sample.push_back(0);//负数
            else if(temp>0) Sample.push_back(1);//正数
        }
        if(Sample.size() == 0) return 1;
        int Next = (Sample[0]==0?1:0),Res = 1;
        for(int i = 1;i<Sample.size();++i)
        {
            if(Sample[i] == Next) {Res++;Next = (Sample[i]==0?1:0);}
        }
        return Res+1;
    }
};

402. 移掉K位数字

https://leetcode-cn.com/problems/remove-k-digits/

// 执行用时 : 0 ms, 在所有 C++ 提交中击败了 100%的用户
// 内存消耗 : 9.5 MB, 在所有 C++ 提交中击败了 31.19%的用户
class Solution {
public:
    string removeKdigits(string num, int k) {
        if(num.size() == k) return string(1, '0');
        string stk;
        int i = 0;
        while(k > 0 && i < num.size()) // 将num中的字符按规则移动到栈中 栈保存遍历过的单调不减的元素
        {
            if(stk.empty() || stk.back() <= num[i])  // 直接入栈,并转而遍历下一个元素
            {
                stk.push_back(num[i]);
                ++i;
            }    
            else // stk.back() > num[i]
            {
                stk.pop_back();
                --k;
            }
        }
        // 1. 如果i == 0, 则 k 可能不等于0, 移除掉stk末尾k个元素.
        // 2. 如果k == 0, 则 i 可能不等于0, 需要加上num中i之后的元素.
        stk = stk.substr(0, stk.size() - k) + num.substr(i);

        // 移除开头的0,在全0的情况下保证至少剩下一个0.
        size_t beginIndex = 0;
        while(beginIndex < stk.size() - 1 && stk[beginIndex] == '0') ++beginIndex;
        
        return stk.substr(beginIndex);
    }
};

// 作者:fu-guang
// 链接:https://leetcode-cn.com/problems/remove-k-digits/solution/c-0ms-ji-bai-100xiang-xi-si-lu-xi-jie-jie-xi-by-fu/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

面试题66. 构建乘积数组

https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/

238. 除自身以外数组的乘积 https://leetcode-cn.com/problems/product-of-array-except-self/

本题初见,题意不是很好理解

其实就是从i开始算,i左侧全部元素的乘积与右侧全部元素的乘积再次做乘法

那么我们很容易发现规律:

前缀部分乘积也是一样的,计算完前后缀乘积后,二者再次相乘,即可完成运算。

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        int size = a.size();
        vector<int>B(size,1);
        for(int i = size-1;i>0;--i)
        {
            B[i-1] = a[i]*B[i];
        }
        vector<int>A(size,1);
        for(int i = 0;i<size-1;++i)
        {
            A[i+1] = a[i]*A[i];
        }
        vector<int>Res;
        for(int i = 0;i<=size-1;++i)
        {
            Res.push_back(A[i]*B[i]);
        }
        return Res;
    }
};

有没有更好的办法,降低空间复杂度?

因为输出数组不算复杂度,那么我们用输出数组来代替上面的左右两个数组

算法参考:https://leetcode-cn.com/problems/product-of-array-except-self/solution/chu-zi-shen-yi-wai-shu-zu-de-cheng-ji-by-leetcode-/

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        if(nums.empty()) return {};
        int size = nums.size();
        vector<int> Res(size,1);
        Res[0] = 1;
        for(int i = 1;i<size;++i)
        {
            Res[i] = Res[i-1]*nums[i-1]; //用保存结果的容器,先保存左侧乘积
        }
        //左侧乘积我们算了,下面就是右侧乘积的事儿了
        //显然我们不能从头算,我们从后往前算
        int R = 1;
        for(int i = size - 1;i>=0;--i)
        {
            Res[i] *= R;
            R *= nums[i];
        }
        return Res;
    }
};

49. 字母异位词分组

https://leetcode-cn.com/problems/group-anagrams/

算法参考:https://leetcode-cn.com/problems/group-anagrams/solution/c-yin-ru-hashbiao-shi-jian-32ms-ji-bai-9948-nei-cu/ 

本题核心内容是:就算字符串不同,但是组成该字符串的字母,都一样的,那么我们对字符串进行排序,不管原来字符串中的字母是怎样组合的,现在都一样了。

然后我们需要知道怎么将这些内容插入到一个一维数组中去

我们使用hash表,index是字符串,value是该字符串在Res二维数组的索引下标

Hash表现查找,有没有这个内容,有的话,就直接将该内容插入到该插入的位置

Hash表没有找到,那么说明第一次出现这个字符串,那么我们规定其下标,同时创建一个一维数组,插入二维数组中

整个过程非常巧妙:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        if(strs.empty()) return {};
        vector<vector<string>>Res;
        int index = 0;//不同数字的下标
        unordered_map<string,int>M;//int部分是该组合全部字符传在res中的索引
        for(auto item:strs)
        {
            string temp = item;
            sort(temp.begin(),temp.end());
            cout<<item<<endl;
            if(M.find(temp)!=M.end()) Res[M[temp]].push_back(item);
            //找到了,那么就按照索引插入到结果中
            else //只有遇到新的字符串,才会进入这个部分,创建一维数组插入到二维数组中,同时记下该类字符串的索引
            {
                vector<string> vec(1,item);//新建一维数组,并插入结构
                Res.push_back(vec);
                M[temp] = index;//标记该字符串的索引(从0开始)
                index++;
            }
        }
        return Res;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值