基础贪心算法集合2(10题)

目录

1.单调递增的数字

2.坏了的计算器

3.合并区间

4.无重叠区间

[5. 用最少数量的箭引爆气球](#5. 用最少数量的箭引爆气球)

6.整数替换

解法1:模拟+记忆化搜索

解法2位运算+贪心

7.俄罗斯套娃信封问题

补充.堆箱子

8.可被3整除的最大和

9.距离相等的条形码

10.重构字符串


1.单调递增的数字

738. 单调递增的数字 - 力扣(LeetCode)

但是如果只这么处理会出现bug

例如如下这种情况,我们需要找到最前面的5.

因此

复制代码
class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        string num = to_string(n);
        int sz = num.size();
        int i = 0;
        while(i+1 < sz && num[i] <= num[i+1])i++;
        if(i+1 == sz)return n;//如果全递增直接返回,或者处理nums[i]的时候判断一下
        while(i - 1 >= 0 && num[i-1] == num[i])i--;
       num[i]--;
        for(int k = i + 1; k < sz; k++)
        {
            num[k] = '9';
        }
        return stoi(num);//std::stoi 会忽略前导零,只返回字符串表示的整数值
    }
};

2.坏了的计算器

991. 坏了的计算器 - 力扣(LeetCode)

看到这题我们首先就想到正向推导。

我们贪心地想到,如果想让初始数字以尽可能少的次数变成目标数字,可以多用*2操作。

但是我们发现这种贪心是不对的,对于*2和-1的操作,判断标准不是很明确。 用正向的方法比较难解决这个问题

于是我们想到正难则反,而*2 和-1的操作正好有相反的/2和+1操作。

这一题有个非常重要的点就是它里面的数据是没有小数的,从起始数据到目标数据,不断*2,-1操作下是不能产生小数的。

所以我们在面对奇数的时候,就不能进行/2操作了,只能进行+1操作。

整理思路,我们得出以下结论:

1.当end <= begin

当 end <= begin时,end要想达到begin,肯定不可能进行/2操作,只能进行+1操作,这时候无论奇数偶数,都只有+1的选择。我们需要进行begin-end次操作

2.当end > begin 的时

当end > begin 的时候

我们如果想要先+1再/2,那么我们必须要加偶数个1,我们才能/2.最后,我们通过k+1次操作得到了(x+k)/2.

而如果我们先/2,那么我们只需要1+k/2 次就能得到(x+k)/2。

我们很容易得到先进行除法是更优的。

因此在end > begin的时候 ,我们奇数仍然是只能+1,偶数则是只进行/2操作

复制代码
class Solution {
public:
    int brokenCalc(int startValue, int target) {
        int ret = 0;
        while(target > startValue)
        {
            if(target % 2 == 0)
            {
                target /= 2;
                ret++;
            }
            else
            {
                target++;
                ret++;
            }
        }
        ret += startValue - target;
        return ret;
    }
};

3.合并区间

56. 合并区间 - 力扣(LeetCode)

区间问题解法

我们为了思路统一,统一采用左端点排序

假设我们有一个区间,从left 到right

那么我们下一个区间有以下这些情况。

整体分为有重叠部分和没有重叠部分。

当有重叠部分时,我们保存左端点,取较大的右端点。(因为进行排序后,左端点是升序排序的)

当没有重叠部分时,我们把{left,right}加入ret数组,然后更新left和right。

复制代码
class Solution {
public:
    static bool cmp(vector<int> a, vector<int> b)
    {
        return a[0] < b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        //排序
        sort(intervals.begin(), intervals.end(), cmp);
        //合并区间
        vector<vector<int>> ret;
        int left = intervals[0][0];
        int right = intervals[0][1];
        for(int i = 1; i < intervals.size(); i++)
        {
            if(intervals[i][0] <= right)//有重叠部分
            {
                right = max(right,intervals[i][1]);//合并
            }
            else//无重叠
            {
                ret.push_back({left, right});//加入到结果中
                left = intervals[i][0];
                right = intervals[i][1];
            }
        }
        ret.push_back({left, right});
        return ret;
    }
};

4.无重叠区间

435. 无重叠区间 - 力扣(LeetCode)

仍然是先按照左端点排序。

我们要移除最少的区间,那也就是说要保留更多的区间。

因此我们可以采用以下策略来保留更多区间。

当存在区间重叠时,我们保留较小的右端点,这样它和后面的 区间有重叠的可能性就会变小。

当区间无重叠,我们更新left right并把count++即可。

