[学习报告]《LeetCode零基础指南》(第六讲)贪心

一,贪心问题分析

贪心算法可以说是用刷题量来堆出来的,在使用它的时候我们往往不会给出严格的数学证明,甚至使用这个算法的本人也不知道为什么会这样去处理,所以贪心算法主要还是要靠我们平时见到过足够多的题来去积累尽量多的算法套路

二,做题记录:

1.1913. 两个数对之间的最大乘积差

原题链接

两个数对 (a, b) 和 (c, d) 之间的 乘积差 定义为 (a * b) - (c * d) 。
例如,(5, 6) 和 (2, 7) 之间的乘积差是 (5 * 6) - (2 * 7) = 16 。
给你一个整数数组 nums ,选出四个 不同的 下标 w、x、y 和 z ,使数对 (nums[w], nums[x]) 和 (nums[y], nums[z]) 之间的 乘积差 取到 最大值 。
返回以这种方式取得的乘积差中的 最大值 。
示例 1:
输入:nums = [5,6,2,7,4]
输出:34
解释:可以选出下标为 1 和 3 的元素构成第一个数对 (6, 7) 以及下标 2 和 4 构成第二个数对 (2, 4)
乘积差是 (6 * 7) - (2 * 4) = 34
示例 2:
输入:nums = [4,2,5,9,7,4,8]
输出:64
解释:可以选出下标为 3 和 6 的元素构成第一个数对 (9, 8) 以及下标 1 和 5 构成第二个数对 (2, 4)
乘积差是 (9 * 8) - (2 * 4) = 64
提示:
4 <= nums.length <= 1 0 4 10^4 104
1 <= nums[i] <= 1 0 4 10^4 104

先看题目数据量 n u m s . l e n g t h > = 4 nums.length>=4 nums.length>=4,也就是说题目给出的数组一定是存在一个合法结果的,那么再看结果要我们求出数组中四个元素 a , b , c , d a,b,c,d a,b,c,d a ∗ b − c ∗ d a*b-c*d abcd最大值,那么就很自然的想到 a , b a,b a,b要尽量大, c , d c,d c,d要尽量小,排序之后返回数组最后两个元素乘积减去前两个元素乘积即可;

int cmp(int *a,int *b){
    return *a-*b;
}
int maxProductDifference(int* nums, int numsSize){
    qsort(nums,numsSize,sizeof(int),cmp);
    return nums[numsSize-1]*nums[numsSize-2]-nums[0]*nums[1];
}

在这里插入图片描述

2.976. 三角形的最大周长

原题链接

给定由一些正数(代表长度)组成的数组 nums ,返回 由其中三个长度组成的、面积不为零的三角形的最大周长 。如果不能形成任何面积不为零的三角形,返回 0。
示例 1:
输入:nums = [2,1,2]
输出:5
示例 2:
输入:nums = [1,2,1]
输出:0

和上一题思路一样,要使得三个元素构成的合法三角形周长最大,我们将数组按照降序排序,再从头开始遍历,遍历途中寻找三个合法的三边直接返回即可。

int cmp(int *a,int *b){
    return *b-*a;
}
int largestPerimeter(int* nums, int numsSize){
    qsort(nums,numsSize,sizeof(int),cmp);
    for(int i=0;i<numsSize-2;i++){
        if((nums[i+1]+nums[i+2])>nums[i]){
            return nums[i+1]+nums[i+2]+nums[i];
        }
    }
    return 0;
}

在这里插入图片描述

3.数组拆分Ⅰ

原题链接

给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。
返回该 最大总和 。
示例 1:
输入:nums = [1,4,3,2]
输出:4
所以最大总和为 4
示例 2:
输入:nums = [6,2,6,5,1,2]
输出:9

  吧数组中的 2 n 2n 2n个元素分成 n n n个数对 ( a i , b i ) (ai,bi) (ai,bi),并且使得数对中最小值的总和最大,我们的思路就是尽量使得数组中较大的元素能够充当较多的数对中的最小值,那么如何选择理想的数对就成了问题。
  由于题目中的条件,那么首先最小的元素我们一定会选上,最大的数字一定选不上,接着向中间扩展,由于我们想要保留尽量多的较大数,那么选择最小数的时候我们可以将第二小的数跟他组成一个数对,排除的最大数字的时候,我们将第二大的数字跟他组成一个数对,这样数组中元素就少了四个。接下来重复这个过程,我们会发现这样得到的结果跟题目中的要求是吻合的,并且在扩展的过程中不难发现,我们每次选择的数字都是讲数组升序排序之后下标为偶数的数字。这样这道题的代码就很简单了.

int cmp(int *a,int *b){
    return *a-*b;
}
int arrayPairSum(int* nums, int numsSize){
    qsort(nums,numsSize,sizeof(int),cmp);
    int ans=0;
    for(int i=0;i<numsSize;i+=2){
        ans+=nums[i];
    }
    return ans;
}

在这里插入图片描述

4.救生艇

原题链接

