二分查找
LeetCode704题 二分查找
搜索区间为左闭右闭[left, right]
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
left = 0
right = len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
elif nums[middle] == target:
return middle
return -1
LeetCode167题 两数之和II-输入有序数组
二分查找变体-查找目标为一对数
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left, right = 0, len(numbers) - 1
while left < right:
sum_val = numbers[left] + numbers[right]
if sum_val == target:
return [left + 1, right + 1]
elif sum_val < target:
left += 1
else:
right -= 1
LeetCode35题 搜索插入位置
二分查找变体-查找目标的插入位置
class Solution:
def searchInsert(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
if left > 0:
return left
else:
return 0
LeetCode34题 在排序数组中查找元素的第一个和最后一个位置
查找有序重复数组的左右边界问题,与二分查找基础题相比,主要差异在对nums[mid]==target时的情况处理
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if len(nums) == 0:
return [-1, -1]
return [self.searchBound(nums, target, 'left'), self.searchBound(nums, target, 'right')]
def searchBound(self, nums, target, side):
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
if side == 'left':
# 收缩右侧边界
right = mid - 1
else:
# 收缩左侧边界
left = mid + 1
if side == 'left':
return left if 0 <= left < len(nums) and nums[left] == target else -1
else:
return right if 0 <= right < len(nums) and nums[right] == target else -1
LeetCode69题 x的平方根
本质是在找符合r*r<=x的r的右边界问题,所以返回right
class Solution:
def mySqrt(self, x: int) -> int:
left = 1
right = x
while left <= right:
mid = left + (right - left) // 2
r = mid * mid
if r == x:
return mid
elif r < x:
left = mid + 1
elif r > x:
right = mid - 1
return right
LeetCode875题 爱吃香蕉的珂珂
吃香蕉速度k和耗时h是负相关关系,所以可以用二分查找,查找符合条件的k的左边界
class Solution:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
m = max(piles)
left = 1
right = m
while left <= right:
mid = left + (right - left) // 2
mid_res = self.cal_hours(piles, mid)
if mid_res > h:
left = mid + 1
else:
right = mid - 1
if right < m:
return left
else:
return right
def cal_hours(self, piles, k):
res = 0
for i in piles:
if i % k != 0:
res += i // k + 1
else:
res += i // k
return res
LeetCode1011题 在D天内送达包裹的能力
运载能力和耗费天数是负相关关系,所以可以用二分查找,查找符合条件的运载能力的左边界
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
max_val = float('-inf')
sum_val = 0
for i in weights:
max_val = max(max_val, i)
sum_val += i
# 最小运载量是max_val,否则有的包裹永远无法被装载
# 最大运载量时sum_val,对应days=1情况
left = max_val
right = sum_val
while left <= right:
mid = left + (right - left) // 2
mid_res = self.cal_days(weights, mid)
if mid_res > days:
left = mid + 1
else:
right = mid - 1
if right < sum_val:
return left
else:
return right
def cal_days(self, weights, carrying):
res = 0
cur = carrying
for i in weights:
# 剩余运载能力装不下时,天数加一
if cur < i:
res += 1
cur = carrying
cur -= i
return res + 1
LeetCode410题 分割数组的最大值
这道题在问分割数组和的最大值的最小值,可以用二分查找搜索这个最小值,也就是二分查找左边界问题(如果要直接求解使得数组和的最大值最小的分割数组方案,复杂度会高一些)
class Solution:
def splitArray(self, nums: List[int], k: int) -> int:
max_val = float('-inf')
sum_val = 0
for i in nums:
max_val = max(max_val, i)
sum_val += i
left = max_val
right = sum_val
while left <= right:
mid = left + (right - left) // 2
is_valid = self.valid(nums, k, mid)
if not is_valid:
left = mid + 1
else:
# 向左侧压缩
right = mid - 1
return left if right < sum_val else right
def valid(self, nums, k, x):
# 判断是否满足最大分割子数组和不超过x
total = 0
cnt = 1
for num in nums:
if total + num > x:
cnt += 1
total = num
else:
total += num
return cnt <= k
LeetCode240题 搜索二维矩阵II
借助递归函数进行搜索,每次搜索排除1/3的搜索区域
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
self.res = False
m, n = len(matrix), len(matrix[0])
self.search(matrix, target, 0, m - 1, 0, n - 1)
return self.res
def search(self, matrix, target, x1, x2, y1, y2):
if self.res or x1 > x2 or y1 > y2:
return
x_mid = x1 + (x2 - x1) // 2
y_mid = y1 + (y2 - y1) // 2
if matrix[x_mid][y_mid] == target:
self.res = True
elif matrix[x_mid][y_mid] > target:
self.search(matrix, target, x1, x_mid - 1, y1, y2) # 上方
self.search(matrix, target, x_mid, x2, y1, y_mid - 1) # 左下方
elif matrix[x_mid][y_mid] < target:
self.search(matrix, target, x_mid + 1, x2, y1, y2) # 下方
self.search(matrix, target, x1, x_mid, y_mid + 1, y2) # 右上方
双指针的应用
LeetCode26题 删除有序数组中的重复项
快慢指针:让慢指针走在后面,快指针走在前面探路,找到一个不重复的元素就赋值给慢指针,并让慢指针前进一步,这样就保证了慢指针之前的元素都是无重复的
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
slow = 1
cur_num = nums[0]
for fast in range(1, len(nums)):
if nums[fast] != cur_num:
cur_num = nums[fast]
nums[slow] = nums[fast]
slow += 1
return slow
LeetCode27题 移除元素
思路同上题,快慢指针
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
LeetCode283题 移动零
思路同上面两个题,快慢指针,但本题需要保证数组中元素和原有数组一致,只是改变顺序,所以需要对快慢指针元素进行交换
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
LeetCode870题 优势洗牌
贪心思路:如果nums1的最大值比不过nums2的最大值,那其他元素肯定是白给了,显然这种情况应该用nums1的最小值去送人头,降低损失保存实力。如果nums1的最大值大于nums2的最大值,那就硬刚好了,反正这把nums1可以赢
所以我们可以对两个数组排序,然后用left指针代表nums1现存最大值,right指针代表nums1现存最小值,left比得过nums2[i]就硬刚,比不过就用right送人头
class Solution:
def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:
res = [0] * len(nums1)
left = 0
right = len(nums1) - 1
nums1.sort(reverse=True)
nums2 = [(i, nums2[i]) for i in range(len(nums2))]
nums2.sort(key=lambda x: x[1], reverse=True)
for i in range(len(nums1)):
if nums1[left] > nums2[i][1]:
res[nums2[i][0]] = nums1[left]
left += 1
else:
res[nums2[i][0]] = nums1[right]
right -= 1
return res
滑动窗口
LeetCode209题 长度最小的子数组
双指针(滑动窗口),不断的调节子序列的起始位置和终止位置,从而得出想要的结果
- 窗口就是满足和>=target的连续子数组
- 窗口的起始位置如何移动:如果当前窗口的值>=target,窗口就要向前移动了
- 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针
class Solution(object):
def minSubArrayLen(self, target, nums):
slow = 0
fast = 0
cum_sum = 0
res = float('inf')
while fast < len(nums):
cum_sum += nums[fast]
while cum_sum >= target:
res = min(res, fast - slow + 1)
cum_sum -= nums[slow]
slow += 1
fast += 1
if res != float('inf'):
return res
else:
return 0
LeetCode904题 水果成篮
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
res = float('-inf')
visited = [-1, -1]
left = 0
right = 0
while right < len(fruits):
if fruits[right] not in visited and visited[0] >= 0:
for i in range(right - 1, left - 1, -1):
if fruits[i] == visited[0]:
left = i + 1
break
res = max(res, right - left + 1)
if fruits[right] != visited[1]:
visited[0], visited[1] = visited[1], fruits[right]
right += 1
return res
前缀和数组
前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和
LeetCode303题 区域和检索-数组不可变
前缀和数组应用,前缀和数组索引i存储的是nums[:i]的和,可以使区域和检索复杂降为O(1)
lass NumArray:
def __init__(self, nums: List[int]):
# 前缀和数组,索引i存储的是nums[:i]的和
self.pre_sum_list = nums
total = 0
for i in range(len(nums)):
total += nums[i]
self.pre_sum_list[i] = total
def sumRange(self, left: int, right: int) -> int:
if left == 0:
return self.pre_sum_list[right]
else:
return self.pre_sum_list[right] - self.pre_sum_list[left - 1]
LeetCode304题 二维区域和检索-矩阵不可变
class NumMatrix:
def __init__(self, matrix: List[List[int]]):
m, n = len(matrix), len(matrix[0])
self.pre_sum_matrix = [[0] * n for _ in range(m)]
# 初始化
row_sum = col_sum = 0
for i in range(n):
row_sum += matrix[0][i]
self.pre_sum_matrix[0][i] = row_sum
for i in range(m):
col_sum += matrix[i][0]
self.pre_sum_matrix[i][0] = col_sum
# 通过dp数组方式得到二维前缀和数组
for i in range(1, m):
for j in range(1, n):
self.pre_sum_matrix[i][j] = self.pre_sum_matrix[i - 1][j] + self.pre_sum_matrix[i][j - 1] + \
matrix[i][j] - self.pre_sum_matrix[i - 1][j - 1]
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
if row1 == 0 and col1 == 0:
return self.pre_sum_matrix[row2][col2]
elif row1 == 0:
return self.pre_sum_matrix[row2][col2] - self.pre_sum_matrix[row2][col1 - 1]
elif col1 == 0:
return self.pre_sum_matrix[row2][col2] - self.pre_sum_matrix[row1 - 1][col2]
else:
return self.pre_sum_matrix[row2][col2] + self.pre_sum_matrix[row1 - 1][col1 - 1] - \
self.pre_sum_matrix[row1 - 1][col2] - self.pre_sum_matrix[row2][col1 - 1]
差分数组
差分数组的主要适用场景是频繁对原始数组某个区间的元素进行增减
定义差分数组为diff,diff[i] = nums[i] - nums[i-1](diff[0]=nums[0]),这样就可以快速进行区间增减的操作,如果你想对区间i~j的元素全部加3,那么只需要让diff[i] + 3,然后再让diff[j+1] - 3即可。最后根据diff再进行数组还原
LeetCode1094题 拼车
class Solution:
def carPooling(self, trips: List[List[int]], capacity: int) -> bool:
# 定义差分数组
dif_list = [capacity] + [0 for i in range(1000)]
# 差分数组更新
for i in trips:
dif_list[i[1]] -= i[0]
dif_list[i[2]] += i[0]
# 数组还原,判断是否超载
for i in range(1001):
if i > 0:
dif_list[i] = dif_list[i - 1] + dif_list[i]
if dif_list[i] < 0:
return False
return True
带权重的随机选择算法
LeetCode528题 按权重随机选择
前缀和数组 + 二分查找
class Solution:
def __init__(self, w: List[int]):
self.total = w[0]
# 前缀和数组
self.pre_sum_list = [0] * len(w)
self.pre_sum_list[0] = w[0]
for i in range(1, len(w)):
self.pre_sum_list[i] = self.pre_sum_list[i - 1] + w[i]
self.total += w[i]
def pickIndex(self) -> int:
import random
rand_num = random.randint(1, self.total)
# 二分查找,查找满足rand_num <= pre_sum_list[i]的左侧边界索引
left = 0
right = len(self.pre_sum_list) - 1
while left <= right:
mid = left + (right - left) // 2
if self.pre_sum_list[mid] < rand_num:
left = mid + 1
else:
right = mid - 1
if right == len(self.pre_sum_list) - 1:
return right
else:
return left
数组花式遍历
LeetCode48题 旋转图像
先按左上右下转置矩阵,然后交换每一行元素位置即可
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix[0])
# 按左上右下转置矩阵
for i in range(n):
for j in range(i, n):
matrix[j][i], matrix[i][j] = matrix[i][j], matrix[j][i]
# 交换每一行元素位置
for i in range(n):
matrix[i].reverse()
LeetCode54题 螺旋矩阵
在循环中,保持左闭右开逻辑(循环不变量)
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
res = []
m, n = len(matrix), len(matrix[0])
min_val = min(m, n)
num = min_val // 2 + min_val % 2
for i in range(num):
if i < n - i - 1 and i < m - i - 1:
for j in range(i, n - i - 1):
res.append(matrix[i][j])
for j in range(i, m - i - 1):
res.append(matrix[j][n - i - 1])
for j in range(n - i - 1, i, -1):
res.append(matrix[m - i - 1][j])
for j in range(m - i - 1, i, -1):
res.append(matrix[j][i])
elif i == n - i - 1 and i == m - i - 1:
res.append(matrix[i][i])
elif i < n - i - 1:
res.extend(matrix[i][i: n - i])
elif i < m - i - 1:
for j in range(i, m - i):
res.append(matrix[j][i])
return res