代码随想录算法训练营第一天 |二分查找、移除元素

414 阅读5分钟

二分查找

解题思路: 看到无重复元素的有序数组,而且时间复杂度要求O(logn)就可以考虑用二分法了 二分法就是选中间的元素比大小,然后根据大小缩小至左区间/右区间,最后缩到符合条件。

题目:leetcode.cn/problems/bi…

代码:

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插入的位置,需要分情况讨论,脑子要想清楚,想不清楚找个示例推一推也行。

题目:leetcode.cn/problems/fi…

代码:

我的思路:

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)//2nums[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

解题思路: 用双指针去做,快指针在前面走,找到不重复的元素就让慢指针指向快指针.

难点: 理解快慢指针的思想,尤其要注意快指针的数组越界问题