64,192
社区成员
发帖
与我相关
我的任务
分享目录
今日是九日集训打卡的第六天,今天学习的内容是贪心算法,也就局部最优解的算法。
两个数对 (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]) 之间的 乘积差 取到 最大值 。返回以这种方式取得的乘积差中的 最大值 。
int cmp(const void*a,const void*b){
return *(int*)a-*(int*)b;
}
int maxProductDifference(int* nums, int numsSize){
qsort(nums,numsSize,sizeof(int),cmp);
return (nums[numsSize-1]*nums[numsSize-2]-nums[0]*nums[1]); //贪心
}
需要返回最大乘积差值直接将最大两数相乘减去最小两项即可,直接排序后取最后两项和前两项即可。
给定由一些正数(代表长度)组成的数组
nums,返回 由其中三个长度组成的、面积不为零的三角形的最大周长 。如果不能形成任何面积不为零的三角形,返回0。
int max(int a,int b){ //可以使得代码简洁
return a>b?a:b;
}
int cmp(const void*a,const void*b){
return *(int*)a-*(int*)b;
}
int largestPerimeter(int* nums, int numsSize){
qsort(nums,numsSize,sizeof(int),cmp); //这里的排序很有必要,详见题解
int ans=0;
for(int i=2;i<numsSize;i++){
if((nums[i-2]+nums[i-1])>nums[i]){ //满足两边之和大于第三边面积才不为0
ans=max(ans,nums[i]+nums[i-1]+nums[i-2]); //通过循环与比较使得ans为最大值
}
}
return ans;
}
排序的作用即为实现贪心,详见 [学习报告]《LeetCode零基础指南》(第五讲)排序(qsort)。
给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。
返回该 最大总和 。
int cmp(const void*a,const void*b){
return *(int*)a-*(int*)b;
}
int arrayPairSum(int* nums, int numsSize){
int ans=0;
qsort(nums,numsSize,sizeof(int),cmp); //排序
for(int i=0;i<numsSize;i+=2){ //排序后每一组中偶数项都最小
ans+=nums[i];
}
return ans;
}
首先对数组进行排序,当排序完成之后,按照题目所分的分法进行分对之后,a 总是小于 b 的值,也就是每一组的 min(a,b)都取 a ,而 a 元素占据的是排序后数组的偶数下标部分,直接全部都取出加和后返回即可。
给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
返回 承载所有人所需的最小船数 。
int cmp(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
int numRescueBoats(int* people, int peopleSize, int limit){
int i;
int l = 0, r = peopleSize-1; //这里的l和r实际上指的是最轻的人和最重的人
int ans = 0;
qsort(people, peopleSize, sizeof(int), cmp); //按从小到大排序
while(l <= r) { //人全被送走之前
if(l == r) {
++ans; break; // 当l=r时也就只剩下一个人了,坐一艘然后直接跳出循环即可
} else if(people[l] + people[r] > limit) { // 当最重和最轻都超过限制时,最重就只能自己坐一艘了
++ans, r--; //所以这时候把最重的用一艘船送走最重的变成下一个
} else //这种情况指的是最重和最轻两个人可以坐同一艘的情况
++ans, ++l, --r; //所以这个时候只需要一艘可以送走最轻和最重两个人
}
return ans;
}
详见注释。
给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序。
你可以假设所有输入数组都可以得到满足题目要求的结果。
int cmp(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
void wiggleSort(int* nums, int numsSize){
int *ret=(int*)malloc(sizeof(int)*numsSize);
for(int i=0;i<numsSize;i++){
ret[i]=nums[i];
}
qsort(ret,numsSize,sizeof(int),cmp);
int r=numsSize-1; //末位元素下标
for(int i=1;i<numsSize;i+=2){ //先对奇数下标项填充
nums[i]=ret[r--];
}
for(int i=0;i<numsSize;i+=2){ //再对偶数下标项填充
nums[i]=ret[r--];
}
}
这题没有需要返回数组,但是直接只对一个数组进行处理比较麻烦,因此将原数组复制了一份处理后往回填充。填充的思路如下:由题意可知,此函数的元素总数一定为偶数,于是我们先将数组排序后再分为两半来看,这样后半所有的元素都将会大于前半的元素,这下只需要把后面的那一半的元素填入前面的空隙即可,按下标来看就是填入后半的数字到奇数下标项,因此定义了 r 作为末位下标,每填一个数字之后就往前移动一格,在填完后半的数字之后刚好下标来到了前半,那就继续使用一样的办法把前半的数字填入偶数下标项。
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
int cmp(const void*a,const void*b){
return *(int*)a-*(int*)b;
}
int findContentChildren(int* g, int gSize, int* s, int sSize){
qsort(g,gSize,sizeof(int),cmp);
qsort(s,sSize,sizeof(int),cmp); //先将两个数组排序
int i=gSize-1,j=sSize-1; //默认两个标记都在数组末位
int ans=0;
while(i>=0&&j>=0){ //当任意的数组越界时结束循环
if(s[j]>=g[i]){ //如果s>=g,这时候满足条件,两个标记一起移动,ans+1
ans++;
j--;
i--;
}
else{ //如果不满足条件,说明这个小朋友已经满足不了了,看下一个
i--;
}
}
return ans;
}
此题既然需要最多满足条件的解,那么就需要排序了,首先对两个数组全都进行排序。然后分别在两个数组的末位元素留一个标记,随后开始循环进行饼干分配(一旦有哪个数组的下标先越界了就结束循环)。如果分量 s 大于食量 g 那么这个时候小朋友就被满足了,因此饼干减一,需要满足的小朋友数量减一。那么一旦当这个饼干不可以满足小朋友的时候怎么办呢?身为一个好家长,如果连目前最大的饼干都已经满足不了这个小朋友了,那这个小朋友就只能先不去管,去看下一个有可能被满足的小朋友了,因此只有食量 g 数组的下标移动。
给你一个整数数组 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 的数组是严格递增的一种特殊情况。
int minOperations(int* nums, int numsSize){
if(numsSize==1){
return 0;
}
int ans=0;
for(int i=1;i<numsSize;i++){
while(nums[i]<=nums[i-1]){
nums[i]++;
ans++;
}
}
return ans;
}
先来看看暴力的解法,把特殊情况(数组长度为1)去掉之后,直接开始循环,如果当前元素比前一个元素小,就一直循环加一到比前一个数大,然后同时操作次数同步加一。很暴力,但是效率低的离谱(过啦!!320ms击败了百分之9的人!),而其实稍微想一想也想的明白,其实只要把内部那个循环加一改一改,数字加几,操作数就加几就好了。
int minOperations(int* nums, int numsSize){
if(numsSize==1){
return 0;
}
int ans=0;
for(int i=1;i<numsSize;i++){
if(nums[i]<=nums[i-1]){
ans=ans+(nums[i-1]-nums[i]+1);
nums[i]=nums[i-1]+1;
}
}
return ans;
}
效率超大幅提升。(320ms→8ms)
给你一个整数数组 nums 。每次 move 操作将会选择任意一个满足 0 <= i < nums.length 的下标 i,并将 nums[i] 递增 1。
返回使 nums 中的每个值都变成唯一的所需要的最少操作次数。
int cmp(const void*a,const void*b){
return *(int*)a-*(int*)b;
}
int minIncrementForUnique(int* nums, int numsSize){
if(numsSize==1){
return 0;
}
qsort(nums,numsSize,sizeof(int),cmp);
int ans=0;
for(int i=1;i<numsSize;i++){
if(nums[i]<=nums[i-1]){
ans=ans+(nums[i-1]-nums[i]+1);
nums[i]=nums[i-1]+1;
}
}
return ans;
}
与上题很类似,不过这题可以要先排序一下,这样在操作之后可以保证肯定没有重复。
给定一个包含非负整数的数组
nums,返回其中可以组成三角形三条边的三元组个数。
int cmp(const void*a,const void*b){
return *(int*)a-*(int*)b;
}
int triangleNumber(int* nums, int numsSize){
qsort(nums,numsSize,sizeof(int),cmp); //首先先对数组进行排序
int i=0,j=1,k=numsSize-1; //三角形有三条边,这下需要三个变量来控制下标了,从两头逼近
int ans=0;
for(j=1;j<numsSize-1;j++){ //j开始向右逼近
for(i=0;i<j;i++){ //i从0开始到j-1每一种情况列出
while(i<j&&k>j){ //防止元素重合
if(nums[k]>=nums[i]+nums[j]){ //找到满足三角形的k下标
k--;
}else{
ans=ans+(k-j); //从k开始到j+1的所有元素都满足
break;
}
}
k=numsSize-1; //重置k
}
}
return ans;
}
这是不贪心(1700+ms)的暴力解法,详细题解都在注释中写出。接下来看一种高效的写法:
int cmp(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
int triangleNumber(int* nums, int numsSize){
int i, j, k;
int ans = 0;
qsort(nums, numsSize, sizeof(int), cmp); //首先进行排序
for(i = 0; i < numsSize; ++i) {
j = i + 1;
k = j + 1; //这样可以防止越界
while(j < numsSize) {
while(k < numsSize) {
if(nums[i] + nums[j] <= nums[k]) {
break; // 一旦出现两边只和小于第三边就没必要继续了,后面只会更大
}
++k; //在过大之前一直增加下标,这其中的元素都符合要求
}
ans += k-j-1; // 这是最后的k到j+1之间所有元素都满足
++j; // 把j向k逼近
if(k == j) k++; //防止元素重合
}
}
return ans;
}
这种写法与上面那种写法的区别很大,用 i 推进,一步步的将范围缩小,而且 j 和 k 是一起走的,所以复杂度只有O(n²)。而上面那种写法实际上只是一种枚举,i 和 k 各走各的,当 j 移动时也并没有缩小范围。复杂度是O(n³)。