算法记录 | Day1数组基础

2,509 阅读3分钟

算法记录 | Day1数组基础

LeetCode 704-二分查找

题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

题目链接:leetcode.cn/problems/bi…

解题思路
  • 该题使用二分法解答。即从中间开始查找,根据结果的大小再向左或者向右查找。可以立刻想到用left、right指针。
  • 注意使用二分的原因:该数组为升序
  • 注意区间问题,判断条件是否能遍历所有下标。
1-二分法-第一种写法(左闭右闭区间 [left, right] )
def search(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = 0
        right = n-1
        while left <= right:
            mid = left + (right-left) // 2
            if target > nums[mid]:
                left = mid + 1
            elif target < nums[mid]:
                right = mid - 1
            else:
                return mid
        return -1
2-二分法-第二种写法(左闭右开区间 [left, right) )
def search(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = 0
        right = n
        while left < right:
            mid = left + (right-left) // 2
            if target > nums[mid]:
                left = mid + 1
            elif target < nums[mid]:
                right = mid
            else:
                return mid
难点
  • 二分法区间问题

    • 第一种--左闭右闭区间

      思考:

      • 假设在 nums = [0, 1, 2, 3, 4, 5] 中查找3。由于是左闭右闭区间,需要包含 0 与 5 -->[0, 5],因此 left 指针为 0,right 指针为 5, 即为len(nums) - 1.

      • 在 while 循环中,若查找到 [3, 3],left == right 是可以满足区间定义的,因此 while left <= right。当进行if else的判断的时候,mid就会已经被判断了,此时直接都进行 +1 或者 -1的操作即可。

    • 第二种--左闭右开区间

      思考:

      • 同样假设在 nums = [0, 1, 2, 3, 4, 5] 中查找3。由于是左闭右开区间,需要包含 0 与 5 --> [0, 6) ,因此 left 指针为 0,right 指针为 6, 即为len(nums).
      • 在 while 循环中,若查找到 [2, 2),等于2又不等于2是矛盾的,无法满足区间定义,因此 while left < right。当进行if else的判断的时候,mid一般根据写法指向中间偏左的位置,比如首先指向2。判断之后对于左边来说是[0, 2),对于2右边来说是[3, 6),所以left = mid + 1,right = mid。
  • 关于mid的写法

    • 这个问题我思考很久,mid = (right - left) // 2mid = (right + left) // 2 以及 mid = left + (right - left) // 2

      • mid = (right - left) // 2 这种写法运用在这里根本就是错误的。因为right - left 仅仅代表两者之间相差几个数的距离,再除以2只是说从左到右的一半距离,和下标就没有关系了。

      • mid = (right + left) // 2这种写法会发生上溢问题。int 占用 4 字节,32 比特,数据范围为:-2147483648 ~ 2147483647 [-2^31 ~ 2^31-1]

        那么对于两个都接近 2147483647的数字而言,它们相加的结果将会溢出,变成负数。所以,为了避免溢出情况的发生,我们不采用这种写法。

      • mid = left + (right - left) // 2 从左指针到右指针的一半距离 + 左指针的下标 才是符合题的下标位置。

总结

本身学过数据结构,这道题看到第一眼就明白是二分法。在看解法之前,我从未思考过这是一个区间问题,只是简单地理解推导分析出边界,比较耗费时间。现在就能更快速地思考出来原因和写法。


LeetCode 27-移除元素

题目描述:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

题目链接:leetcode.cn/problems/re…

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案
解题思路
1-暴力解法
def removeElement(self, nums: List[int], val: int) -> int:
        n = len(nums)
        i = 0
        while i < n:
            if nums[i] == val:
                for j in range(i + 1 , n):
                    nums[j - 1] = nums[j]
                i -= 1
                n -= 1
            i += 1
        return n

注意:遍历过程中遇到的问题:第一次没有使用while,使用for循环的时候,发现结果输出不对,原因是for i in range(n): 这个过程种的 i 只用于循环,-1的操作并不会对遍历中的 i 产生任何影响。

2-快慢指针
def removeElement(self, nums: List[int], val: int) -> int:
        # 快指针遍历元素
        fast = 0
        # 慢指针记录位置
        slow = 0
        for fast in range(len(nums)):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
        return slow

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置
难点

容易忘记快慢指针的解法,没办法立刻想到用快慢指针,看了题解才记起来。

总结

虽然知道双指针法(快慢指针法)在数组和链表的操作中是非常常见的,但这道题还是没有办法立刻找到这种思路。

收获:以上总结就是我最大的收获了,以及第一次写博客,说实话费了我不少时间,主要是我还挺在意排版格式的~~(强迫症o(≧口≦)o)~~,暂时先这么多吧,后面会继续更新,以及让排版更舒服。