Leetcode精选50题-Day03

Leetcode精选50题-Day03

  • 011 盛最多水的容器
      • 1. [题目描述](https://leetcode-cn.com/problems/container-with-most-water/)
      • 2. 思路&代码
        • 2.1 我的解法
      • 3. 复杂度分析
  • 014 最长公共前缀
      • 1. [题目描述](https://leetcode-cn.com/problems/longest-common-prefix/)
      • 2. 思路&代码
        • 2.1 直接解法
        • 2.2 奇技淫巧
      • 3. 复杂度分析
  • 015 三数之和
      • 1. [题目描述](https://leetcode-cn.com/problems/3sum/)
      • 2. 思路&代码
        • 2.1 排序 + 双指针
      • 3. 复杂度分析

011 盛最多水的容器

1. 题目描述

给你 n n n 个非负整数 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1a2...an,每个数代表坐标中的一个点 ( i , a i ) (i, a_i) (i,ai) 。在坐标内画 n n n
条垂直线,垂直线 i i i 的两个端点分别为 ( i , a i ) (i, a_i) (i,ai) ( i , 0 ) (i, 0) (i,0) 。找出其中的两条线,使得它们与 x x x
轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。
Leetcode精选50题-Day03_第1张图片
提示:

  • n = h e i g h t . l e n g t h n = height.length n=height.length
  • 2 < = n < = 3 ∗ 1 0 4 2 <= n <= 3 * 10^4 2<=n<=3104
  • 0 < = h e i g h t [ i ] < = 3 ∗ 1 0 4 0 <= height[i] <= 3 * 10^4 0<=height[i]<=3104

2. 思路&代码

2.1 我的解法

采用双指针法从两端向中间移动,left为左边指针,从左向右移动,right为右边指针,从右向左移动。
选择移动后可以得到相对更长边长或边长有增长一端优先移动(好啰嗦,看了题解发现可以归纳为一句话,,,移动较短边),并记录过程中获得的最大容量

class Solution:
    def maxArea(self, height: List[int]) -> int:
        left = 0
        right = len(height)-1
        area_max = 0

        while left != right:
            area = min(height[left], height[right]) * (right- left)
            area_max = max(area, area_max)
            if height[left] < height[right]:
                left += 1
            else:
                right -= 1
        return area_max
        

Leetcode精选50题-Day03_第2张图片

好像都是用双指针法,没啥更优的解法了似乎?(如果有,请告诉我)

3. 复杂度分析

  • 我的解法
    时间复杂度:O(N)O(N),双指针总计最多遍历整个数组一次。
    空间复杂度:O(1)O(1),只需要额外的常数级别的空间。

014 最长公共前缀

1. 题目描述

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 " "
Leetcode精选50题-Day03_第3张图片

2. 思路&代码

2.1 直接解法

  1. 横向扫描
    L C P ( S 1 … S n ) LCP(S_1 … S_n) LCP(S1Sn) 表示字符串 S 1 … S n S_1 …S_n S1Sn 的最长公共前缀。
    可以得到以下结论:
    L C P ( S 1 … S n ) = L C P ( L C P ( L C P ( S 1 , S 2 ) , S 3 ) , … S n ) LCP(S_1 …S_n)=LCP(LCP(LCP(S_1, S_2), S_3), … S_n) LCP(S1Sn)=LCP(LCP(LCP(S1,S2),S3),Sn)
    基于该结论,可以得到一种查找字符串数组中的最长公共前缀的简单方法。依次遍历字符串数组中的每个字符串,对于每个遍历到的字符串,更新最长公共前缀,当遍历完所有的字符串以后,即可得到字符串数组中的最长公共前缀。
    如果在尚未遍历完所有的字符串时,最长公共前缀已经是空串,则最长公共前缀一定是空串,因此不需要继续遍历剩下的字符串,直接返回空串即可。
    Leetcode精选50题-Day03_第4张图片
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if not strs:
            return ""
        
        prefix, count = strs[0], len(strs)
        for i in range(1, count):
            prefix = self.lcp(prefix, strs[i])
            if not prefix:
                break
        
        return prefix

    def lcp(self, str1, str2):
        length, index = min(len(str1), len(str2)), 0
        while index < length and str1[index] == str2[index]:
            index += 1
        return str1[:index]

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/zui-chang-gong-gong-qian-zhui-by-leetcode-solution/
  1. 纵向扫描
    纵向扫描时,从前往后遍历所有字符串的每一列,比较相同列上的字符是否相同,如果相同则继续对下一列进行比较,如果不相同则当前列不再属于公共前缀,当前列之前的部分为最长公共前缀。

Leetcode精选50题-Day03_第5张图片

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/
  1. 分治
    注意到 L C P LCP LCP 的计算满足结合律,有以下结论:
    L C P ( S 1 … S n ) = L C P ( L C P ( S 1 … S k ) , L C P ( S k + 1 … S n ) ) LCP(S_1 …S_n)=LCP(LCP(S_1 …S_k),LCP(S_k+1 …S_n)) LCP(S1Sn)=LCP(LCP(S1Sk),LCP(Sk+1Sn))
    其中 L C P ( S 1 … S n ) LCP(S_1 …S_n) LCP(S1Sn) 是字符串 S 1 … S n S_1 …S_n S1Sn 的最长公共前缀, 1 < k < n 11<k<n
    基于上述结论,可以使用分治法得到字符串数组中的最长公共前缀。
    对于问题 L C P ( S i ⋯ S j ) LCP(S_i ⋯S_j) LCP(SiSj),可以分解成两个子问题 L C P ( S i … S m i d ) LCP(S_i …S_mid) LCP(SiSmid) L C P ( S m i d + 1 … S j ) LCP(S_mid+1 …S_j) LCP(Smid+1Sj),其中 m i d = i + j 2 mid= \frac{i+j}{2} mid=2i+j
    对两个子问题分别求解,然后对两个子问题的解计算最长公共前缀,即为原问题的解。
    Leetcode精选50题-Day03_第6张图片
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        def lcp(start, end):
            if start == end:
                return strs[start]

            mid = (start + end) // 2
            lcpLeft, lcpRight = lcp(start, mid), lcp(mid + 1, end)
            minLength = min(len(lcpLeft), len(lcpRight))
            for i in range(minLength):
                if lcpLeft[i] != lcpRight[i]:
                    return lcpLeft[:i]

            return lcpLeft[:minLength]

        return "" if not strs else lcp(0, len(strs) - 1)

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/zui-chang-gong-gong-qian-zhui-by-leetcode-solution/

  1. 二分查找
    显然,最长公共前缀的长度不会超过字符串数组中的最短字符串的长度。
    m i n L e n g t h minLength minLength 表示字符串数组中的最短字符串的长度,则可以在 [ 0 , m i n L e n g t h ] [0,minLength] [0,minLength] 的范围内通过二分查找得到最长公共前缀的长度。
    每次取查找范围的中间值 m i d mid mid,判断每个字符串的长度为 m i d mid mid 的前缀是否相同,如果相同则最长公共前缀的长度一定大于或等于 m i d mid mid,如果不相同则最长公共前缀的长度一定小于 m i d mid mid,通过上述方式将查找范围缩小一半,直到得到最长公共前缀的长度。
    Leetcode精选50题-Day03_第7张图片
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        def isCommonPrefix(length):
            str0, count = strs[0][:length], len(strs)
            return all(strs[i][:length] == str0 for i in range(1, count))

        if not strs:
            return ""

        minLength = min(len(s) for s in strs)
        low, high = 0, minLength
        while low < high:
            mid = (high - low + 1) // 2 + low
            if isCommonPrefix(mid):
                low = mid
            else:
                high = mid - 1

        return strs[0][:low]

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/zui-chang-gong-gong-qian-zhui-by-leetcode-solution/

2.2 奇技淫巧

  1. 先找出数组中字典序最小和最大的字符串,最长公共前缀即为这两个字符串的公共前缀
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        
        if not strs: 
            return ""
            
        str0 = min(strs)
        str1 = max(strs)
        for i in range(len(str0)):
            if str0[i] != str1[i]:
                return str0[:i]
        return str0
  1. zip合并
    来自:javaniuniu
    思路:
    使用 zip 根据字符串下标合并成数组,
    判断合并后数组里元素是否都相同
class Solution(object):
    def longestCommonPrefix(self, strs):
        ans = ''
        for i in zip(*strs):
            if len(set(i)) == 1:
                ans += i[0]
            else:
                break
        return ans

作者:javaniuniu
链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/shi-yong-zip-ji-xing-dai-ma-jian-dan-gao-ding-pyth/

3. 复杂度分析

横向扫描

  • 时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 是字符串数组中的字符串的平均长度, n n n 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
  • 空间复杂度: O ( 1 ) O(1) O(1)。使用的额外空间复杂度为常数。

纵向扫描

  • 时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 是字符串数组中的字符串的平均长度, n n n 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
  • 空间复杂度: O ( 1 ) O(1) O(1)。使用的额外空间复杂度为常数。

分治

  • 时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 是字符串数组中的字符串的平均长度, n n n 是字符串的数量。时间-复杂度的递推式是 T ( n ) = 2 ⋅ T ( 2 n ​ ) + O ( m ) T(n)=2⋅T( 2n​ )+O(m) T(n)=2T(2n)+O(m),通过计算可得 T ( n ) = O ( m n ) T(n)=O(mn) T(n)=O(mn)

  • 空间复杂度: O ( m   l o g n ) O(m\ logn) O(m logn),其中 m m m 是字符串数组中的字符串的平均长度, n n n 是字符串的数量。空间复杂度主要取决于递归调用的层数,层数最大为 l o g n logn logn,每层需要 m m m 的空间存储返回结果。

二分查找

  • 时间复杂度: O ( m n   l o g m ) O(mn\ logm) O(mn logm),其中 m m m 是字符串数组中的字符串的最小长度, n n n 是字符串的数量。二分查找的迭代执行次数是 O ( l o g   m ) O(log\ m) O(log m),每次迭代最多需要比较 m n mn mn 个字符,因此总时间复杂度是 O ( m n   l o g m ) O(mn\ log m) O(mn logm)
  • 空间复杂度: O ( 1 ) O(1) O(1)。使用的额外空间复杂度为常数。

015 三数之和

1. 题目描述

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

注意:答案中不可以包含重复的三元组。

Leetcode精选50题-Day03_第8张图片

2. 思路&代码

2.1 排序 + 双指针

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
                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/

3. 复杂度分析

时间复杂度: O ( N 2 ) O(N^2) O(N2),其中 N N N 是数组 n u m s nums nums 的长度。

空间复杂度: O ( l o g   N ) O(log\ N) O(log N)。我们忽略存储答案的空间,额外的排序的空间复杂度为 O ( l o g   N ) O(log\ N) O(log N)。然而我们修改了输入的数组 n u m s nums nums,在实际情况下不一定允许,因此也可以看成使用了一个额外的数组存储了 n u m s nums nums 的副本并进行排序,空间复杂度为 O ( N ) O(N) O(N)

你可能感兴趣的:(leetcode)