贪心算法篇——万千抉择中的唯一考量,最优解追寻的跬步累积(8)


在这里插入图片描述

引言

初篇我们介绍了贪心算法的相关背景知识,本篇我们将结合具体题目,进一步深化大家对于贪心算法的理解和运用。

一、整数替换

1.1 题目链接:整数替换

1.2 题目分析:

  • 给定一个正整数 n ,你可以做如下操作:
  • 如果 n 是偶数,则用 n / 2替换 n 。
  • 如果 n 是奇数,则可以用 n + 1或n - 1替换 n 。
  • 返回 n 变为 1 所需的 最小替换次数 。

1.3 思路讲解:

贪心策略:

我们的任何选择,应该让这个数尽可能快的变成 1 。

  • 对于偶数:只能执⾏除 2 操作,没有什么分析的;
  • 对于奇数
    i. 当 n== 1 的时候,不⽤执⾏任何操作;
    ii. 当 n == 3 的时候,变成 1 的最优操作数是 2 ;
    iii. 当n > 1 && n % 3 == 1的时候,那么它的⼆进制表⽰是 …01 ,最优的⽅式应该选择 -1 ,这样就可以把末尾的 1 ⼲掉,接下来执⾏除法操作,能够更快的变成1 ;
    iv. 当n > 3 && n % 3 == 3的时候,那么它的⼆进制表⽰是 …11 ,此时最优的策略应该是 +1 ,这样可以把⼀堆连续的 1 转换成 0 ,更快的变成 1 。

1.4 代码实现:

class Solution {
public:
    int integerReplacement(int n) {
        int ret = 0; // 最终次数
        while (n > 1) {
            if (n % 2 == 0) // 偶数直接除以2
            {
                n /= 2;
                ret++;
            } else if (n == 3) {
                ret += 2;
                return ret;
            } else if (n % 4 == 1) // 为...01的情况,需要把末尾的1减去
            {
                ret += 2;
                n /= 2;
            } else {
                ret += 2; n = n / 2 + 1;
            } // 为...11的情况,需要加1然后再除以2
        }
        return ret;
    }
};

二、俄罗斯套娃信封问题

2.1 题目链接:俄罗斯套娃信封问题

2.2 题目分析:

  • 给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度高度
  • 当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
  • 请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
  • 注意:不允许旋转信封。

2.3 思路讲解:

贪心策略:
为了尽可能多的套娃,我们应该保证让宽度和高度较小的信封在下面,与其尺寸相似但略大于他的信封在其上方。

因此,我们可以对其进行预处理.
当我们把整个信封按照「下⾯的规则」排序之后:

  • i. 左端点不同的时候:按照「左端点从⼩到⼤」排序;
  • ii. 左端点相同的时候:按照「右端点从⼤到⼩」排序
    我们发现,问题就变成了仅考虑信封的「右端点」,完完全全的变成的「最⻓上升⼦序列」的模型。那么我们就可以⽤「贪⼼ + ⼆分」优化我们的算法。

分析:

  • 我们遍历排序的过程是从左到右进行
  • 在排序过程中,我们可能会遇到宽度相同但高度不同的信封,由于我们要求宽度和高度尽可能小,在宽度相同时,我们应该选取高度较小的信封,因此应该按照规则ii进行排序
  • 而宽度不同时,直接按照宽度从小到大排序,判断高度即可。

2.4 代码实现:

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        int n=envelopes.size();
        //排序
        sort(envelopes.begin(),envelopes.end(),[&] (const vector<int>& p,const vector<int>& q)
        {
            return p[0]!=q[0]?p[0]<q[0] :p[1]>q[1];
            
        });
       vector<int> ret;
        ret.push_back(envelopes[0][1]);
        //贪心+二分
        for(int i=1;i<n;i++)
        {
            int b=envelopes[i][1];
            if(b>ret.back())
            {
                ret.push_back(b);//高度大于当前最大值,可以直接插入


            }
            else//二分查找更换
            {
                int left=0,right=ret.size()-1;
                
                while(left<right)
                {
                    int mid=(left+right)/2;
                    int t=ret[mid];
                    if(b>t)
                    {
                        left=mid+1;
                    }
                    else
                    {
                        right=mid;
                    }
                }
                ret[left]=b;//更新操作
            }
        }
        return ret.size();
        
    }
};

三、可被三整除的最大和

3.1 题目链接:可被三整除的最大和

3.2 题目分析:

  • 给你一个整数数组 nums,请你找出并返回能被三整除的元素 最大和。

3.3 思路讲解:

