
引言
初篇我们介绍了贪心算法的相关背景知识,本篇我们将结合具体题目,进一步深化大家对于贪心算法的理解和运用。
一、整数替换
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;
}
};
小结
本篇关于贪心算法的介绍就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!