1661 字
8 分钟
代码随想录算法训练营第一天 | 数组 Part01
数组基础
- 数组下标从0开始,内存空间地址连续分布。
- 数组查询时间复杂度为O(1),插入和删除时间复杂度为O(n)(因为需要移动其他元素)。
二分查找
- 前提条件:数组必须是有序的,才能使用二分查找快速定位元素。
- 二分查找时间复杂度为O(log n),相比线性查找的O(n)更加高效。
704. 二分查找
class Solution: # 二分查找前需要明确区间定义:选择[left, right]还是[left, right) # 这里采用左闭右闭区间[left, right] def search(self, nums: List[int], target: int) -> int: # nums下标范围是[0, len(nums)-1],采用[left, right]区间,所以right初始值为len(nums)-1 left, right = 0, len(nums) - 1 # 当left == right时,区间[left, right]仍然有效,所以条件是left <= right while left <= right: mid = left + (right - left) // 2 if nums[mid] > target: # nums[mid]已经比较过,且大于target,所以新的right范围需要排除mid right = mid - 1 elif nums[mid] < target: # nums[mid]已经比较过,且小于target,所以新的left范围需要排除mid left = mid + 1 else: return mid return -1
35.搜索插入位置
class Solution: # 因为数组有序,我们希望插入后仍然有序 # 插入的合法位置,一定是"从左往右数,第一个不小于 target 的位置" # 这就是"找第一个 >= target 的位置" def searchInsert(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] > target: right = mid - 1 elif nums[mid] < target: left = mid + 1 else: return mid # 二分查找过程中保持的循环不变量: # - nums[0..left-1] < target # - nums[right+1..end] > target # 也就是说,left左边所有元素都 < target,right右边所有元素都 > target # 当循环结束时,left 正好指向第一个 >= target 的位置(也是插入位置) return left
34. 在排序数组中查找元素的第一个和最后一个位置
LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置
class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: left, right = self.leftBound(nums, target), self.rightBound(nums, target) return [left, right]
# 寻找左边界就是找第一个 >= target 的位置 def leftBound(self, nums, target): left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] < target: left = mid + 1 else: # 寻找左边界:当nums[mid] == target时,继续向左收缩,寻找最左端的target right = mid - 1 # 循环结束后,left 是最后一个 <= target 的位置 # 检查边界条件:确保left在有效范围内,且nums[left]确实等于target return left if left >= 0 and left < len(nums) and nums[left] == target else -1
# 寻找右边界就是找到第一个 <= target 的位置 def rightBound(self, nums, target): left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] > target: right = mid - 1 else: # 寻找右边界:当nums[mid] == target时,继续向右收缩,寻找最右端的target left = mid + 1 # 循环结束后,right 是第一个 >= target 的位置 # 检查边界条件:确保right在有效范围内,且nums[right]确实等于target return right if right >= 0 and right < len(nums) and nums[right] == target else -1
69. x 的平方根
class Solution: # 本质是查找满足 mid * mid <= x 的最大整数 mid # 即找到最后一个使得平方不超过 x 的数 def mySqrt(self, x: int) -> int: left, right = 0, x while left <= right: mid = left + (right - left) // 2 if mid * mid > x: right = mid - 1 elif mid * mid < x: left = mid + 1 else: return mid # 循环结束时: # - 所有小于等于 x 的平方都在 [0, right] # - 所有大于 x 的平方都在 [left, x] # 此时 right 是满足 mid*mid <= x 的最大整数,即 x 的整数平方根 return right
367. 有效的完全平方数
class Solution: def isPerfectSquare(self, num: int) -> bool: left, right = 0, num while left <= right: mid = left + (right - left) // 2 temp = mid * mid if temp < num: left = mid + 1 elif temp > num: right = mid - 1 else: return True return False
双指针
- 双指针是处理数组或链表问题的常用算法技巧。
- 双指针主要分为两种类型:
- 快慢指针:通常用于链表,快指针每次移动两步,慢指针每次移动一步,用于检测环或寻找中点
- 左右指针:在数组或字符串中使用两个指针分别从两端向中间移动,常用于查找满足条件的子数组或子字符串
27. 移除元素
class Solution: # 双指针思路:slow指针指向新数组的下一个待填充位置,fast指针遍历原数组 # 当fast指向的值等于val时,fast继续向前移动(跳过该元素) # 当fast指向的值不等于val时,将该值复制到slow位置,然后slow和fast都向前移动 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
283. 移动零
class Solution: # 思路与上题相同:将val改为0,额外步骤是将数组后面的位置全部填充为0 def moveZeroes(self, nums: List[int]) -> None: slow = 0 for fast in range(len(nums)): if nums[fast] != 0: nums[slow] = nums[fast] slow += 1 # 将slow之后的所有位置填充为0 for i in range(slow, len(nums)): nums[i] = 0
844. 比较含退格的字符串
class Solution: def backspaceCompare(self, s: str, t: str) -> bool: s = self.traverse(s) t = self.traverse(t) return s == t
def traverse(self, s): s = list(s) slow = 0 for fast in range(len(s)): if s[fast] != '#': s[slow] = s[fast] slow += 1 else: # 关键处理:当fast指向'#'时,需要模拟退格操作 # slow向后退一格(如果可能),表示删除前一个字符 if slow > 0: slow -= 1 return ''.join(s[:slow])
977. 有序数组的平方
class Solution: # 左右双指针策略:由于存在负数,平方后最大值只能出现在数组两端 # 比较左右指针位置元素的平方大小,将较大值放入结果数组的末尾 # 然后移动对应指针,继续比较,直到处理完所有元素 def sortedSquares(self, nums: List[int]) -> List[int]: left, right = 0, len(nums) - 1 res = [0] * len(nums) cur = len(nums) - 1 # 结果数组的填充位置,从后往前填充 while left <= right: multi_left = nums[left] * nums[left] multi_right = nums[right] * nums[right] if multi_left >= multi_right: res[cur] = multi_left left += 1 else: res[cur] = multi_right right -= 1 cur -= 1 return res
代码随想录算法训练营第一天 | 数组 Part01
https://fuwari.vercel.app/posts/code_musings_day_one/