遍历数组结束后要记得加上最后存在left right里面的那段区间。

最后,因为我们求的是移除的区间,因此把数组的长度减去count就是我们的返回值

复制代码
class Solution {
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        int count = 0;
        sort(intervals.begin(), intervals.end());
        
        int left = intervals[0][0];
        int right = intervals[0][1];
        for(int i = 1; i < intervals.size(); i++)
        {
            if(intervals[i][0] < right)
            {
                right = min(right,intervals[i][1]);
            }
            else
            {
                count++;
                left = intervals[i][0];
                right = intervals[i][1];
            }
        }
        count ++;
        return intervals.size() - count;
    }
};

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

452. 用最少数量的箭引爆气球 - 力扣(LeetCode)

理解题意,我们发现这也是一个区间问题。

我们发现这题找的实际上是交集。

复制代码
class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(), points.end());
        int cover = 0;
        int right = points[0][1];
        for(int i = 1; i < points.size(); i++)
        {
            if(points[i][0] <= right)//和上一个区间有交集
            {
                cover++;//重叠部分++
                right = min(right, points[i][1]);//右端点较小的同时也是是重叠区间的右端点
            }
            else
            {
                right = points[i][1];//无交集,以新的区间为基准再求重叠部分
            }
        }
        return points.size() - cover;
    }
};

6.整数替换

解法1:模拟+记忆化搜索

就是暴力枚举每种情况,对于偶数直接除以2,对于奇数则是取 min(hash[n-1], hash[n+1])

需要注意的地方是,为了防止INT_MAX + 1 溢出,我们dfs函数的类型使用long long,哈希表也是一样。

复制代码
class Solution {
public:
    unordered_map<long long, int> hash;
    int integerReplacement(int n) {
       
        return dfs(n);
    }
    int dfs(long long n)
    {
        if(n == 1)return 0;
        if(n % 2 == 0)
        {
            if(!hash.count(n/2))
            {
                hash[n/2] = dfs(n/2);
            }
            return 1 + hash[n/2];
        }
        else
        {
            if(!hash.count(n+1) )
            {
                hash[n+1] = dfs( n+1 );
            }
            if(!hash.count( n-1 ) )
            {
                hash[ n-1 ] = dfs( n-1 );
            }
            return 1  + min(hash[n-1], hash[n+1]);
        }
    }
};

解法2位运算+贪心

对于偶数我们进行/2操作即可。

对于奇数操作, 当其为01结尾的时候,我们采用-1操作操作数会更少。

当其为11结尾的时候,-1操作会使得一位1变为0,而+1操作可能会使得多于一位的1变为0,因此采用+1的操作会更快捷地得到最终值。

最后有一个例外,即3,我们应该采用-1操作。

要判断最后两位是01还是11,我们只需要把这些数对4取余即可,看余数是1还是3即可判断。

这道题的贪心就贪在不同的奇数是+1还是-1,已经在过程中证明好了

同样需要加一个longlong

复制代码
class Solution {
public:
    int integerReplacement(int num) {
        int ret = 0;
        long long n = num;
        while(n > 3)
        {
            if(n % 2 == 0)
            {
                ret++;
                n  = n >> 1;
            }
            else
            {
                if(n % 4 == 3)
                {
                    n = n + 1;
                    ret++;
                }
                else if(n % 4 == 1)
                {
                    n = n - 1;
                    ret++;
                }

            }
        }
        if(n == 3)return ret + 2;
        else if(n == 2)return ret + 1;
        else return ret;
    }
};

7.俄罗斯套娃信封问题

解法1:区间排序+动态规划(超时)

解法2:区间排序 + 贪心 + 二分

我们严格按照左端点排序后,实际上已经不用看左端点了,因为总是递增的。

那么这道题经过这样处理,就变成了一个最长递增子序列问题。

但是当左端点有重复的时候需要注意,这时候如果我们的右端点按照升序排,我们就会把一些不该加的元素加入。

这时候就需要我们在左端点相同的时候把右端点降序排序

这样的好处是,我们就不会有可能把这些数字全部接到原数组后面了。因为如果有更小的数,那么我们会将其与原来的数替换。例如上一个数是3,按照升序排序的话,4679都会排到它后面。

可是如果我们降序,则我们在将9加入数组后,后面的764都只是更新数组末尾的9而已,让9不断被替换为764.

