LeetCode 704.二分查找
曾在快手一面时遇到过,当时下意识用递归做的,事后想一想感觉迭代更好,代码简洁好调试
题目链接:704. 二分查找
思路:因为给定数组是有序的,思路就是中间值小了就右移左边界,中间值大了就左移右边界,python用的是左闭右闭的写法。
class Solution:
def search(self, nums: List[int], target: int) -> int:
n = len(nums)
left, right = 0, n-1
# 注意这里是<=,<的话会忽略left==right的情况,因为是左闭右闭,所以left==right也需要进行判断
while left <= right:
# 这个写法等价于(left+right)//2, 但可以避免(left+right)可能会内存溢出的情况
mid = left + (right-left)//2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid+1
else:
right = mid-1
return -1
时间复杂度: O ( l o g n ) O(log n) O(logn),空间复杂度: O ( 1 ) O(1) O(1)
go版本(左闭右开的写法):
func search(nums []int, target int) int {
left := 0
right := len(nums)
for left<right{
mid := left + (right-left)>>1
if nums[mid] == target{
return mid
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid
}
}
return -1
}
感觉go的代码结构比python好看,大括号yyds
————————分割线————————
20230505:这里详细记录一下左闭右开和左闭右闭的区别和在代码上的体现。
左闭右开,字面意思是取值的时候能取到left索引所在的值,但取不到right索引所在的值。这里思考一下,能取到意味着取值需要在索引范围内,并且判断的时候需要考虑在内;反之,取不到的值,其初始定义不应该在索引范围内,因为定义在索引范围内却又不取值的话,就会出现bug,同时判断的时候也不应该考虑在内。
左闭右闭,即left索引和right索引的值都能取到,那两个值如上文所述,初始定义都应该在索引范围内,且判断的时候都需要考虑在内。
初始定义这个事情好理解。判断情况举个例子,比如左闭右开的情况,在大循环判断的时候,因为right取不到,所以判断的时候就不应该考虑在内,那判断逻辑就应该写为
l
e
f
t
<
r
i
g
h
t
left<right
left<right;同时,因为右开的情况下,right在更改自己值的时候,也不应该“踏足”接下来要判断的区间的索引范围以内,所以是
r
i
g
h
t
=
m
i
d
right=mid
right=mid 而不能是
r
i
g
h
t
=
m
i
d
−
1
right=mid-1
right=mid−1。
LeetCode 27. 移除元素
题目链接:27.移除元素
思路:题目需要 “原地移除”,“不要使用额外的数组空间”,看到这些字眼首先想到的就是双指针。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow = 0
for _, v in enumerate(nums):
if v != val:
nums[slow] = v
slow += 1
return slow
时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( 1 ) O(1) O(1)
go版本:
func removeElement(nums []int, val int) int {
left, right := 0, 0
for right <= len(nums)-1 {
if nums[right]!=val {
nums[left] = nums[right]
left++
}
right++
}
return left
}
————————分割线————————
20230505:right指针正常的顺序遍历整个数组,left指针意味着有多少个跟val不一样的值。left个与val不相同的值就组成了新数组的长度left。