贪心策略:
直接求所有元素相加的和sum,再通过逐个减去小数的方式直到可被三整除为止,此时一定是最大和。

正难则反:
不能被3整除的情况:

  • 累加和除以3的余数为1
  • 累加和除以3的余数为2

因此,我们可以记录两个最小的余数为1的值x1,x2,和两个最小的余数为2的值y1与与y2,在求取所有元素之和后,判断即可。

  • sum%3==1,此时减去一个余数为1的最小数,或者减去两个余数为2的最小数,即可求得最大值
  • sum%3==2,此时减去两个余数为1的最小数,或者减去一个余数为2的最小数,即可求得最大值。

3.4 代码实现:

class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        //贪心:首先求取所有元素和
        const int INF=0x3f3f3f3f;
        int sum=0,x1=INF,x2=INF,y1=INF,y2=INF;
        for(auto e:nums)
        {
            sum+=e;
            if(e%3==1)
            {
                if(e<x1)
                {
                    x2=x1;
                    x1=e;//更新最小值
                }
                else if(e<x2) x2=e;
            }
            else if(e%3==2)
            {
                if(e<y1)
                {
                    y2=y1;
                    y1=e;//更新最小值
                }
                else if(e<y2) y2=e;
            }
        }
        if(sum%3==0) return sum;//刚好可以整出,直接返回
        else if(sum%3==1) return max(sum-x1,sum-y1-y2);
        else return max(sum-x1-x2,sum-y1);


    }
};

四、距离相等的条形码

4.1 题目链接:距离相等的条形码

4.2 题目分析:

  • 在一个仓库里,有一排条形码,其中第 i 个条形码为 barcodes[i]。

  • 请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。

4.3 思路讲解:

贪⼼策略:

  • 每次处理⼀批相同的数字,往 n 个空⾥⾯摆放;
  • 每次摆放的时候,隔⼀个格⼦摆放⼀个数;
  • 优先处理出现次数最多的那个数。

4.4 代码实现:

class Solution {
public:
    vector<int> rearrangeBarcodes(vector<int>& barcodes) {
        unordered_map<int,int> hash;
        int maxvalue=0,maxcount=0;//记录出现次数最多的值
        for(auto a:barcodes)
        {
            if(maxcount < ++hash[a])
            {
                maxcount=hash[a];
                maxvalue=a;
            }
        }//求出出现次数最多的数
        //先排列出现次数最大的数
        int index=0;
        int n=barcodes.size();
        vector<int> ret(n);
        for(int i=0;i<maxcount;i++)
        {
            ret[index]=maxvalue;
            index+=2;

        }
        hash.erase(maxvalue);//更新哈希
        //排列剩余的数字
        for(auto& [x,y] :hash)
        {
           
            for(int i=0;i<y;i++)
            {
                 if(index>=n) index=1;//处理越界情况
                 ret[index]=x;
                 index+=2;

            }

        }
        return ret;
        
    }
};

五、重构字符串

5.1 题目链接:重构字符串

5.2 题目分析:

  • 给定一个字符串 s ,检查是否能重新排布其中的字母,使得两相邻的字符不同。

  • 返回 s 的任意可能的重新排列。若不可行,返回空字符串 “” 。

5.3 思路讲解:

本题与上篇中的条形码类似,但是上题保证存在合法情况,本题可能会返回空字符串。
根据上篇思路,我们知道首先需要排列出现次数最多的字母maxcount。

  • 字符串长度为n,如果maxcount>(n+1)/2,那么一定会存在相邻情况,直接返回空即可(鸽巢原理)
  • 剩余思路与上题类似

5.4 代码实现:

class Solution {
public:
    string reorganizeString(string s) {
        int n=s.size();
        unordered_map<char,int> hash;
        int maxcount=0;
        char maxch;
        for(auto e:s)
        {
            if(maxcount < ++hash[e])
            {
                maxch=e;
                maxcount=hash[e];
            }//更新最大次数和字符
        }
        //判断能否排列
        if(maxcount>(n+1)/2)
        {
            return "";
        }
        string ret(n,' ');//初始化
        //先排列出现次数最多的字符串
        int index=0;//索引
        for(int i=0;i<maxcount;i++)
        {
            ret[index]=maxch;
            index+=2;
        }
        hash.erase(maxch);
        //排列剩下字符
        for(auto [a,b] : hash)
        {
            for(int i=0;i<b;i++)
            {
                if(index>=n) index=1;
                ret[index]=a;
                index+=2;
            }
        }
        return ret;

        
    }
};

小结

本篇关于贪心算法的介绍就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值