LeetCode:1337. 矩阵战斗力最弱的 K 行、11. 盛最多水的容器、剑指 Offer 51. 数组中的逆序对题解

前言

博主在刷LeetCode中遇到了几道特别技巧的题,真的特别巧,明明自己做的时候难的一匹,但是当我看到别人的方法时,顿时感觉豁然开朗,就觉得能想到这么巧妙的方法的人是真的厉害!!!于是我便把这几个巧妙的方法给记录下来!!!以便后面学习!!!

一、1337. 矩阵战斗力最弱的 K 行

题目:
在这里插入图片描述
点这里:快来征服我吧!
首先我们发现题目叙述的很简单!
我们不由想一下,假设我们现在知道了每一行的军人数量,而且我们现在把它存起来放在一个数组里面,这个数组叫做战斗力数组;
比如:
在这里插入图片描述
在这里插入图片描述
该数组的下标表示第几行,其对应的值表示改行军人数量;
然后想在要我们返回战斗力最弱的k(k=3)个行标,这可就坏了;如果题目是让我们返回战斗力最弱的k个值的话,那还好说,我们直接对其进行升序排序,取其前k个返回就行了,但是现在不是这样的,题目要的是行标(战斗力数组的下标),如果我们对其进行升序排序的话,我们能够得到战斗力最弱的几个,但是他们原本对应的下标确消失了,我们也就没法返回了,相当于我们白干了一场,那么有没有什么办法能让我们带上下标在进行排序呢?
别说还真有;

大致思路:
我们首先定义一个宏,#define weighting 1000
然后呢我们让我们的:军人数*weighting+行标(对应下标)组成新的战斗力存在数组里面,形成新的战斗力,这样来说相比于原来的只有军人数目的战斗力数组来说,这个值是不是具有更多意义,同时我们新形成的值并不会改变原来数组元素间的相对大小;为什么这么说呢?
我们都知道每一行军人数目最多也就100人对吧(题目给的数据范围)!那么现在我们乘以一个weighting是不是相当于把军人数量放大了,在这时候你可以明白这时的数组元素间的大小关系还是没有变,但是假设你的weighting很大(往无穷大想)那么我的战斗力是不是也就很大,然后再来加上下标这个微小的数,这不还是没改变战斗力之间的相对大小嘛;如果还不明白的话,我在举个例子:
比如:马云很有钱吧!当然我也有钱!当我把我的钱给给马云,对于马云来说它的财产是不是没什么变化,我这给他的前对于他的才来来说就是毛毛雨!根本改变不了马云财产的位数,根本不值一提!现在我们加的这个小标就是这么个理!!!然后我们这个新新形成的战力是不是包含了我们的下标信息,既然我们存进去了,我们该如何通过战斗力拿出来呢?
我们可以这样想:
新战斗力=军人数*weighting+行标(对应下标);你看这个是不是和一个公式很像;
被除数=商*除数+余数;现在我们的余数就相当于我们的行标,而weighting就相当于除数,现在我们知道被除数和余数按照取%的操作我们就能取出想要的行标了;因此为了方便我们可以利用qsort函数来帮我们实现新战斗力数组的排序,然后再取前K个战斗力的行!!!

具体细节:
当然我们做这些的前提是我们知道了每行军人的数目,那我们该如何求呢?
题目其实给了我们一个很好的提示,我们知道军人一定再平民前面,我们就可以利用二分查找的方法来实现查找军人的数量:
假设我们nums[mid]==1,那么[left,mid]区间是不是就全是军人,我们亦可以计算出该区间的军人数:mid-left+1;从而继续搜寻[mid+1,right]区间,我们不知道该区间是否还有军人,只能去搜索;如果nums[mid]==0,那么不用说[mid,right]区间一定没有军人,[left,mid-1]区间才有军人,我们就去该区间索;以上便是该题的大致思路!!!😁😁😁。

代码实现:

