Leetcode-数组
1. 盛最多水的容器(11M)
难度:【中等】
思路:
- 从两端往中间走,谁小谁动,一样的情况下谁动都行;
- 记录每一步 min(X, Y) * distance;
- 在头尾相遇时结束,也就是 O(n) 时间复杂度与 O(1) 空间复杂度。
注意:两端往中间走,移动高度小的数,每走一步计算一下。
代码:
class Solution:
def maxArea(self, height: List[int]) -> int:
l,r = 0,len(height)-1
curMax = 0
while l != r:
curMax = max(curMax, min(height[l],height[r]) * (r - l))
if height[l] < height[r]:
l += 1
else:
r -= 1
return curMax
三数之和
难度:【中等】
思路:排序 + 双指针
本题的难点在于如何去除重复解。
1、判断:如果len(nums) < 3 ,直接返回空
2、使用sort( )方法进行排序
3、遍历排序后的数组
- 若nums[i] > 0,后面不可能有三个数加和等于0,直接返回结果即可。
- 对于重复元素,跳过,避免出现重复解。
- 令左指针 left = i + 1,右指针 right = n - 1,当left < right,执行循环,三种情况:1、当满足三数之和为0时,需要判断左界和右界是否和下一位重复,进行去重,并更新左右指针;2、如果和大于0,右指针左移;3、如果小于0,左指针右移。
代码如下:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
res = []
if n < 3:
return []
nums.sort()
for i in range(n):
if nums[i] > 0:
return res
if i > 0 and nums[i] == nums[i -1]:
continue
left = i + 1
right = n -1
while left < right:
if nums[i] + nums[left] + nums[right] == 0:
res.append([nums[i],nums[left],nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif nums[i] + nums[left] + nums[right] < 0:
left += 1
else:
right -= 1
return res
时间复杂度:O(n^2)
空间复杂度:O(1)
搜索旋转排序数组(33E)
难度:【中等】
思路一:暴力解法
直接遍历整个数组,找到目标值target
代码如下:
class Solution:
def search(self, nums: List[int], target: int) -> int:
for i,num in enumerate(nums):
if num == target:
return i
return -1
时间复杂度:O(n)
空间复杂度:O(1)
思路二:二分查找
先要设置整个数组的左右两端端点:left = 0,right = len(nums) - 1
1、若 target == nums[mid],直接返回
2、若 nums[left] <= nums[mid],说明左侧区间 [left,mid]「连续递增」。此时:
若 nums[left] <= target <= nums[mid],说明 target 位于左侧。令 right = mid-1,在左侧区间查找
否则,令 left = mid+1,在右侧区间查找
3、否则,说明右侧区间 [mid,right]「连续递增」。
此时:
若 nums[mid] <= target <= nums[right],说明 target 位于右侧区间。令 left = mid+1,在右侧区间查找
否则,令 right = mid-1,在左侧区间查找
代码如下:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) -1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
时间复杂度:O(logn)
空间复杂度:O(1)
在排序数组中查找元素的第一个和最后一个位置(34M)
难度:【中等】
思路:
- 先判断nums是否存在,不存在直接返回[-1,-1]
- 用二分查找到nums中和target相等的值
- 查找第一个元素和最后一个元素
代码:
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if not nums:
return [-1,-1]
n = len(nums)
# 查找第一个和最后一个元素
def find(is_find_first):
begin = 0
end = n-1
# if和elif的逻辑跟正常的二分查找一样
while begin<=end:
mid = begin+(end-begin)//2
if nums[mid]>target:
end = mid-1
elif nums[mid]<target:
begin = mid+1
# 找到目标值了,开始定位到第一个和最后一个位置
else:
# 查找第一个和最后一个逻辑很类似,这里用一个变量标记
# 是查找第一个还是查找最后一个
if is_find_first:
# 如果不满足条件,缩小右边界,继续往左边查找
if mid>0 and nums[mid]==nums[mid-1]:
end = mid-1
else:
return mid
else:
# 如果不满足条件,增大左边界,继续往右边查找
if mid<n-1 and nums[mid]==nums[mid+1]:
begin = mid+1
else:
return mid
return -1
return [find(True), find(False)]
后来自己又写了一种更简单的写法
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
p = q = mid
while p >= 0 and nums[p] == target:
p -= 1
while q < n and nums[q] == target:
q += 1
return [p + 1, q - 1]
elif nums[mid] > target:
right = mid - 1
else:
left = mid + 1
return [-1,-1]
接雨水(42H)
难度:【困难】
四种方法,暴力法,动态规划,单调栈,双指针。
方法一:暴力法
对于数组height中的每个元素,分别向左和向右扫描并记录左边和右边的最大高度,然后计算每个下标位置能接的雨水量。
代码如下:
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
leftMax, rightMax, res = 0,0,0
for i in range(n-1):
a = b = i
while a >= 0:
leftMax = max(leftMax,height[a])
a -= 1
while b <= n-1:
rightMax = max(rightMax,height[b])
b += 1
res += min(leftMax, rightMax) - height[i]
return res
时间复杂度为O(n^2)
在leetcode上运行会超时。
方法二:动态规划
创建两个长度为 n 的数组 leftMax 和 rightMax;
先从左往右遍历一遍,记录每个位置左边高度的最大值,存入leftMax;
从右往左遍历一遍,记录每个位置右边高度的最大值,存入 rightMax;
最后再从左往右遍历,计算每个下标位置得到的雨水总量。
代码如下:
class Solution:
def trap(self, height: List[int]) -> int:
if not height:
return 0
n = len(height)
res = 0
leftMax = [height[0]] + [0] * (n-1)
rightMax = [0] * (n-1) + [height[n-1]]
for i in range(1,n):
leftMax[i] = max(leftMax[i-1],height[i])
for j in range(n-2,-1,-1):
rightMax[j] = max(rightMax[j+1],height[j])
for x in range(n):
res += min(leftMax[x],rightMax[x]) - height[x]
return res
时间复杂度:O(n)
空间复杂度:O(n)
方法二:单调栈
维护一个单调栈,存储下标,满足从栈底到栈顶的下标对应数组height中的元素单调递减;
遍历height中的元素,如果栈不为空且当前元素值大于栈顶元素,取出栈顶元素得到最小的高度值low,此时再比较栈顶元素与当前元素值的大小,取小后减去low得到高度差;再计算得到宽度,最后得到雨水量。
代码如下:
class Solution:
def trap(self, height: List[int]) -> int:
s = []
res = 0
for i,h in enumerate(height):
while s and height[s[-1]] < h:
low = height[s.pop()]
if not s:
break
width = i - s[-1] - 1
heighth = min(height[s[-1]],h) - low
res += width * heighth
s.append(i)
return res
时间复杂度:O(n)
空间复杂度:O(n)
全排列(46M)
难度:【中等】
思路:回溯算法
使用回溯算法框架即可,先考虑一位的情况,然后把递归地考虑去掉这一位数后的数组的全排列。
模板如下:
核心思想就是for循环,在递归调用之前做选择,在递归调用之后撤销选择。
代码如下:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(tmp,nums):
if len(tmp) == len(nums):
res.append(tmp[:])
for num in nums:
if num not in tmp:
tmp.append(num)
backtrack(tmp,nums)
tmp.pop()
backtrack([],nums)
return res
时间复杂度:O(n×n!)
空间复杂度:O(n)
最大子序和(53E)
难度:【简单】
思路:动态规划
写出状态转移方程,来描述子问题之间的关系.
简化后为
代码如下:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [0 for i in range(len(nums))]
dp[0] = nums[0]
for i in range(1,len(nums)):
if dp[i - 1] > 0:
dp[i] = dp[i - 1] + nums[i]
else:
dp[i] = nums[i]
return max(dp)
时间复杂度:O(n)
空间复杂度:O(n)
这里再提供一种优化空间的代码,方法依然是动态规划。
用两个变量,一个变量pre用来记录当前最大值,一个变量res记录历史最大值。
代码如下:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
pre = 0
res = nums[0]
for i in range(n):
pre = max(pre + nums[i], nums[i])
res = max(res,pre)
return res
时间复杂度:O(n)
空间复杂度:O(1)
跳跃游戏(55M)
难度:【中等】
思路:贪心算法
依次遍历数组中的每一个位置,并实时维护最远可以到达的位置 rightMost,注意当前遍历到的位置 i 要在最远可以到达的位置范围内(也就是满足 i <= rightMost),当rightMost 大于或等于 数组最后一个位置时,就表示可以到达,返回 True,否则就要返回 False。
代码如下:
class Solution:
def canJump(self, nums: List[int]) -> bool:
n = len(nums)
rightMost = 0
for i in range(n):
if i <= rightMost:
rightMost = max(rightMost, i + nums[i])
if rightMost >= n-1:
return True
return False
时间复杂度:O(n),其中 n 为数组的大小。只需要访问 nums 数组一遍,共 n 个位置。
空间复杂度:O(1),不需要额外的空间开销。
最小路径和(64M)
难度:【中等】
思路:动态规划
由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。
对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。
创建二维数组dp,与原始网格的大小相同,dp[i][j] 表示从左上角出发到 (i,j)位置的最小路径和。显然,dp[0][0]=grid[0][0]。对于dp 中的其余元素,通过以下状态转移方程计算元素值。
代码如下:
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m = len(grid)
n = len(grid[0])
dp = [[0] * n for _ in range(m)]
dp[0][0] = grid[0][0]
for i in range(1, m):
dp[i][0] = dp[i-1][0] + grid[i][0]
for j in range(1, n):
dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m):
for j in range(1,n):
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
return dp[-1][-1]
时间复杂度:O(mn)
空间复杂度:O(mn)
颜色分类 (75M)
难度:【中等】
本题主要思路:快排
思路一:单指针
对数组进行两次遍历,考虑使用单指针 ptr 进行遍历,第一次遍历中需要把所有的 0 交换到数组的头部,每交换一次,ptr 向右移动一位,直到遍历结束,此时 ptr 之前的元素都为 0;第二次遍历从 ptr 开始遍历,将所有的 1 交换到中间位置,每交换一次,ptr 向后移动一位,直到遍历结束,此时 ptr 之后(包括ptr)的元素都为2,排序完成。
代码:
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
ptr = 0
for i in range(len(nums)):
if nums[i] == 0:
nums[i],nums[ptr] = nums[ptr],nums[i]
ptr +=1
for i in range(ptr,len(nums)):
if nums[i] == 1:
nums[i],nums[ptr] = nums[ptr],nums[i]
ptr +=1
return nums
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
思路二:双指针
相比单指针只需要一次遍历即可完成。需要指针 p0 来交换等于 0 的元素,指针 p1 来交换等于 1 的元素,需要特别注意的如下:
先判断元素是否等于 1,满足等于1 就进行交换,并将 p1 + 1,再判断是否等于0 ,如果等于 0 也相应进行交换,另外需要判断 p0 和 p1 的关系,如果满足 p0 < p1,还需要再次进行交换,完成后将 p0 和 p1 同时 +1。
代码如下:
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
p0 = p1 = 0
for i in range(len(nums)):
if nums[i] == 1:
nums[i],nums[p1] = nums[p1],nums[i]
p1 +=1
elif nums[i] == 0:
nums[i],nums[p0] = nums[p0],nums[i]
if p0 < p1:
nums[i],nums[p1] = nums[p1],nums[i]
p0 += 1
p1 += 1
return nums
时间复杂度:O(n),其中 nn 是数组 nums 的长度。
空间复杂度:O(1)。
合并两个有序数组
88. 合并两个有序数组
难度:【简单】
三种思路:
方法一:将数组nums2放到nums1的尾部,然后直接用sort方法对整个数组进行排序。
代码如下:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
nums1[m:] = nums2
nums1.sort()
时间复杂度:O((m + n)log(m + n))
空间复杂度:O(log(m+n))
方法二:双指针
新建一个数组res,定义两个指针来标记两个数组的位置,将nums1和nums2的数进行比较,如果nums1中的数小,就将该数添加到res中,并将对应的指针p1向前移动一位,需要考虑的一种情况就是当nums1或nums2中的指针到最后了,可以将指针没到最后的数组直接添加到res中,特别要注意最后面 nums[:] = res。将res的值赋给nums
代码如下:
这里提供两种代码写法,有微小区别,仅供参考。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
res = []
p1,p2 = 0,0
while p1 < m and p2 < n:
if nums1[p1] < nums2[p2]:
res.append(nums1[p1])
p1 += 1
else:
res.append(nums2[p2])
p2 +=1
if p1 == m:
res.extend(nums2[p2:])
else:
res.extend(nums1[p1:m])
nums1[:] = res
官方提供的代码
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
sorted = []
p1, p2 = 0, 0
while p1 < m or p2 < n:
if p1 == m:
sorted.append(nums2[p2])
p2 += 1
elif p2 == n:
sorted.append(nums1[p1])
p1 += 1
elif nums1[p1] < nums2[p2]:
sorted.append(nums1[p1])
p1 += 1
else:
sorted.append(nums2[p2])
p2 += 1
nums1[:] = sorted
时间复杂度:O(m+n)
空间复杂度:O(m+n)
方法三:逆向双指针
方法二是将使用了临时变量,如果考虑不使用临时变量的话,可以从nums1的尾部进行添加,将较大的数添加到后面,从而不会影响前面的数。
代码如下:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
p1 = m - 1
p2 = n - 1
tail = m + n -1
while p1 >= 0 or p2 >= 0:
if p1 == -1:
nums1[tail] = nums2[p2]
p2 -= 1
elif p2 == -1:
nums1[tail] = nums1[p1]
p1 -= 1
elif nums1[p1] > nums2[p2]:
nums1[tail] = nums1[p1]
p1 -= 1
else:
nums1[tail] = nums2[p2]
p2 -= 1
tail -=1
时间复杂度:O(m+n)
空间复杂度:O(1)
买卖股票的最佳时机(121)
难度:【简单】
两种方法:
方法一:暴力解法
对数组进行遍历,找到后一个数与前一个数的最大差值,返回。
注意遍历 j 时要从 i + 1 进行遍历。
代码如下:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
res = 0
for i in range(len(prices)):
for j in range(i+1,len(prices)):
res = max(res,prices[j] - prices[i])
return res
在 leetcode 上运行上面代码会出现超出时间限制的问题。
时间复杂度:O(n^2)
空间复杂度:O(1)
方法二:
只进行一次遍历,在遍历过程中更新两个值,股票最小值和差值最大值,更新到最后即可。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
res = 0
minPrice = prices[0]
for i in range(len(prices)):
res = max(res, prices[i] - minPrice)
minPrice = min(minPrice, prices[i])
return res
时间复杂度:O(n)
空间复杂度:O(1)
连续子数组的最大乘积(152)
4. 寻找旋转排序数组中的最小值(153M)
难度:【中等】
思路:
由于数组不包含重复元素,并且只要当前的区间长度不为 1,
p
i
v
o
t
\it pivot
pivot 就不会与
h
i
g
h
\it high
high 重合;而如果当前的区间长度为 1,这说明我们已经可以结束二分查找了。因此不会存在
nums
[
pivot
]
=
nums
[
high
]
\textit{nums}[\textit{pivot}] = \textit{nums}[\textit{high}]
nums[pivot]=nums[high] 的情况。
当二分查找结束时,我们就得到了最小值所在的位置。
代码:
class Solution:
def findMin(self, nums: List[int]) -> int:
low, high = 0, len(nums) - 1
while low < high:
pivot = low + (high - low) // 2
if nums[pivot] < nums[high]:
high = pivot
else:
low = pivot + 1
return nums[low]
5. 寻找旋转排序数组中的最小值 II(154H)
难度:【困难】
思路:
主要思路参考上一题153,但需注意特殊情况。
特殊情况:当
nums
[
pivot
]
=
nums
[
high
]
\textit{nums}[\textit{pivot}] = \textit{nums}[\textit{high}]
nums[pivot]=nums[high] 时,需要忽略二分查找区间的右端点。
代码
class Solution:
def findMin(self, nums: List[int]) -> int:
low, high = 0, len(nums) - 1
while low < high:
pivot = low + (high - low) // 2
if nums[pivot] < nums[high]:
high = pivot
elif nums[pivot] > nums[high]:
low = pivot + 1
else:
high -= 1
return nums[low]
6. 岛屿数量(200M)
难度:【中等】
思路:
深度优先搜索:对网格进行遍历,如果一个位置为1,则以该位置为起点进行深度搜索,在搜索过程中,为1的位置都会变为0,直到遍历结束。注意边界条件
代码
class Solution:
def dfs(self, grid, r, c):
grid[r][c] = '0'
nr = len(grid)
nc = len(grid[0])
for x,y in [(r,c-1),(r,c+1),(r-1,c),(r+1,c)]:
if 0 <= x < nr and 0 <= y < nc and grid[x][y] == '1':
self.dfs(grid, x, y)
def numIslands(self, grid: List[List[str]]) -> int:
nr = len(grid)
if nr == 0:
return 0
nc = len(grid[0])
numIslands = 0
for r in range(nr):
for c in range(nc):
if grid[r][c] == '1':
numIslands += 1
self.dfs(grid, r, c)
return numIslands
数组中的第K个最大的元素(215M)
难度:【中等】
三种思路:一种是直接使用sorted函数进行排序,一种是使用小顶堆,一种是使用快排(双指针 + 分治)。
方法一:直接使用sorted函数进行排序
代码如下:
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
return sorted(nums, reverse = True)[k-1]
方法二:
维护一个size为 k 的小顶堆,把每个数丢进去,如果堆的 size > k,就把堆顶pop掉(因为它是最小的),这样可以保证堆顶元素一定是第 k 大的数。
代码如下:
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
heap = []
for num in nums:
heappush(heap,num)
if len(heap) > k:
heappop(heap)
return heap[0]
时间复杂度:O(nlogk)
空间复杂度:O(k)
方法三:双指针 + 分治
partition部分
定义两个指针left 和 right,还要指定一个中心pivot(这里直接取最左边的元素为中心,即 nums[i])
不断将两个指针向中间移动,使得大于pivot的元素都在pivot的右边,小于pivot的元素都在pivot的左边,注意最后满足时,left是和right相等的,因此需要将pivot赋给此时的left或right。
然后再将中心点的索引和 k - 1 进行比较,通过不断更新left和right找到最终的第 k 个位置。
代码如下:
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
left, right, target = 0, len(nums)-1, k-1
while True:
pos = self.partition(nums, left, right)
if pos == target:
return nums[pos]
elif pos > target:
right = pos -1
else:
left = pos + 1
def partition(self, nums, left, right):
pivot = nums[left]
while left < right:
while nums[right] <= pivot and left < right:
right -=1
nums[left] = nums[right]
while nums[left] >= pivot and left < right:
left +=1
nums[right] = nums[left]
nums[left] = pivot
return left
时间复杂度:O(n),原版快排是O(nlogn),而这里只需要在一个分支递归,因此降为O(n)
空间复杂度:O(logn)
二分查找(704E)
难度:【简单】
思路:搜索区间两端闭,while条件带等号,mid要加减1。
代码:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left)//2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
return -1
时间复杂度:O(logN)。
空间复杂度:O(1)。
公平的糖果棒交换(888E)
难度:【简单】
思路:
将两个人的糖果相减后除以2,取 set(B),遍历A,找 i - x 是否在 B 中存在
代码:
class Solution:
def fairCandySwap(self, A: List[int], B: List[int]) -> List[int]:
x = (sum(A) - sum(B))//2
d = set(B)
for i in A:
if i - x in d:
return [i,i-x]