前言
欢迎大家积极在评论区留言发表自己的看法,知无不言,言无不尽,养成每天刷题的习惯,也可以自己发布优质的解题报告,供社区一同鉴赏,吸引一波自己的核心粉丝。
今天是五月集训第四天:贪心
一、练习题目
1221. 分割平衡字符串
1827. 最少操作使数组递增
2144. 打折购买糖果的最小开销
1400. 构造 K 个回文字符串
二、算法思路
- 1、 1221. 分割平衡字符串:原来的字符串是一个平衡的字符串,所以一定能通过n次的切割转换成平衡的字符串。用一个统计数组遇到’L’在第一个位置加一,遇到’R’在第二个位置加一,如果相等说明可以贪心的切一刀,答案计数器加一,接着把统计数组全部置零继续去找。
- 2、1827. 最少操作使数组递增:就从下标为1的开始,如果当前位置比前一个数字要大就不需要操作,否则我需要将当前数更新为前一个数加1的值,操作的次数就是前一个位置数-当前位置数 + 1,统计完整个个数组返回最终的操作数即可。
- 3、2144. 打折购买糖果的最小开销:
1.本题要计算购买所有物品的最小开销,且可以买二赠一,但是赠送的一定比买的这两个便宜;
2.根据贪心的思想,我们每次可以选择买最贵的两个物品,然后赠送第三贵的物品,然后再从剩余的物品中重复这一操作,剩余物品不足两个时直接购买,最后得到的就是最小开销;
3.那么,虽然解题思路非常容易理解,但是如何证明这样贪心求解一定是最优解呢?简单通过反证法来证明:如果每次购买的不是最贵的两个,而是任意两个物品,这里假设买了第二和第三贵的物品,那么获赠的只能是第四贵及以下的物品,最贵的物品仍然必须购买,那么,局部花费为:cost[1]+cost[2]+cost[3],而贪心求解的结果为买第一、第二、第四贵:cost[1]+cost[2]+cost[4],花费更小 - 4、1400. 构造 K 个回文字符串:
1、构成的回文字符串可以是:abba, cabbac,每个字母出现的次数都是偶数;abyab,c,这种形式的回文串是中间出现了一个奇数的字母。
abba | k |
---|---|
abba | 1 |
aba b or bab a | 2 |
aa b b or bb a a | 3 |
a b b a | 4 |
从表格可以看出只要k不大于字符串的长度的话有偶数个字母的情况怎么都可以变成回文字符串的。但是如果有奇数个字母就不一样了,举例:abcbay k = 2,那就能拆成 abcba 和 y这样的回文,但是如果k = 1呢,会发现奇数个字母c和y出现的次数是2次,不管怎样都是构不成回文的,因为奇数字母需要插入到偶数字母才能构成回文,无论如何都会多出一个。
2、总结的话,如果有奇数的字母出现,次数必须小于等于k的值,然后k的值也必须小于字符串的长度满足这俩条件之一才可以构成回文字符串。
三、源码剖析
// 1221. 分割平衡字符串
class Solution {
public:
int balancedStringSplit(string s) {
vector<int> counter(2, 0); //(1)
int ans = 0;
for(auto ch : s) {
if(ch == 'L') {
counter[0]++;
} else
counter[1]++; //(2)
if(counter[0] == counter[1]) { //(3)
ans++;
counter[0] = 0;
counter[1] = 0;
}
}
return ans;
}
};
- 1、创建一个统计数组统计’L’和’R’的数量;
- 2、找到’L’或者’R’相应的计数器加一;
- 3、如果发现统计的计数器相等了说明可以贪心的切一刀构成平衡字符串了,答案计数器加一,然后统计计数器清零继续找。
// 1827. 最少操作使数组递增
class Solution {
public:
int minOperations(vector<int>& nums) {
int n = nums.size(), ans = 0;
for(int i = 1; i < n; i++) {
if(nums[i] <= nums[i - 1]) { //(1)
ans += nums[i - 1] - nums[i] + 1; //(2)
nums[i] = nums[i - 1] + 1; //(3)
}
}
return ans;
}
};
- 1、如果当前的数小于等于前一个那就说明我们要操作一下把它变大;
- 2、操作次数 = 前一个数 - 当前数 + 1;
- 3、更新当前数,变成钱一个数加一。
// 2144. 打折购买糖果的最小开销
class Solution {
public:
int minimumCost(vector<int>& cost) {
int n = cost.size(), ans = 0, i = n - 1;
sort(cost.begin(), cost.end());//(1)
while(i >= 0) {
ans += cost[i]; //(2)
if(i - 1 >= 0) //(3)
ans += cost[i - 1];
i -= 3;
}
return ans; //(4)
}
};
- 1、按升序排序,i设为数组末端先买最贵的;
- 2、因为买俩送一,先加一个进来,因为有可能只剩下最后一个了或者一开始就是只有一个卖;
- 3、大于等于0说明后面至少还有一个,继续买第二个;
- 4、买二送一,说明下一轮从当前位置减3的地方开始重复上述步骤(2),直到糖果已经无法再买为止退出循环返回购买的价格。
// 1400. 构造 K 个回文字符串
class Solution {
public:
bool canConstruct(string s, int k) {
int hash[26];
int n = s.length();
memset(hash, 0, sizeof(hash));
for(int i = 0; i < n; i++) {
hash[s[i] - 'a']++; //(1)
}
int cnt = 0;
for(int i = 0; i < 26; i++) {
if (hash[i] & 1) //(2)
cnt++;
}
if(k < cnt || k > n) //(3)
return false;
return true;
}
};
- 1、用哈希表统计字母出现的次数;
- 2、如果出现奇数个字母计数器加一
- 3、奇数个字母的数量如果大于k或者k大于字符串长度返回false,否则返回true。