#define weighting 1000
int cmp(const void* p1, const void* p2)
{
    return *(int*)p1 - *(int*)p2;
}
void count(int* nums, int left, int right, int* tmp, const int ret)//统计军人数量,ret表示对应行数tmp数组用来存储
{
    if (left > right)
        return;
    int mid = 0;
    mid = (right - left) / 2 + left;
    if (nums[mid] == 1)
    {
        tmp[ret] += mid -left+ 1;
        count(nums, mid + 1, right, tmp, ret);
    }
    else if (nums[mid] == 0)
    {
        count(nums, left, mid - 1, tmp, ret);
    }
}
void count_soldier(int** nums, int row, int col, int* tmp)
{
    int ret = 0;
    for (int i = 0; i < row; i++)
    {
        ret = i;//一行一行的搜索军人数量
        count(nums[i], 0, col - 1, tmp, ret);
        tmp[ret] = tmp[ret] * weighting + ret;//同时完成新的战斗力的计算
    }
}
int* kWeakestRows(int** mat, int matSize, int* matColSize, int k, int* returnSize) {
    int* p = (int*)calloc(matSize, sizeof(int));
    count_soldier(mat, matSize, *matColSize, p);
    *returnSize=0;
    qsort(p, matSize, sizeof(int), cmp);//排序
    for (int i = 0; i < k; i++)
    {
        p[i] = p[i] % weighting;//取出下标
        (*returnSize)++;
    }
    return p;
}

在这里插入图片描述
时间复杂度:O(rowlog col+rowlog row+k)
空间复杂度:O(row)

二、11. 盛最多水的容器

题目:
在这里插入图片描述
点这里:快来征服我吧!
题目要求很简单,就是叫我们求出能够接纳水的最大体积!!!
越是题目叙述简单的题目,越是难以下手!!!
方法一:
首先我们可以通过暴力破解的办法来求的最大面积,但是显然时间复杂度不能通过:
代码实现:

int min(int a,int c)
{
    return a>c?c:a;
}
int maxArea(int* height, int heightSize){
    int max=0;
    int i=0;
    int j=0;
    for(i=0;i<heightSize;i++)
    {
        for(j=i+1;j<heightSize;j++)
        {
            int V=(j-i)*min(height[i],height[j]);
            if(V>max)
            max=V;
        }
    }
    return max;
}

在这里插入图片描述
我们发现时间上跑不过去:
时间复杂度:O(N^2)
空间复杂度:O(1)
方法二:
其实该题我们可以通过双指针的解法来做
在这里插入图片描述
我们定义一个i和j分别表示左右两个墙壁;
我们都知道只有决定装多少水的是短的那一面;
因此我们计算水的面积就是:S=(j-i)*min(height[i],height[j]);

那我们的两个指针该如何移动呢?
1、高度短的那边指针移动;
2、高度相等的话,随便移动一个指针都可;
那么为什么要这么移动呢?
用上述例子来说明:
我们知道i=0时,height[i]=1;
j=numsSize-1时,height[j]=7;
首先我们需要明确我们无论移动那个指针,这个墙壁之间的宽度是肯定会减小的对吧!!!
假设我们移动墙壁高的那个指针,我们下一次能形成的水的最高范围是不是就是:0~1;因为你移动的是高柱子,但是肯下次会遇到比1高的柱子,但是也可能遇到比1低的柱子;但是总的来说我们水面的高度顶破天也就最多与1一样高,我们就按最大的高度去计算,相比于上一次的水面坑定是要小的对吧!
在这里插入图片描述
上面我们论述了不能移动高墙指针的原因,现在我们来论述一下可以移动矮墙指针的原因:
同上面一样的道理,我们移动了矮墙的指针,那么我们接下来所形成的水面高度范围也就是:0~7对吧!尽管我们宽度减小了,但是如果我们第二次的指针所指的墙面足够高的话,是可以弥补我们宽度上的损失的,甚至有可能超过,上一次水面的大小;同时也有可能照成上次,更小的水面,但是与移动高墙指针我们是一定能遇到更小的水池相比,我们肯定愿意移动矮墙的指针,因为只有移动这个我们才有希望啊!!!希望在“矮”的一方啊!!!;只要有希望,我们就不应该放弃啊!!尽管有可能得到不尽人意的结果,但是它充满着希望啊!!

