Leetcode精选50题-Day03
011 盛最多水的容器
1. 题目描述
给你 n n n 个非负整数 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,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
轴共同构成的容器可以容纳最多的水。说明:你不能倾斜容器。
提示:
- 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<=3∗104
- 0 < = h e i g h t [ i ] < = 3 ∗ 1 0 4 0 <= height[i] <= 3 * 10^4 0<=height[i]<=3∗104
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
好像都是用双指针法,没啥更优的解法了似乎?(如果有,请告诉我)
3. 复杂度分析
- 我的解法:
时间复杂度:O(N)O(N),双指针总计最多遍历整个数组一次。
空间复杂度:O(1)O(1),只需要额外的常数级别的空间。
014 最长公共前缀
1. 题目描述
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串
" "
。
2. 思路&代码
2.1 直接解法
- 横向扫描
用 L C P ( S 1 … S n ) LCP(S_1 … S_n) LCP(S1…Sn) 表示字符串 S 1 … S n S_1 …S_n S1…Sn 的最长公共前缀。
可以得到以下结论:
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(S1…Sn)=LCP(LCP(LCP(S1,S2),S3),…Sn)
基于该结论,可以得到一种查找字符串数组中的最长公共前缀的简单方法。依次遍历字符串数组中的每个字符串,对于每个遍历到的字符串,更新最长公共前缀,当遍历完所有的字符串以后,即可得到字符串数组中的最长公共前缀。
如果在尚未遍历完所有的字符串时,最长公共前缀已经是空串,则最长公共前缀一定是空串,因此不需要继续遍历剩下的字符串,直接返回空串即可。
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/
- 纵向扫描
纵向扫描时,从前往后遍历所有字符串的每一列,比较相同列上的字符是否相同,如果相同则继续对下一列进行比较,如果不相同则当前列不再属于公共前缀,当前列之前的部分为最长公共前缀。
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/
- 分治
注意到 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(S1…Sn)=LCP(LCP(S1…Sk),LCP(Sk+1…Sn))
其中 L C P ( S 1 … S n ) LCP(S_1 …S_n) LCP(S1…Sn) 是字符串 S 1 … S n S_1 …S_n S1…Sn 的最长公共前缀, 1 < k < n 1<k<n 1<k<n。
基于上述结论,可以使用分治法得到字符串数组中的最长公共前缀。
对于问题 L C P ( S i ⋯ S j ) LCP(S_i ⋯S_j) LCP(Si⋯Sj),可以分解成两个子问题 L C P ( S i … S m i d ) LCP(S_i …S_mid) LCP(Si…Smid) 与 L C P ( S m i d + 1 … S j ) LCP(S_mid+1 …S_j) LCP(Smid+1…Sj),其中 m i d = i + j 2 mid= \frac{i+j}{2} mid=2i+j。
对两个子问题分别求解,然后对两个子问题的解计算最长公共前缀,即为原问题的解。
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/
- 二分查找
显然,最长公共前缀的长度不会超过字符串数组中的最短字符串的长度。
用 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,通过上述方式将查找范围缩小一半,直到得到最长公共前缀的长度。
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 奇技淫巧
- 先找出数组中字典序最小和最大的字符串,最长公共前缀即为这两个字符串的公共前缀
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
- 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)=2⋅T(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 a,b,c ,使得 a + b + c = 0 ? a + b + c = 0 ? a+b+c=0? 请你找出所有和为 0 0 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
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<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/
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)。