LeetCode刷题之路:数组
前言
看完《算法图解》一书后,需要算法进阶与大量实践,目前准备先按照网上大佬推荐的LeetCode刷题顺序进行实战,此博客仅作学习过程的记录。
一、二分查找
LeetCode第704题:
(1)暴力求解
class Solution:
def search(self, nums: List[int], target: int) -> int:
for i in range(len(nums)):
if target == nums[i]:
return i
else:
return -1
结果:
时间复杂度为O(n)。
(2)二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n -1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target: #一定要用elif,否则下面的else只与这一个联系
right = middle - 1
else:
return middle
return -1
这里使用的是二分查找中,左闭右闭的类型,在while判断中有等于符号,在left、right迭代中,需要+1、-1.
class Solution:
def search(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n -1
while left < right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target: #一定要用elif,否则下面的else只与这一个联系
right = middle
else:
return middle
return -1
这里使用的是左闭右开的写法,在提交的时候报错了,应该是只有列表中只有一个元素的会出现判断错误,可能需要单独考虑一个元素时的问题。
LeetCode第35题:
(1)暴力求解:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
for i in range(len(nums)):
if nums[i] >= target:
return i
return len(nums)
这种解法不满足题目O(logn)的要求,但是很好用。
(2)二分查找
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
# O(logn)复杂度,采用二分查找
# 相比经典的二分查找,需要解决在查找不到是的情况
# 思路:先解决一般查找情况
# 左闭右闭写法
left = 0
n = len(nums)
right = n - 1
while(left <= right):
middle = (left + right) // 2 #避免小数
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
else:
return middle
return right + 1
考虑四种情况:
a.target存在与数组中
b.target在数组左端
c.target在数组右端
d.target在数组中间
第一种按照return middle处理,其余三种经过分析都可以按照right + 1处理。
LeetCode第34题:
(1)暴力求解:
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 仍然先采用暴力求解:
res = [] #创建用于储存的结果的数组
for i in range(len(nums)):
if target == nums[i]:
res.append(i)
if len(res) > 0:
return [res[0],res[-1]]
else:
return [-1,-1]
虽然不满足题目要求,但是直观、简单。
(2)二分查找
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def find_left(nums,target):
left = 0
right = len(nums) - 1
leftboard = -2
while(left <= right):
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
# leftboard = left
else:
right = middle - 1
leftboard = right + 1
# 使用right赋值左边界的理由:比如当只有一个元素时,可能求不了if的情况,而只有else的情况,这时不会执行if语句,也就是说leftboard保持初始值,而会执行else语句,因为else包含了=的情况,所以这一步操作是为了处理这一类特殊的问题。且right = left - 1
return leftboard
def find_right(nums,target):
left = 0
right = len(nums) - 1
rightboard = -2
while(left <= right):
middle = (left + right) // 2
if nums[middle] > target:
right = middle - 1
# rightboard = right
else:
left = middle + 1
rightboard = left - 1
return rightboard
leftboard = find_left(nums,target)
rightboard = find_right(nums,target)
if leftboard == -2 or rightboard == -2:
return [-1,-1]
if rightboard >= leftboard:
return [leftboard,rightboard]
return [-1,-1]
这里写了很久,最难处理的是二分法的边界问题,以及考虑到各种奇怪的输入,中间需要注意的主要有两点:
a.当顺序数组中存在相同元素的时候,查找该元素时,经典二分法返回的结果只会是其中的一个。所以当考虑到有重复元素时,需要将查找定值问题转换为查找边界问题,而我总结到的规律时,当要查找左边界时,在if语句中对左边界的操作不变,而将>=target的情况合并为右边界操作。查找右边界类似(左闭右闭法)。最终的left为第一个=target的元素的下标,而right = left - 1。
b.第二个需要注意的点时,边界值给定后修改的问题,见代码中的注释。
二、删除元素
LeetCode第27题
(1)暴力求解
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
i, j = 0, 0
while i < n:
if nums[i] == val:
while j < (n - i - 1):
#这里比较容易绕进去,易将索引值与元素数弄混,这里右边代表的时元素数量,所以 = n - (i + 1) = n - i - 1
nums[i + j] = nums[i + j + 1]
j = j + 1
i = i - 1
n = n - 1
j = 0 # 每次循环完j的值记得归零
i = i + 1
return n
两个循环的暴力求解法,这里在使用Python写的时候出现了一点问题,不像C++或者Java中的for循环很容易能够在循环中改变循环的范围,Python中采用range(n)的话相当于生成了一个新的列表,即使在循环中改变n的值也不会使得循环次数改变,所以这里采用了while循环,但是一定要将边界考虑情况,不然容易出现循环不完整或者循环超出范围的问题。
(2)双指针
暴力求解虽然能够通过,但是时间复杂度为O(n**2),那么有没有一种解法能够将复杂度降低为O(n)呢?答案是双指针,代码如下:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
SlowIndex = 0
n = len(nums)
for FastIndex in range(n):
if nums[FastIndex] != val:
nums[SlowIndex] = nums[FastIndex]
SlowIndex = SlowIndex + 1
return SlowIndex
双指针的思想感觉上是将一个数组当两个数组来用,而使用的必要条件就是两个指针对数组的调用不会相互干扰,因此才有了快慢指针一说,在这里,快指针的作用时查找元素,即找到新数组中的元素,慢指针的作用是更新数组,当查找到不为val的元素时,进行储存。实际上这与最为直观的思路保持一致,遍历整个数组,找到不为val的元素并将其储存在新的数组中,只是这样使得时间复杂度与空间复杂度都为N,那么为了降低空间复杂度,不开辟新的空间,直接在原数组中操作,就变为了现在的双指针的思想。
总结
这次就更新这么多内容,后续将一直记录与更新。