1. 数组
- 地址连续 类型相同
- 双指针算法:适用于数组的查找、删除、特定位置插入等。
2.算法详解
2.1 704. 二分查找
(1) 遍历查找
算法复杂度:O(n)
- 当涉及到数组的查询操作时,最容易想到的是暴力解法,即遍历法。通过从第一个元素遍历到最后一个元素的操作,不会遗漏任何一个待查找元素。然而,正因为遍历操作需要访问每一个元素,因此当数组长度过长时,算法复杂度也会很高,运行时间过长。
- 具体算法如下:
class Solution:
def search(self, nums: List[int], target: int) -> int:
for i in range(len(nums)):
if nums[i] == target:
return i
return -1
(2) 二分查找
算法复杂度:O(log(n))
- 二分法是数学上用于解决数据查找的有效手段。在每一次代码的执行中,将搜索区间一分为二,也就是说,每次都会缩小1/2的搜索范围,因此相比起遍历查找算法具有更加高效的性能。
- 具体算法如下:
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 定义左右指针
left, right = 0, len(nums)-1
# 左闭 右开
# while left < right:
# 左闭 右闭
while left <= right:
# mid = (right - left) // 2 + left
mid = (right + left) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
# 左闭 右开(right不包含在内)
# right = mid
# 左闭 右闭
right = mid - 1
else:
left = mid + 1
return -1
易错点
(1) 左闭右闭[left, right]:即left和right指针所指的值均在搜索范围之内,因此当nums[mid]>target
时,右指针right应该不包含mid,因此right=mid-1
。另外,在执行代码的过程可能出现左右边界值相等的情况,因此循环体循环条件为left <= right
。
(2) 左闭右开[left, right]:即仅left指针所指值在搜索范围之内,而right指针所指值不在搜索范围之内。同上,当nums[mid]>target
时,右指针应当包含mid,因此right=mid
。另外,由于右边界值不在搜索范围内,假设当左右边界为同一个值时,左右边界无法相等,因此循环条件为left < right
。
2.2 27. 移除元素
注意:需要原地移除元素,无法排序。
算法:双指针。
步骤:1. 查找;2. 覆盖。
- 思路:由于算法结果涉及到一个新的数组,故需采用两个指针分别指向原始数组和新数组。
- left:永远指向新数组的最后一个元素。
- right:用于遍历原始数组的每一个元素。(由于不可排序,故采用遍历方法实现元素的查找)
双指针法算法思路
- 初始化left和right均为0。注意,此时right用于遍历原始数组,从0出发并非初始化为数组长度。
- 判断nums[right]是否等于目标值。
(1)若不等于目标值,则说明这个元素应该出现在新数组中,故保留,right和left同时右移。
(2)若等于目标值,则说明该元素不应该出现在新数组中,故left不移动;right向后移动,并从后往前依次覆盖元素。 - 当right遍历完数组时,算法结束,返回left的值(left永远指向新数组的最后一个值)。
- 具体算法如下
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
left, right = 0, 0
while right < len(nums):
if nums[right] == val:
right += 1
else:
nums[left] = nums[right]
left += 1
right += 1
return left