代码实现:

int min(int a,int b)
{
    return a>b?b:a;
}
int maxArea(int* height, int heightSize){
         int i=0;
         int j=heightSize-1;
         int max=0;
         int s=0;
         while(i<j)//相等水面就为0,没有计算的必要
         {
            s=(j-i)*min(height[i],height[j]);
            if(s>max)
            max=s;
            if(height[i]>=height[j])//移动指针
                    j--;
                    else
                    i++;
         }
         return max;
}

在这里插入图片描述
时间复杂度:O(N)
空间复杂度:O(1)

三、剑指 Offer 51. 数组中的逆序对

在这里插入图片描述
点这里:快来征服我吧!

有效数对,该题呢主要用到归并排序的思想和分治的思想为什么这么说呢?
我们首先看一个例子:
[7,5,6,4]
我们可以分为两个区间:
[7,5],[6,4]
我们现在来计算一下前一个区间有后一个区间有多少个逆序对:
(7,6)、(7,4)、(5,4)
那么我们对两个区间进行排序过后呢?
[5,7],[4,6]
再来计算一下逆序对:
(5,4)、(7,4)、(7,6)
是不是经过排序过后还能得出与上面一样的结果;(实际上前一个区间相对于后一个区间元素顺序还是没变,原来在前一个区间的原素,与后区间所有元素相对位置没变,能够组成逆序对的数目也不会变)
还有我们发现如果5能和4组成逆序对的话,那么我们5后面的数是不是也能和4组成逆序对,有这个结论我们就可以在前一个区间找到第一个比后区间待组成逆序对的数大就行了,所有能组成的逆序对个数也就是:前区间的尾下标-当前下标+1;
当然我们[7,5,6,4]能组成的逆序对还没有算完,比如前区间[7,5]的逆序对和后区间[6,4]他们本身的逆序对,如何求?按照上面的思路,分治的思想一步一步分下去,我们先算完自己区间的逆序数,再来算本区间和后区间的逆序数;也就是先有序在计算区间之间的逆序对,我们把其分为区间只有一个元素是不是就算是有序的了(此时也不能往下分了,该区间只有一个元素,可以看作是有序的了),然后我们在计算其它与相邻区间(也是只有一个元素,也可看成有序)逆序对,同时进行归并排序;
然后返回上一层,形成一个大区间,在进行一样的步骤!!!
大致思路也就是这些了,下面我们来看看代码:

代码实现:

void merge(int* nums, int* tmp, int left, int right, int* ret)
{
    if (left == right)
        return;
    int mid = 0;
    mid = (right - left) / 2 + left;
    merge(nums, tmp, left, mid, ret);//不断分区间
    merge(nums, tmp, mid + 1, right, ret);//把区间分到只有一个元素(不能再分了)
    int L = left;
    int R = mid + 1;
    int i = 0;
    while (L <= mid && R <= right)//排序
    {
        if (nums[L] > nums[R])//前面区间的值大于后面区间的值,那么L之后的值都能与R组成逆序数
        {//同时完成一次选小操作,真是两全其美
            *ret += mid - L + 1;//记录一下逆序数个数
            tmp[i] = nums[R];
            R++;
        }
        else
        {
            tmp[i] = nums[L];
            L++;
        }
        i++;
    }
    while (R<=right)//左区间先走完
    {
        tmp[i++] = nums[R++];
    }
    while (L<=mid)//右区间先走完
    {
        tmp[i++] = nums[L++];
    }
    int k = 0;
    for (int j = left; j <= right; j++)//拷贝
    {
        nums[j] = tmp[k++];
    }
}

int reversePairs(int* data, int dataLen) {
    if(!dataLen)//数组没有元素,自然也就没有逆序对
    return 0;
    int count = 0;//记录逆序对个数
    int* tmp = (int*)malloc(sizeof(int) * dataLen);
    merge(data, tmp, 0, dataLen - 1, &count);
    free(tmp);//记得释放开辟的空间,防止造成内存泄漏
    tmp = NULL;
    return count;
}
  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 23
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南猿北者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值