LeetCode(力扣)数组题库题解(中等组)(题号11~16)(2023.12.25有更新)(待续)

前言:

        本文收录下以下专栏:

力扣Leetcode数组类中等组(python版本)(专栏)icon-default.png?t=N7T8http://t.csdnimg.cn/PVDQO

        结合目录快速定位!!!

        标题下面有免费配套讲解资源可下载使用!

        本篇LeetCode中等组的题解,会持续更新!

        许多代码其实考察的是一个数学建模的问题,抽象成一个数学问题就能很好解决了!!

        每一部分尽量留一个问题给读者自行解决!

11.盛最多水的容器

题目:5f507584bde1466c9a9e0b65d21c95cd.png

6be8bf9d89a1449299cf2d0efb9c409f.png

  题解思路:

         先根据题目进行分析,就是一个求体积最大的问题,抽象成二维平面实际上就是一个求最大面积的问题:

        首先假设有两个指针 i,j 从数组的两端开始,他们所指向的长条高度分别为:

         height[i] ,height[j]

        根据木板效应,题中能装多少水是取决于我们最短的木板,所以我们要求的面积为:

         eq?S%20%3D%20min%28height%5Bi%5D%2Cheight%5Bj%5D%29%20*%20%28j%20-i%29

        上述式子为什么是min(height[i],height[j]呢?

        接下来我们要想去求最大值,让 i ,j 把每一个位置试遍显然会超时,我们要采取其他办法:

        我们如果只挪动高的木板,由于木板效应,存放的水只有变少有可能不变,绝不可能变多!:

        分三种情况讨论:

        第一种,高的木板里面的木板高度一样,由于长度(横坐标变化)显然水变少;

094d138a03a344218d719f480a8ac16f.png

        第二种,高的木板里面的木板高度变得更高,我们能装多少水是由短板决定,存放水变少;

d67885f1d76d4b8f9e95617cdd1204d3.png

        第三种高木板里面是短木板(比他自己短),这种情况变少:

a8bcd697c27e402ea5212cd1e950dee8.png

        如果我们选择短板移动,那么效果就完全不一样了:

        第一种情况短板内高度变高,这时候存水可能变少也有可能变多:

 202dad8cdba94818a80444f7cbcb9ef0.png

        第二种情况短板高度不变,这种情况肯定变少!

61569473eef44b43a0bfa1ada2046b0f.png

        第三种情况短板内高度更低:这种情况也是变少:

97aec5c131a14d7f80860b88cf25f983.png

         综上所述:要是存水面积变大我们只能采取移动短板的方法,换言之我们要找到存水最大的方法,就是把 i,j 两个指针从头开始向内移动,每次移动较短的那一个,直到 i,j 相遇,期间肯定能找对最大值。

本题完整代码:

class Solution:
    def maxArea(self, height: List[int]) -> int:
        # 初始化两个指针 i=0, j=len(height)-1
        i, j = 0, len(height) - 1
        maxArea = 0
        while i != j:

            if height[i] < height[j]:
                min_height = height[i]
                # print('i = {}, j = {}'.format(i, j))
                i += 1
            else:
                min_height = height[j]
                # print('i = {}, j = {}'.format(i, j))
                j -= 1
            # 这里长度变成 j - i + 1 是因为前面会多减一个1,造成结果有问题!!!
            maxArea = max(maxArea, (j - i + 1) * min_height)
            # print('maxArea = {},min_height = {}'.format(maxArea, min_height), )
        return maxArea

可能遇到的问题:

  1. 无脑暴力循环,中等组一般会超时
  2. 移动了高指针,找错了最大值
  3. 最后计算 maxArea 没注意前面指针减 1 了,可能会有问题
  4. 数学抽象能力较弱

改进的空间:

        中间的 min_height 可以尝试不用变量存起来,可以节省一定空间(留给读者)

15.三数之和:

题目:

b1848da6dad24a4ea22fbccb28c4170e.png

题解思路:

        首先暴力解法,三个for循环肯定能解,不过肯定会超时,排除。

        想要排除重复的情况,可以利用多指针来剔除重复的解。(这在降低时间复杂度中很常用)

        然后就是对数组进行排序(直接库函数就行),一个无序的数组,我们很难有什么好的办法,这一步也成为数据的预处理,数据在一开头进行预处理,一般能为我们节省大量的时间。

        现在我们得到一个排好序的数组了, 然后仿照三层 for 的暴力解法,设三个指针 k ,i , j ,k指针用 for 循环来移动, i,j指针在while循环中移动。

        k指针从头开始,i从k+1开始,j从数组末尾开始,其中,k成为固定指针,在一个for循环中位置不变,i,j在一个for循环中可变:

                 c41c49f5e62a47abbc0ba33e01118f9d.png

         我们在一个for循环中,如果nums[k]+nums[i]+nums[j]的值小于0如上图,那么移动左指针i,在nums[i]与nums[i+1]值不同的情况下,可以使得我们的三数之和可以变大:

b74d788a129647fb9d2e62918dee01fe.png

        如果  nums[k]+nums[i]+nums[j]  的值大于0 ,在nums[j]与nums[j-1] 的值不同的情况下,可以使我们的sum三数之和值变小:

8f52f465827842b2af1c3733c1cc41ef.png

 47e3ef0b4803449fac3935073b5ca14a.png

        如果  nums[k]+nums[i]+nums[j] 的值加起来正好使0 ,那就是我们的答案:

713a038ce73c42b69fbf0d495b8e0de1.png

        对于题解我们还不能直接存入到我们的结果数组之前,还需要检查是否重复! (这里体现了我们对数组预排序的好处。

        当我们的i、j相遇的时候,这个for循环就能结束了。

        然后对k用一个for循环使他能走便每一个位置,这样不会漏解。

        还有一个减少时间消耗的办法:

        当我们nums[k]的值大于0时可以直接退出,后面的三者之和一定大于0,没有题解。

        这样一来有暴力解法的时间复杂度O(N^3)变成了O(N^2)降低了复杂度。

本题完整代码:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # 对数组排序
        nums.sort()
        new_nums = []
        # 特例
        if len(nums) < 3 or nums is None:
            return []
        for k in range(len(nums) - 1):
            # 结束条件
            if nums[k] > 0:
                break
            i, j = k + 1, len(nums) - 1
            while True:
                if i == j:
                    break
                sum_nums = nums[k] + nums[i] + nums[j]
                if sum_nums == 0:
                    temp_nums = [nums[k], nums[i], nums[j]]  # 注意顺序,用来判断是否重复的解
                    # print("k,i,j,temp_nums:",k,i,j,temp_nums)
                    if temp_nums not in new_nums:
                        new_nums.append(temp_nums)
                    i += 1
                # 移动指针
                elif sum_nums > 0:
                    j -= 1
                elif sum_nums < 0:
                    i += 1
        return list(new_nums)

可能遇到的问题:

  1. 我们有三个指针,但题目给的数组元素小于三或者是一个空数组要特判
  2. for循环里面有一个while循环,while一定要注意有退出条件!
  3. 重复的解要剔除!

改进的空间: 

        我们这里剔除重复元素的办法是用库函数(in)来检测,我们可以自己在函数中设计指针移动,使其解肯定不重复,这样可以再进一步减少时间复杂度。(留给读者)

16.最接近的三数之和:

题目:3e278992a37d449698852943fc393297.png

题解思路:

        这题看上去就和15题差不多,无非就是一个是能等于target,一个可能等于target可能不等于,要找一个最接近的值,我们在上题的基础上,只要增加一个临时变量temp来记录目前最接近的值,每次比较都要进行更新值的操作即可。三指针按照上题所述,正常进行移位即可。

        我们这里还能略微优化一下,如果temp为0说明题目给的数组正好有三个数之和等于target,这时候直接返回答案即可。

本题完整代码:

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        nums.sort()
        temp = inf
        for k in range(len(nums)):
            i, j = k + 1, len(nums) - 1
            while i < j:
                s = nums[k] + nums[i] + nums[j]
                if s == target:
                    return s
                # 更新值
                if abs(s - target) <= abs(temp - target):
                    # print('s,k,i,j', s, nums[k], nums[i], nums[j])
                    temp = s
                # 移动指针
                if s <= target:
                    i += 1
                elif s > target:
                    j -= 1

        return temp

可能遇到的问题:

  1. 更新值的时候要明确知道比较的是谁离 target 比较近,要带绝对值
  2. 移动指针的时候要注意,我们是根据我们的三数之和与 target 大小比较来进行移动指针
  3. 移动指针的时候要考虑 = 的条件,不然会有死循环
  4. temp初始化要为最大值,不然后面比较会出问题

改进的空间:

        中途还有部分小细节可以减少一点重复计算的步骤

        例如和15题一样,如果nums[k]的值都比target大,那么直接退出循环,因为后面的结果会渐行渐远(留给读者)

18.四数之和

题目:2db92582938f454180c91e149c78154e.png

题解思路:

        这题和上面三数排序之和差不多,首先对原数组排序,方便后期去判断重复的解。

        然后开设四个指针 m , n ,i , j指向首尾四个位置,形如下图:

294b55e2006246c0b29b100d1c54cf66.png

        按照上面的逻辑,m , n由for循环来改变他们的值,i ,j 称为移动指针,根据四数之和与 target 大小关系,如果四数之和 s 比 target 小那么移动 i 指针,大于,移动 j 指针, 相等,保存结果并且判断是否重复。

本题完整代码:

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        # 预处理、排序
        nums.sort()
        new_nums = []
        for m in range(len(nums)):
            for k in range(len(nums)):
                # n 从末尾开始
                n = len(nums) - k - 1
                i, j = m + 1, n - 1
                while i < j:
                    s = nums[m] + nums[i] + nums[j] + nums[n]  # 注意顺序,用来判断是否重复
                    if s < target:
                        i += 1
                    elif s > target:
                        j -= 1
                    elif s == target:
                        temp_nums = [nums[m], nums[i], nums[j], nums[n]]  # 注意顺序
                        if temp_nums not in new_nums:
                            new_nums.append(temp_nums)
                        j -= 1

        return new_nums

可能遇到的问题:

  1. 注意 n 要从末尾开始,使得四个指针逐渐向中间靠拢
  2. 判断循环终止条件的时候,容易把位置处于数组中间的解给漏了(常错)
  3. 最好进行特例特判,把长度小于 4 的数组特判空解

改进的空间:

        本题没做任何剪枝处理,我们仿照前面的题目,如果有 nums[m]大于 target 那么直接退出,后面一定全部值都比 target 大,以此类推进行适量剪枝操作。(留给读者)

未完待续:

        

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

钅日 勿 XiName

给作者打赏一点小零食吧~~

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

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

打赏作者

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

抵扣说明:

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

余额充值