复制代码
class Solution {
public:
    static bool cmp(const vector<int> & a, const vector<int> & b)
    {
        if(a[0] != b[0])return a[0] < b[0];
       else return a[1] > b[1];
    }
    int maxEnvelopes(vector<vector<int>>& env) {
        sort(env.begin(), env.end(), cmp);
        vector<int> ret;
        ret.push_back(env[0][1]);
        for(int i = 1; i < env.size(); i++)
        {
            if(env[i][1] > ret.back())
            {
                ret.push_back(env[i][1]);
            }
            else
            {
                int left = 0; 
                int right = ret.size() - 1;
                while(left < right)
                {
                    int mid = left + (right-left) / 2;
                    if( env[i][1] <= ret[mid])
                    {
                        right = mid;
                    }
                    else{
                        left = mid + 1;
                    }
                }
                ret[left] = env[i][1];
            }
        }
        return ret.size();
    }
};

补充.堆箱子

虽然俄罗斯套娃信封中的解法1会超时,但是在堆箱子这题中是可以通过的

面试题 08.13. 堆箱子 - 力扣(LeetCode)

排序后的思路和最长递增子序列相同

有两个需要注意的问题

复制代码
class Solution {
public:
    static bool cmp(vector<int> a, vector<int> b)
    {
        return a[0] < b[0];
    }
    int pileBox(vector<vector<int>>& box) {
        sort(box.begin(), box.end(),cmp);//1.需要自己手动写一个排序函数,按照某一维度来排。 
                                          //默认排序会出问题
        int n = box.size();
        if(n == 0)return 0;
        vector<int> dp(n , 0);
        for(int i = 0; i < n; i++)//2.初始化dp表的时候,每个箱子最小的堆叠高度是自己的高度
        {
            dp[i] = box[i][2];
        }
        int ret = dp[0];
        for(int i = 1; i < n; i++)
        {
            for(int j = i - 1; j >= 0; j--)
            {
                if(box[j][0] < box[i][0] && box[j][1] < box[i][1] && box[j][2] < box[i][2])
                {
                    dp[i] = max(dp[i], dp[j] + box[i][2]);
                }
            }
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

8.可被3整除的最大和

1262. 可被三整除的最大和 - 力扣(LeetCode)

我们正难则反,根据累加和的不同情况,删去一些数即可,而我们只需要贪心地删除最小值即可。

分类情况中, 我们把第二种情况拿来举例子,可能我们有1个或者4个或者7个除以3余1的数,但是我们只需要取出最小的那个。整体来看是把这一组数分成两块,一块是一个除以3余1的数,另一块是可以被3整除的。

下面的另外三种也是一样的分组方式。

但是我们要注意,这些情况有可能不会全都有,因此我们可以先将x1 x2 y1 y2等赋值为

10010(题目中数组元素最大为10000),这样我们在最后取max的时候就必然不会取到无效情况。

更新最小值以及次小值只需要分类讨论即可

复制代码
class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        int sum = 0;
        int n = nums.size();
        for(int i = 0; i < n; i++)
        {
            sum += nums[i];
        }
        if(sum % 3 == 0)
        {
            return sum;
        }
        else if(sum % 3 == 1)
        {
            int x1,y1,y2;
            x1 = y1 = y2 = 1e4 + 10;
            for(int i = 0; i < n; i++)
            {
                int tmp = nums[i];
                if(tmp % 3 == 1)
                {
                    x1 = min(x1, tmp);
                }
                else if(tmp % 3 == 2)
                {
                    if(tmp <= y1)
                    {
                        y2 = y1;
                        y1 = tmp;
                    }
                    else if(tmp > y1 && tmp <= y2)
                    {
                        y2 = tmp;
                    }
                }
            }
            return max(sum - x1, sum - y1 - y2);
        }
        else//取余等于2
        {
            int x1,x2,y1;
            x1 = x2 = y1 = 1e4 + 10;
            for(int i = 0; i < n; i++)
            {
                int tmp = nums[i];
                if(tmp % 3 == 2)
                {
                    y1 = min(y1, tmp);
                }
                else if(tmp % 3 == 1)
                {
                    if(tmp <= x1)
                    {
                        x2 = x1;
                        x1 = tmp;
                    }
                    else if(tmp > x1 && tmp <= x2)
                    {
                        x2 = tmp;
                    }
                }
            }
        return max(sum - x1 - x2, sum - y1);
        }
    }
};

class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        int sum = 0;
        int n = nums.size();
        for(int i = 0; i < n; i++)
        {
            sum += nums[i];
        }
        if(sum % 3 == 0)
        {
            return sum;
        }
        else if(sum % 3 == 1)
        {
            int x1,y1,y2;
            x1 = y1 = y2 = 1e4 + 10;
            for(int i = 0; i < n; i++)
            {
                int tmp = nums[i];
                if(tmp % 3 == 1)
                {
                    x1 = min(x1, tmp);
                }
                else if(tmp % 3 == 2)
                {
                    if(tmp <= y1)
                    {
                        y2 = y1;
                        y1 = tmp;
                    }
                    else if(tmp > y1 && tmp <= y2)
                    {
                        y2 = tmp;
                    }
                }
            }
            return max(sum - x1, sum - y1 - y2);
        }
        else//取余等于2
        {
            int x1,x2,y1;
            x1 = x2 = y1 = 1e4 + 10;
            for(int i = 0; i < n; i++)
            {
                int tmp = nums[i];
                if(tmp % 3 == 2)
                {
                    y1 = min(y1, tmp);
                }
                else if(tmp % 3 == 1)
                {
                    if(tmp <= x1)
                    {
                        x2 = x1;
                        x1 = tmp;
                    }
                    else if(tmp > x1 && tmp <= x2)
                    {
                        x2 = tmp;
                    }
                }
            }
        return max(sum - x1 - x2, sum - y1);
        }
    }
};