给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
返回 承载所有人所需的最小船数 。
示例 1:
输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)
示例 2:
输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)
示例 3:
输入:people = [3,5,3,4], limit = 5
输出:4
解释:4 艘船分别载 (3), (3), (4), (5)
提示:
1 <= people.length <= 5 * 10^4
1 <= people[i] <= limit <= 3 * 10^4


这一题还是有一定的难度的,同时它的提示十分重要,首先他告诉了我们$people[ i ] people[i]people[i]<=limit$那么也就是说我们必须把所有人都送走,接下来这样考虑 1 ) 题目要求我们返回把所有人均送走的最小船数,并且每个船在两人的重量和小于$limit$时可以载走他们。那么很自然的想到在载重不超过$limit$的情况下每条船要尽量找到两个重量之和最接近$limit$的人,使得空间得到充分利用。 2 ) 这里我们利用贪心思想,因为要使两人的重量尽量靠近$limit$首先对数组进行升序排序,接着使得数组最右侧的人和最左侧的人放到一组,也即是决定将目前还没有运走的人中最重的人和最轻的人尝试匹配到一组中去。 如果匹配不成功则为最重的人单独分配一艘船。 因为若是最重的人与最轻的人也无法匹配成功那么他与剩下的人也必定无法匹配成功,只能自己乘坐一艘船。 如果匹配成功的话,那么接着匹配次重和次轻的人,如此往复即可。
int cmp(int *a,int *b){
    return *a-*b;
}
int numRescueBoats(int* people, int peopleSize, int limit){
    qsort(people,peopleSize,sizeof(int),cmp);
    int l=0,r=peopleSize-1;
    int ans=0;
    while(l<=r){
        if(people[l]+people[r]<=limit){
            l++;
        }
        r--;
        ans++;
    }
    return ans;
}

在这里插入图片描述

5.324. 摆动排序 II

原题链接

给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。
你可以假设所有输入数组都可以得到满足题目要求的结果。
示例 1:
输入:nums = [1,5,1,1,6,4]
输出:[1,6,1,5,1,4]
解释:[1,4,1,5,1,6] 同样是符合题目要求的结果,可以被判题程序接受。
示例 2:
输入:nums = [1,3,2,2,3,1]
输出:[2,3,1,3,1,2]
提示:
1 <= nums.length <= 5 * 104
0 <= nums[i] <= 5000
题目数据保证,对于给定的输入 nums ,总能产生满足题目要求的结果

  注意这道题没有返回值,所以我们要原地修改数组,并且注意到这里只需要数组满足符合条件的任意一个情况即可,那么我们来考虑如何达到题目要求的条件。
  题目要求数组每个奇数下标要大于偶数下标,也即是说数组中较小的元素位于偶数下标上,较大的元素位于奇数下标上,那么这道题的思路就很简单了:我们先将原数组拷贝一份出来,并对其按照升序排序,接着按下标插入前半部分和后半部分的数即可。

int cmp(int *a,int *b){
    return *a-*b;
}
void wiggleSort(int* nums, int numsSize){
    int * tmp=(int*)malloc(sizeof(int)*numsSize);
    for(int i=0;i<numsSize;i++){
        tmp[i]=nums[i];
    }
    qsort(tmp,numsSize,sizeof(int),cmp);
    int pos=numsSize-1;
    int mid=pos/2;
    for(int i=0;i<numsSize;i++){
        nums[i]=i&1?tmp[pos--]:tmp[mid--];
    }
    
}

在这里插入图片描述

6.455.分发饼干

原题链接

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例 2:
输入: g = [1,2], s = [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

1 ) 1) 1先看题目对两个数组的要求:每个孩子只能分得一个饼干,并且每个孩子拥有一个胃口数组 g [ i ] g[i] g[i],表示第 i i i个孩子的胃口值为 g [ I ] g[I] g[I],同时只有饼干的尺寸 s [ i ] s[i] s[i]大于某个 g [ i ] g[i] g[i]时我们才能吧饼干分配给他
2 ) 2) 2)那么为了使得分到饼干的孩子最多我们要尽量满足他们的胃口值,这就要求尺寸较大的饼干去尝试分配给胃口较大的孩子(如果尺寸较大的饼干分配给胃口较小的孩子会使得本来可以分出去的尺寸较小的饼干无法分出),首先先对两个数组排序,接着利用双指针不断进行枚举当前尺寸和当前孩子的胃口值如果符合两个指针均向左移动, a n s + + ans++ ans++。否则就是当前最大尺寸的饼干也无法满足当前的孩子胃口值,说明无论如何目前的孩子都无法得到饼干,指向胃口值的指针左移,直到饼干指针或者胃口指针越界。

int cmp(int *a,int *b){
    return *a-*b;
}
int findContentChildren(int* g, int gSize, int* s, int sSize){
    qsort(g,gSize,sizeof(int),cmp);
    qsort(s,sSize,sizeof(int),cmp);
    int gp=gSize-1;
    int sp=sSize-1;
    int ans=0;
    while(sp>=0&&gp>=0){
        ans+=g[gp]<=s[sp]?(gp--,sp--,1):(gp--,0);
        //逗号表达式的返回值为最后一个元素,如果不理解可以看下列代码
        /*if(g[gp]<=s[sp]){
        	ans++;
        	gp--,sp--;
        }else gp--;
        */
        
    }
    return ans;
}

