- LeetCode 704.二分查找
- LeetCode 27.移除元素
前言
打卡代码随想录算法训练营第49期第一天,希望自己能继续坚持!同时,因为是第一次系统地写代码复现(刷题)的博文,经验不足,文章的结构和内容肯定有所欠缺,后续要逐步优化完善。
LeetCode 704. 二分查找
704-1 原始想法
我的初步理解是,二分查找实现的关键在于双下标法,通过不断缩小两下标所夹的区间实现对数组的高效搜索。
704-2 随想录提醒
要注意使用的前提:有序无重复元素的数组
注意区分两种区间设置方法,分别是“左闭右闭”“左闭右开”(与“左开右闭”与“左闭右开”同理,但业界使用较少,“左开右开”很容易出现死循环的问题,要加入额外的判断,不推荐)
由于有两种不同的区间设置,二分查找中有两大坑要注意甄别,关键是区间的开闭设置:
- while循环条件的设置,即left和right跳出循环的处理
- 更新区间边界是围绕middle的和target的比较结果展开的,要厘清区间更新后是取middle±1还是middle本身
704-3 最终题解
“左闭右闭”实现
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
left, right = 0, len(nums)-1
while (left <= right):
middle = (left+right)//2 # 这里不用纠结middle是向下还是向上取整,因为middle只代表着区间搜索的方向,不用绝对的精细
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else: return middle
return -1
“左闭右开”实现,主要区别就是初始的right取值和right更新的处理
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
left, right = 0, len(nums)
while (left < right):
middle = (left+right)//2 # 注意看,这里与“左闭右闭”是一致的,因为middle的取值不起决定作用,只代表区间收缩的方向
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle
else: return middle
return -1
704-4 Debug过程
在代码实现的过程中有一些错误,记录如下:
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else: return middle
以上面中“左闭右闭”为例,最初错误地将elif写为if,导致第一个if独立出来,后面的if和else自成一对,导致满足了第一个if必定会执行else中的return造成错误,与算法中的三种情形无法对应。
if nums[middle] < target:
left = middle + 1
if nums[middle] > target:
right = middle - 1
else: return middle
LeetCode 27. 移除元素
27-1 原始想法
没有想过用暴力方法求解,因为看到了训练营题目讲解中给出的**“双指针法”,自然而然地联想到“二分查找”里采用的首尾指针法**。后来发现,采用随想录里设计的快慢指针法可以在不改变原有数组元素顺序的条件下实现移除,应用场景更广。
27-2 随想录提醒
同上,快慢指针法可以保证不改变数组顺序,且复杂度仅为O(n)
27-3 最终题解
class Solution(object):
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
fast, slow = 0, 0
while fast <= len(nums)-1:
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
27-4 Debug过程
一开始没有真正理解快慢指针的含义,要特别注意,快指针适用于探索数据(遍历数组中的元素),慢指针用于指示符合要求的元素的数量。
由于混淆了快慢指针各自的作用,最初的代码如下:
class Solution(object):
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
fast, slow = 1, 0
while fast <= len(nums)-1:
if nums[slow] == val:
nums[slow] = nums[fast]
elif nums[slow] != val:
slow += 1
fast += 1
return slow
和最终题解的代码对比可以发现,不同的地方有两处,一是fast的初始取值,二是判断条件的对象。尽管while循环条件一致,但正确的方法是对nums[fast]进行判断,而非nums[slow],这表明fast才起到类似于“数据探索”的作用,而slow充其量只发挥索引指示的功能。总的来看,fast是主动的,slow是被动的,要把握好这一态势。