九日集训(第六天)贪心
零、昨日复盘
今天是九日集训的第六天,我们来学习贪心的概念,视频版请 戳我。
一、概念定义
所谓贪心,总是做出在当前看来是最好的选择。也就是说,不从整体最优上进行考虑,算法得到的是在某种意义上的局部最优解。
比如,对于一个全是正整数的数组,我要找到其中两个数,使得它们的乘积最大,毫无疑问,一定是取 最大 和 次大 的两个数进行相乘,得到的结果最大。这个就是贪心思想。
那么接下来,让我们做几道例题,来真正了解一下贪心。
二、题目分析
1、最大乘积差
- (1) 先对数组进行排序;
- (2) 利用贪心,直接取最大的两个数下标作为 w 和 x,最小的两个数下标作为 y 和 z。
2、三角形的最大周长
- (1) 利用三角形两边之和大于第三边的性质,假设小的两条边为 a 和 b,大的那条为 c,那么必须满足如下等式才能满足它是一个三角形: a + b > c
这个问题要求最大的三角形周长,那么势必是最大的那条边 c 越大越好,所以我们可以枚举将所有的边按照递增排序,然后逆序枚举最大的那条边 c,去剩下的边里找小的两条边,最好的情况肯定是比 c 小的最大和次大边最优,如果这两条边都不能满足上述不等式,剩下的边也就肯定也不满足了,所以只需要一个循环即可解决问题。
3、数组拆分 I
- (1) 对数组进行排序;
- (2) 选择偶数项进行相加,就是最大总和。
4、救生艇
- (1) 按照重量从小到大排序;
- (2) 如果只剩一个人,那么直接加上一只船,并且跳出循环;
- (3) 如果最重的那个人和最轻的那个人加起来不能坐一条船,那么最重的那个人势必只能 "一意孤行" 了,因为其他人更加不可能和他同行。转变成 的子问题。
- (4) 如果最重的那个人可以和最轻的人一起坐一条船,那就顺带捎上,转变成 的子问题。
5、摆动排序 II
- (1) 这题要求没有返回值的原地排序,所以我们可以先将数组拷贝一份出来;
- (2) 然后对拷贝的数组进行排序操作;
- (3) 然后,不断进行插孔,先插奇数位置;
- (4) 再插偶数位置;
6、分发饼干
- (1) 分别对 胃口值 和 饼干 都进行递增排序;
- (2) 然后将两个游标都指向 胃口值 和 饼干 的末尾;
- (3) 如果某一个 饼干 和 胃口值 满足s[j] >= g[i]的条件,则两个游标都往前移动一格,并且答案加一;
- (4) 否则,胃口值的游标往前移动一格,寻找更加容易满足条件的解,直到 饼干 或者 胃口值 枚举完毕;
7、最少操作使数组递增
- (1) 如果 当前的数 比 前一个数 大,则没有任何消耗,并且重置 前一个数 为 当前数加一,进行下一轮判断;
- (2) 否则,需要把 当前的数 变成 前一个数加一;
8、有效三角形的个数
- (1) 首先,将所有数组元素按照递增排序;
- (2) 利用三个游标 i、j、k,递增枚举三条边;
- (3) 如果遇到 nums[i] + nums[j] <= nums[k] 则直接跳出第三层循环,因为边是递增的,后面的 k 都会满足这个条件,从而无法构成三角形;
- (4) 在 i 固定的情况下,满足条件的边数就是 (j, k) 二元组的对数(从循环的跳出条件来看 k 的取值是 j+1,j+2,j+3, ...,k-1,所以总的个数就是 (k-1)-(j+1) + 1 = k-j-1 );
- (5) 继续自增 j,如果发现 j 和 k 相等,则自增 k; 总体来说,就是枚举 i 和 j, k 是跟着 j 一起走的,所以时间复杂度为 O(n*n)。
九日集训(第六天)贪心
三、课后习题
课后所有习题,在上文都能找到答案,所以务必将下面的题全部刷完。 第六天了,有什么感觉吗?最后四天,相信已经不用激励,你已经养成了习惯,自驱动吧! 坚持!加油!你可以的!
561. 数组拆分
给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。
返回该 最大总和 。
示例 1:
输入: nums = [1,4,3,2] 输出: 4 解释: 所有可能的分法(忽略元素顺序)为:
- (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
- (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
- (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4 所以最大总和为 4
示例 2:
输入: nums = [6,2,6,5,1,2] 输出: 9 解释: 最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9
提示:
1 <= n <= 10^4nums.length == 2 * n-10^4 <= nums[i] <= 10^4
代码
class Solution {
public:
int arrayPairSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
int n = nums.size();
int ans = 0; // 初始化累加和 ans 为 0
for (int i = 0; i < n; i += 2) {
ans += nums[i];
}
return ans;
}
};
这段代码的逻辑是通过先对数组 nums 进行排序,然后遍历排序后的数组,每次跳过一个元素(即以步长为2进行循环),将当前元素加到累加和 ans 中。这样做的目的是利用了一个性质:在已排序的数组中,每对相邻的元素构成的对 (ai, bi) 中的较小值总是在偶数索引(因为数组是从0开始索引的,所以这里的“偶数”是指实际的索引+1之后的偶数)。通过这种方法,可以保证加和的是每对中较小的那个,从而达到题目要求的“使得从 1 到 n 的 min(ai, bi) 总和最大”。
简而言之,这个策略是基于“排序后的相邻成对挑选”的策略,保证了能够获得最大的 min(ai, bi) 总和。