704. 二分查找法
学习视频地址:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
学习文档地址:
学习开始时间:17:00-18:00
文档记录时间: 19:00-20:00
状态:已听懂|可单独复写代码
1. 看到问题后的初始想法
因为我本人在大二的时候上过数据结构课程,其中就重点讲解了二分法,因此我对这个算法还是有较深的映像。但是我一直以来仅仅掌握了二分搜索法的“中心思想”——不断地分割中心点一直分到中心点即为目标所求值(属实是非常浅显的思维与理解了哈哈哈哈哈)。但是我从未真正系统的了解二分法的具体实现,数据结构课程的课程项目中的二分法也是直接复制粘贴老师的代码,并未逐行代码的推敲。所以我对于这道题的水平层次还没达到left<right还是left<=right的水平。(我之前尝试推过代码,也可以推出90%,但是一些核心点我相信在没有经过仔细推敲和系统学习算法的情况下我一定是推导不出来的。)
2. 看完随想录后的迭代想法
看似简单、类似于abandon的算法有着非常重要的一个思想:区间的不变性。首先我先定义区间的属性,在下文中,本篇记录将左闭右开[left, right)以及左闭右闭[left, right]称作区间的属性,属性的不变性即代表当我们确认区间的属性后,在二分查找法接下来的所有代码中,我们都默认区间的属性是固定不变的。我们在使用二分法时,需要不断地和区间打交道,进行区间的分割,区间端点的比较。因此区间的自身属性的不变性就非常重要了,如果每个循环中区间的属性都不一样,那就根本无法完成二分法的查找任务。其次,二分查找法更新的是left与right两端的端点(根据middle与target的值进行更新)。
区间属性在代码中的应用:
#######################区间属性的运用#####################
### 1. while 循环
## 当[left, right]时
while left <= right : # 由于规定左闭右闭,故我们允许left = right
## 当[left, right)时
while left < right: # 此时不可以left = right
### 2. left or right进行更新
## 当[left, right]时
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
## 当[left, right)时
if nums[middle] > target:
right = middle # 因为右开
elif nums[middle] < target:
left = middle + 1
最终完整代码为:
## Method 1 左闭右闭
def binarysearch(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
### Use [left, right]
left = 0
right = len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
## Method 2 左闭右开
def binarysearch(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
### Use [left, right)
left = 0
right = len(nums) #Alarm: 和第一问不一样!因为这里是右开
while left < right:
middle = (left + right) // 2
if nums[middle] > target:
right = middle
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
leetcode测试结果:
27. 移除元素
学习视频地址:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili
学习文档地址:代码随想录 (programmercarl.com)
学习开始时间:20:00-20:40
文档记录时间: 21:32-21:50
状态:已听懂|可单独复写代码|需复习
1. 看到问题后的初始想法
看了第一眼感觉很懵,我甚至连暴力解法都没有想出来。。。我的第一反应是创建一个空数组以储存需要的元素,但这明显是和题意不相符的。我需要提高对于处理数组本身的技巧(不借助其他数据结构来完成目的)。
2. 看完随想录后的迭代想法
醍醐灌顶,双指针居然如此巧妙!我甚至觉得暴力解法都有点巧妙哈哈哈哈
暴力解法
首先定义一个指针,是指针遍历整个需要的数组(这里使用需要的指的是并不是遍历整个数组,遍历对象的长度会随着发现删除值而缩小,可以看下面代码辅助理解)。当指针碰到需要删除的目标value,则将该指针后所有的数据均往前移动一格,该指针保持不动(因为该指针所在的位置以及被下一个位置的数据覆盖了,我们还得判断下一个位置的数据是否和目标value相同)
## 暴力求解
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
### 暴力解法
node = 0
size = len(nums)
while node < size:
if nums[node] == val:
for i in range(node+1, size):
nums[i-1] = nums[i]
size -= 1
else:
node += 1
return size
双指针法
我们首先定义2个指针:
- Fast Node :负责遍历整个数组以寻找能够进入新数组的数值(即与目标value不相同的数值)
- Slow Node:负责确定整个新数组的大小以及Fast Node找到能够进入新数组的数值后进入的位置
这样,我们是Fast Node遍历整个数组以寻找目标value,当没找到目标value时,将Fast node所在数值传递给Slow Node,并且Fast和Slow均往后移动一格。若已经找到了和目标value相同的数值,Fast Node往后移动一格,但是Slow Node不动,因为此时新数组的插入位置不变(和目标value相同的数值不可以进入新数组),最后返回slow node的位置即可(大小即为新数组的size)。
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
### 双指针法
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] =nums[fast]
slow += 1
length = slow
return length
leetcode测试结果: