算法记录 | Day1数组基础
LeetCode 704-二分查找
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
解题思路
- 该题使用二分法解答。即从中间开始查找,根据结果的大小再向左或者向右查找。可以立刻想到用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) // 2、mid = (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 的元素,并返回移除后数组的新长度。
输入: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)~~,暂时先这么多吧,后面会继续更新,以及让排版更舒服。