TLP-Task03学习笔记


本篇为Datawhale组队学习计划第21期LeetCode精选题目组Task01学习笔记。
初学,时间有点仓促,很多解法没有详细分析,未来会修改,见谅。
所有题目均参考了Datawhale学习文档,开源地址:
https://github.com/datawhalechina/team-learning-program/tree/master/LeetCodeTencent

011 盛水最多的容器

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/container-with-most-water/

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器

示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
在这里插入图片描述

思路

暴力、备忘录、双指针法(官方题解)。有兴趣的话,也可参考045题盛水容器,容器壁具有了宽度后会如何变化。
本篇的分析还参考了https://leetcode-cn.com/problems/container-with-most-water/solution/container-with-most-water-shuang-zhi-zhen-fa-yi-do/

双指针法,通过不断移动指针、寻找每次以双指针为左右边界(也就是「数组」的左右边界)计算出的容量中的最大值

首先,双指针l,r分别指向容器的左右边界。容量=两数的最小值*指针间距离,即min(y1,y2)*(x1-x2)。容量与长板无关
然后,向内移动较小的数对应的指针(边界上指针只能向内移动)。我们的目标是找到可能大于原容量的组合,由于容量与较大数无关,移动较大数的指针只会让容量更小,移动较小数的指针才有希望让短板变长。
接下来,两指针之间可视为一个新的数组,重复操作:

求出当前双指针对应的容器的容量;
对应数字较小的那个指针以后不可能作为容器的边界了,将其丢弃,并移动对应的指针。

双指针代表了什么?

双指针代表的是 可以作为容器边界的所有位置的范围。在一开始,双指针指向数组的左右边界,表示数组中所有的位置都可以作为容器的边界,因为我们还没有进行过任何尝试。在这之后,我们每次将 对应的数字较小的那个指针另一个指针 的方向移动一个位置,就表示我们认为 这个指针不可能再作为容器的边界了

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/
来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Python实现

class Solution:
    def maxArea(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        ans = 0
        while l < r:
            area = min(height[l], height[r]) * (r - l)
            ans = max(ans, area)
            if height[l] <= height[r]:
            # 左指针处为短板则l指针向右移动1格
                l += 1
            else:
                r -= 1
        return ans


#作者:LeetCode-Solution
#链接:https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

014 最长公共前缀

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-common-prefix/

编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。

示例 :

输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”

输入:strs = [“dog”,“racecar”,“car”]
输出:""
解释:输入不存在公共前缀。

提示:

0 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成

思路

官解介绍了四种方法
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/zui-chang-gong-gong-qian-zhui-by-leetcode-solution/

1.横向扫描
依次遍历字符串数组中的每个字符串,对于每个遍历到的字符串,更新最长公共前缀,当遍历完所有的字符串以后,即可得到字符串数组中的最长公共前缀。

2.纵向扫描
从前往后遍历所有字符串的每一列,比较相同列上的字符是否相同,如果相同则继续对下一列进行比较,如果不相同则当前列不再属于公共前缀,当前列之前的部分为最长公共前

3.分治
(没太懂,这里暂时不谈)

4.二分查找
显然,最长公共前缀的长度不会超过字符串数组中的最短字符串的长度。用minLength表示字符串数组中的最短字符串的长度,则可以在 [0,minLength] 的范围内通过二分查找得到最长公共前缀的长度。每次取查找范围的中间值 mid,判断每个字符串的长度为 mid的前缀是否相同,如果相同则最长公共前缀的长度一定大于或等于mid,如果不相同则最长公共前缀的长度一定小于mid,通过上述方式将查找范围缩小一半,直到得到最长公共前缀的长度。

Python实现

水平原因选择了比较容易的纵向扫描法

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if not strs:
            return ""  #判断空串
        
        length, count = len(strs[0]), len(strs)
        for i in range(length):
            c = strs[0][i]
            if any(i == len(strs[j]) or strs[j][i] != c for j in range(1, count)):
            # 中止条件:最短串检测完毕,或出现不同字符
                return strs[0][:i]
        
        return strs[0]

#作者:LeetCode-Solution
#链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/zui-chang-gong-gong-qian-zhui-by-leetcode-solution/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

015 三数之和

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例 :

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

输入:nums = []
输出:[]

输入:nums = [0]
输出:[]

提示:

0 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5

思路

本题的难点是“不重复”,官解中给出“排序+双指针”的思路。

「不重复」的本质是什么?我们保持三重循环的大框架不变,只需要保证:
第二重循环枚举到的元素不小于当前第一重循环枚举到的元素;
第三重循环枚举到的元素不小于当前第二重循环枚举到的元素。
也就是说,我们枚举的三元组(a,b,c)满足 a≤b≤c,保证了只有 (a,b,c)这个顺序会被枚举到,要实现这一点,我们可以将数组中的元素从小到大进行排序,随后使用普通的三重循环就可以满足上面的要求。

同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。举个例子,如果排完序的数组为
[0, 1, 2, 2, 2, 3]
^ ^ ^
我们使用三重循环枚举到的第一个三元组为(0,1,2),如果第三重循环继续枚举下一个元素,那么仍然是三元组(0,1,2),产生了重复。因此我们需要将第三重循环「跳到」下一个不相同的元素,即数组中的最后一个元素3,枚举三元组(0,1,3)

三重循环的优化:

如果我们固定了前两重循环枚举到的元素a和b,那么只有唯一的c满足a+b+c=0。当第二重循环往后枚举一个元素 b′时,由于b′>b,那么满足 a+b′+c′=0的c’一定有c′<c,即c’在数组中一定出现在c的左侧。也就是说,我们可以从小到大枚举b,同时从大到小枚举c,即第二重循环和第三重循环实际上是并列的关系。

有了这样的发现,我们就可以保持第二重循环不变,而将第三重循环变成一个从数组最右端开始向左移动的指针。

这个方法就是我们常说的「双指针」,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2)减少至 O(N)。

思路和代码均来自官方解法(尤其强烈推荐再看一下原理部分的讲解),原代码注释非常清晰,直接放在这里。

Python实现

代码来自官方题解

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        nums.sort()
        ans = list()
        
        # 枚举 a
        for first in range(n):
            # 需要和上一次枚举的数不相同
            if first > 0 and nums[first] == nums[first - 1]:
                continue
            # c 对应的指针初始指向数组的最右端
            third = n - 1
            target = -nums[first]
            # 枚举 b
            for second in range(first + 1, n):
                # 需要和上一次枚举的数不相同
                if second > first + 1 and nums[second] == nums[second - 1]:
                    continue
                # 需要保证 b 的指针在 c 的指针的左侧
                while second < third and nums[second] + nums[third] > target:
                    third -= 1
                # 如果指针重合,随着 b 后续的增加
                # 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if second == third:
                    break
                if nums[second] + nums[third] == target:
                    ans.append([nums[first], nums[second], nums[third]])
        
        return ans


# 作者:LeetCode-Solution
# 链接:https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/cosima0/article/details/112590732