二分查找
解题思路: 看到无重复元素的有序数组,而且时间复杂度要求O(logn)就可以考虑用二分法了 二分法就是选中间的元素比大小,然后根据大小缩小至左区间/右区间,最后缩到符合条件。
代码:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid - 1
else:
left = mid + 1
return -1
这道题的思路就是选择nums[mid]与target比大小,比target大,说明target位于左区间[left,mid-1],比target小,说明target位于右区间[mid+1,right],等于就返回结果了。
难点:
在于判断条件和循环的递增是怎么判断。是left<=right,还是left<right;是right=mid,还是right=mid-1,这其实是两种写法,都可以用,但不能用混了。
以left<=right为例,就意味着left=right的时候是一个有意义的区间,所以需要选择的区间是[left,right]这样一个闭区间,所以当nums[mid]>target时,nums[mid]不是你要找的值,你需要去[left,mid-1]中去找,所以right=mid-1。
题目:(leetcode.cn/problems/se…)leetcode.cn/problems/se…
代码:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = right - 1
return left
解题思路:
这道题基于二分查找的基础上还要求“如果目标值不存在于数组中,返回它将会被按顺序插入的位置。”所以需要考虑target不在数组中的情况。
这时候可以分情况讨论:
-target位于区间的左边,即在循环中left没变过,right一直在-1,直到不满足left<=right,跳出循环,即[0,-1],target应该插入的位置是0,即left
-target位于区间的右边同理
-target位于区间内,但是需要插入到数组中的某个位置,即区间缩小至left==right时还没有找到target,返回值上一步区间为[left,left+1] (其实就是区间里没有第三个数了),可知nums[left]<target<nums[right],所以target位于目前left的后一位,循环中执行到这一步时,紧接着left=mid+1,这时候mid= (left + right) // 2 = left,所以target应该插入的位置就是left。
难点:
难点就在于判断target插入的位置,需要分情况讨论,脑子要想清楚,想不清楚找个示例推一推也行。
代码:
我的思路:
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left = 0
right = len(nums) - 1 # target位于[left,right]区间内
while left <= right: # [right,right] still have number
mid = left + (right - left) // 2
if nums[mid] < target:
left = left + 1 # target in [left+1,right]
elif nums[mid] > target:
right = right - 1 # target in [left,right-1]
elif nums[mid] == target:
while nums[left] != target:
left += 1
while nums[right] != target:
right -= 1
return [left,right]
return [-1,-1]
找左右边界的思路
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def getRightBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
else: # 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1
rightBoder = left
return rightBoder
def getLeftBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] >= target: # 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1
leftBoder = right
else:
left = middle + 1
return leftBoder
leftBoder = getLeftBorder(nums, target)
rightBoder = getRightBorder(nums, target)
# 情况一
if leftBoder == -2 or rightBoder == -2: return [-1, -1]
# 情况三
if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
# 情况二
return [-1, -1]
解题思路:
这道题还是基于二分法去找target,我的思路就是如果target命中了,说明target位于[left,right]这个区间了,就left和right往里缩直到都等于target就找到第一个和最后一个了。如果target命中不了,说明target不在数组内,就返回[-1,-1]
找左右边界的思路,以找右边界为例,当nums[middle]等于目标值时,我们希望继续向右查找,以确定是否有更大的索引处的值也等于目标值。因此,我们将left设置为middle + 1,并将rightBoder更新为left。
这样做的原因是,如果nums[middle]等于目标值,那么middle就可能是我们正在寻找的右边界。但是,我们还需要继续向右查找,以确定是否有更大的索引处的值也等于目标值。因此,我们将left设置为middle + 1,并将rightBoder更新为left,这样在下一次循环中,我们将从middle + 1开始查找,如果找不到等于目标值的元素,那么rightBoder就是我们要找的右边界。
难点:
在二分法的基础上学会找左右边界,脑子里要想清楚,当我们在找右边界时,其实是在让mid指向满足target的最右边的那个值,如何做到这一点,mid = (left+right)//2,nums[mid]>target时,right=mid-1,mid在往左走,这个不用关注,按照二分法写就行了,nums[mid]<target时,left = mid+1,mid在往右走,需要关注,nums[mid]=target时,不能确定有没有比mid更大的索引会等于target了,所以还要看[mid+1,right]里能不能命中target,有就更新右边界,没有就不更新。左边界也同理。
移除元素
** 题目:leetcode.cn/problems/re…
代码:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow, fast = 0, 0
while fast < len(nums):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
解题思路: 用双指针去做,快指针在前面走,找到不重复的元素就让慢指针指向快指针.
难点: 理解快慢指针的思想,尤其要注意快指针的数组越界问题