文章目录
704. 二分查找 (Binary search)
二分查找最重要的就是坚持同一个思路,在循环中必须一直保持不变量成立。
Debug 的重要方法:在小数组(例如[]
,[1, 2]
和[1, 2, 3]
)上进行测验,没能保持不变量的解法一般会陷入死循环或是错误的答案。
注意 int overflow:mid_idx = left_idx + (right_idx - left_idx >> 1)
左闭右闭
搜索范围:[left_idx, right_idx]
Invariant: left_idx
<= target_idx
<= right_idx
class Solution:
def search(self, nums: List[int], target: int) -> int:
left_idx = 0
right_idx = len(nums) - 1
while (left_idx <= right_idx):
mid_idx = left_idx + (right_idx - left_idx) // 2 # prevent overflow
if (nums[mid_idx] < target):
left_idx = mid_idx + 1
elif (nums[mid_idx] > target):
right_idx = mid_idx - 1
else:
return mid_idx
# if exit the while loop, means left_idx > right_idx and no target is found
return -1
左闭右开
The search range is [left_idx, right_idx).
class Solution:
def search(self, nums: List[int], target: int) -> int:
left_idx = 0
right_idx = len(nums)
while (left_idx < right_idx):
mid_idx = left_idx + (right_idx - left_idx) // 2 # prevent overflow
if (nums[mid_idx] < target):
left_idx = mid_idx + 1
elif (nums[mid_idx] > target):
right_idx = mid_idx
else:
return mid_idx
# if exit the while loop, means left_idx > right_idx and no target is found
return -1
35. 搜索插入位置
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left_idx = 0
right_idx = len(nums) - 1
while (left_idx <= right_idx):
mid_idx = left_idx + (right_idx - left_idx >> 1)
if (nums[mid_idx] < target):
left_idx = mid_idx + 1
elif (nums[mid_idx] > target):
right_idx = mid_idx - 1
else:
return mid_idx
# exiting the loop, must have left_idx = right_idx + 1
return left_idx
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left_idx = 0
right_idx = len(nums) - 1
while (left_idx <= right_idx):
mid_idx = left_idx + (right_idx - left_idx >> 1)
if (nums[mid_idx] < target):
left_idx = mid_idx + 1
elif (nums[mid_idx] > target):
right_idx = mid_idx - 1
else:
left_end_idx = right_start_idx = mid_idx
while (left_idx < left_end_idx and nums[left_idx] != target):
left_mid_idx = left_idx + (left_end_idx - left_idx >> 1)
if nums[left_mid_idx] != target:
left_idx = left_mid_idx + 1
else:
left_end_idx = left_mid_idx
while (right_idx >= right_start_idx and nums[right_idx] != target):
right_mid_idx = right_start_idx + (right_idx - right_start_idx >> 1)
if nums[right_mid_idx] != target:
right_idx = right_mid_idx - 1
else:
right_start_idx = right_mid_idx + 1
return [left_idx, right_idx]
return [-1, -1]
27. 移除元素 (Remove Element)
暴力算法
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
num_vals = 0
i = 0
while (i < len(nums) - num_vals):
if nums[i] == val:
num_vals += 1
for j in range(len(nums) - i - num_vals):
nums[i+j] = nums[i+j+1]
else:
i += 1
return len(nums) - num_vals
以下方法更简单,但是需要sort()
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
num_vals = 0
for i in range(len(nums)):
if (nums[i] == val):
nums[i] = float("inf")
num_vals += 1
nums.sort()
return len(nums) - num_vals
双指针 / 快慢指针 - 经典解法
fast_idx
: 快指针负责获取数组信息(探路)slow_idx
: 慢指针根据快指针的结果决定是否复制快指针的结果(保留元素)- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
快慢指针保持了数组元素的相对位置,但缺点是很容易出现worst-case(只要第一个元素是目标值,就会出现 n n n FLOPs)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow_idx = 0
for fast_idx in range(len(nums)):
if nums[fast_idx] != val:
nums[slow_idx] = nums[fast_idx]
slow_idx += 1
return slow_idx
双向指针
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
双向指针中,每当从左向右遇到一个需要删除的目标值,就从数组从右向左地找到一个非目标值填入,从而最大程度地减少所需 FLOPs。但双向指针不能保持数组元素顺序。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
head_idx = 0
tail_idx = len(nums) - 1
while (head_idx <= tail_idx):
if nums[head_idx] != val:
head_idx += 1
elif nums[tail_idx] != val:
nums[head_idx] = nums[tail_idx]
head_idx += 1
tail_idx -= 1
else:
tail_idx -= 1
return head_idx