在这里插入图片描述

7.1827. 最少操作使数组递增

原题链接

给你一个整数数组 nums (下标从 0 开始)。每一次操作中,你可以选择数组中一个元素,并将它增加 1 。
比方说,如果 nums = [1,2,3] ,你可以选择增加 nums[1] 得到 nums = [1,3,3]
请你返回使 nums 严格递增 的 最少 操作次数。
我们称数组 nums 是 严格递增的 ,当它满足对于所有的 0 <= i < nums.length - 1 都有 nums[i] < nums[i+1] 。一个长度为 1 的数组是严格递增的一种特殊情况。
示例 1:
输入:nums = [1,1,1]
输出:3
示例 2:
输入:nums = [1,5,2,4,1]
输出:14
示例 3:
输入:nums = [8]
输出:0

根据题意进行模拟即可,定义 p r e v prev prev变量存储上一个元素,如果当前数字小于等于 p r e v prev prev,吧当前数字变为前一个数字减一, p r e v prev prev++否则 p r e v prev prev等于当前元素并继续向后移动.

int minOperations(int* nums, int numsSize){
    if(numsSize==0){
        return 0;
    }
    int ans=0;
    int prev=nums[0];
    for(int i=1;i<numsSize;i++){
        if(prev>=nums[i]){
            ans+=prev-nums[i]+1;
            prev++;
        }else prev=nums[i];
    }
    return ans;
}

在这里插入图片描述

8.945. 使数组唯一的最小增量

原题链接

给你一个整数数组 nums 。每次 move 操作将会选择任意一个满足 0 <= i < nums.length 的下标 i,并将 nums[i] 递增 1。
返回使 nums 中的每个值都变成唯一的所需要的最少操作次数。
示例 1:
输入:nums = [1,2,2]
输出:1
解释:经过一次 move 操作,数组将变为 [1, 2, 3]。
示例 2:
输入:nums = [3,2,1,2,1,7]
输出:6
解释:经过 6 次 move 操作,数组将变为 [3, 4, 1, 2, 5, 7]。
可以看出 5 次或 5 次以下的 move 操作是不能让数组的每个值唯一的。
提示:
1 <= nums.length <= 10^5
0 <= nums[i] <= 10^5

跟上一个题目一样,不同的是我们需要先将数组进行排序,然后按照上一题的思路即可.

int cmp(int *a,int *b){
    return *a-*b;
}
int minIncrementForUnique(int* nums, int numsSize){
    if(numsSize==0){
        return 0;
    }
    int ans=0;
    qsort(nums,numsSize,sizeof(int),cmp);
    int prev=nums[0];
    for(int i=1;i<numsSize;i++){
        if(prev>=nums[i]){
            ans+=prev-nums[i]+1;
            prev++;
        }else prev=nums[i];
    }//核心代码和上一题完全一致
    return ans;
}

在这里插入图片描述

9.611. 有效三角形的个数

原题链接

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。
示例 1:
输入: nums = [2,2,3,4]
输出: 3
解释:有效的组合是:
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3
示例 2:
输入: nums = [4,2,3,4]
输出: 4
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000

这里介绍一种简单易懂的方法,为了能够快速求出有效三角形的个数我们先把数组进行升序排序,接着从第三个元素向前进行枚举,如果遇到 n u m s [ l ] + n u m s [ r ] < n u m s [ i ] nums[l]+nums[r]<nums[i] nums[l]+nums[r]<nums[i]跳出,枚举下一个边即可。

int cmp(int *a,int*b){
    return *a-*b;
}
int triangleNumber(int* nums, int numsSize){
    if(numsSize<3)
    return 0;
    qsort(nums,numsSize,sizeof(int),cmp);
    int n=numsSize;
    int ans=0;
    for(int i=2;i<n;i++)
    {
    //从排序后的第三条边开始枚举
       int l=0,r=i-1;//l是数组中最小的边,r是比当前边小的最长边
       while(l<r)
       {
            if(nums[l]+nums[r]>nums[i])
            //如果符合条件那么[l-r]的边全部符合
            //否则就是两边之和大于第三边了这时候我们要移动较长边,若是移动较短边两边之和只会越来越大
            {
                ans+=r-l;
                r--;
            }
            else l++;
            //两边之和<=第三边,移动较短边
            //这种处理方法在处理子集的问题时十分常见可以多加练习
        }
    }
    return ans;
}

在这里插入图片描述

三,本日收获

本日的练习题来自英雄那里归来的LeetCode零基础指南专栏,感兴趣的可以自己尝试《LeetCode零基础指南》(第六讲) 贪心,对于贪心算法的题目,如果我们无法想出合理的方法那么还是说明我们积累的做题量还是不够,应该多加练习。贪心就是没有什么快速提高的方法,就是要凭借我们日积月累的做题量。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值