9.距离相等的条形码

1054. 距离相等的条形码 - 力扣(LeetCode)

如果出现最多的数超过(n+1)/2,那么必定有两个相同元素会相邻

我们只需要把最大的那个元素先排好,然后去排其它的即可,我们的i如果越界,那么将其置为1即可

复制代码
class Solution {
public:
    vector<int> rearrangeBarcodes(vector<int>& barcodes) {
        int maxval = -1; 
        int maxcount = 0;
        unordered_map<int, int> hash;
        for(auto ch : barcodes )
        {
            if( maxcount < ++hash[ch] )
            {
                maxval = ch;
                maxcount = hash[ch];
            }
        }
        int n = barcodes.size();
        int i;
        vector<int> ret(n);
        for(i = 0; i < n && maxcount > 0; i += 2)
        {
            ret[i] = maxval;
            maxcount --;
        }
        hash.erase(maxval);
        for(auto& [x, y] : hash)
        {
            for(int j = 0; j < y; j++)
            {
                if(i >= n)i = 1;
                ret[i] = x;
                i += 2;
            }
        }
        return ret;
    }
};

10.重构字符串

767. 重构字符串 - 力扣(LeetCode)

这道题和上一道题是一模一样的的,唯一的不同就是这道题可能无解,因此我们需要判断一些maxcount 会不会大于(s.size() + 1)/2.

复制代码
class Solution {
public:
    string reorganizeString(string s) {
        int n = s.size();
        char maxval;
        int maxcount = 0;
        string ret = s;
        unordered_map<char, int> hash;
        for(auto ch: s)
        {
            if(maxcount < ++hash[ch])
            {
                maxval = ch;
                maxcount = hash[ch];
            }
        }
        if(maxcount > (n + 1) / 2)return "";
        int i;
        for(i = 0; i < n && maxcount > 0; i += 2)
        {
            ret[i] = maxval;
            maxcount--;
        }
        hash.erase(maxval);
        for(auto &[x, y] : hash)
        {
            for(int j = 0; j < y; j++)
            {
                if(i >= n)i = 1;
                ret[i] = x;
                i += 2;
            }
        }
        return ret;
    }
};
相关推荐
莫有杯子的龙潭峡谷19 分钟前
4.15 代码随想录第四十四天打卡
c++·算法
A懿轩A33 分钟前
2025年十六届蓝桥杯Python B组原题及代码解析
python·算法·蓝桥杯·idle·b组
灋✘逞_兇35 分钟前
快速幂+公共父节点
数据结构·c++·算法·leetcode
姜行运1 小时前
每日算法(双指针算法)(Day 1)
c++·算法·c#
stoneSkySpace1 小时前
算法——BFS
前端·javascript·算法
明月看潮生2 小时前
青少年编程与数学 02-016 Python数据结构与算法 20课题、几何算法
python·算法·青少年编程·编程与数学
wuqingshun3141592 小时前
经典算法 判断一个图是不是树
数据结构·c++·算法·蓝桥杯·深度优先
微臣愚钝2 小时前
图论-BFS搜索图/树-最短路径问题的解决
算法·图论·宽度优先
南玖yy2 小时前
排序算法复杂度及稳定性全解析(八种排序)
数据结构·算法·排序算法
雾里看山3 小时前
算法思想之位运算(一)
算法·leetcode·推荐算法