数组是存储在连续内存空间上的、相同类型数据的集合(在删除或者增添元素时难免要移动其他元素的地址)
在数组中,可以通过下标索引获取对应数据(数组下标都是从0开始)
数组的元素是不能删的,只能覆盖
目录
34. 在排序数组中查找元素的第一个和最后一个位置【middle】
704. 二分查找
使用二分法的前提条件:数组有序+数组中无重复元素
两种写法的时空间复杂度均:
- 时间复杂度:O(logn)
- 空间复杂度:O(1)
(一)定义target在 [left, right] 里
while (left <= right) : 可以带等号
如果nums[middle] > target,则更新搜索范围右下标right为middle-1
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 定义target在左闭右闭的区间
left = 0
right = len(nums)-1
while left <= right:
middle = (right + left) // 2
if nums[middle] > target:
right = middle-1
elif nums[middle] < target:
left = middle+1
else:
return middle
# 未找到目标值
return -1
(二)定义target在 [left, right) 里
跟(一)左闭右闭的代码相比,有以下几处不同:
-
right = len(nums),不需要-1
-
while (left < right) ,不带等号
-
如果nums[middle] > target,则更新搜索范围右下标right为middle
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
while left < right:
middle = (left + right) // 2
if nums[middle] > target:
right = middle
elif nums[middle] < target:
left = middle+1
else:
return middle
return -1
相关题目推荐
35. 搜索插入位置
暴力解法时间复杂度为O(n),而本题要求必须使用时间复杂度为 O(logn) 的算法 —— 二分查找法
重点:如果目标值不存在于数组中,返回它将会被按顺序插入的位置,为此时的left指针
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)-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 left
34. 在排序数组中查找元素的第一个和最后一个位置【middle】
题目要求时间复杂度为O(logn)
考虑 target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target 的位置」(记为 leftIdx)和「第一个大于 target 的位置减一」(记为 rightIdx)
三种情况:
思路参考: 官方题解
-
在nums数组中二分查找得到第一个 ≥ target 的下标 leftidx
-
在nums数组中二分查找得到第一个 ≥ target+1 的下标,减1为 rightidx
-
如果第一个 ≥ target 的下标 leftidx 在数组右边,或者数组里不存在target,则返回 [-1, -1],否则返回 [leftidx, rightidx]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums, target):
left = 0
right = len(nums)-1
while left <= right:
middle = (left + right)//2
if nums[middle] >= target:
right = middle-1
else:
left = middle+1
return left # 若存在target,则返回第一个=target的值
# 第一个位置:返回第一个 >= target 元素的下标
leftidx = binarySearch(nums, target)
# 第二个位置:返回第一个 > target 元素的下标,再减1
rightidx = binarySearch(nums, target+1)-1
if leftidx == len(nums) or nums[leftidx] != target:
return [-1,-1]
else:
return [leftidx, rightidx]
69. x 的平方根
注意,例如 8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去,最后输出结果是2。因此,最后跳出 while循环时,返回的是 right指针,而不是left指针(跳出循环后,left指针在right指针的右侧)
class Solution:
def mySqrt(self, x: int) -> int:
left = 0
right = x
while left <= right:
middle = (left + right) // 2
if middle*middle > x:
right = middle-1
elif middle*middle < x:
left = middle+1
else:
return middle
return right
367. 有效的完全平方数
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left = 0
right = num
while left<=right:
middle = (left+right)//2
if middle*middle > num:
right = middle-1
elif middle*middle < num:
left = middle+1
else:
return True
return False
27. 移除元素
不需要考虑数组中超出新长度后面的元素
数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖
(一)暴力解法【两个for循环】
一个for循环遍历数组元素【注意实际写的是while】,一个for循环更新数组
- 时间复杂度O(n^2)
- 空间复杂度O(1)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i = 0
l = len(nums)
while i < l: # 遍历数组元素
if nums[i] == val:
for j in range(i+1, l): # 更新数组
nums[j-1] = nums[j]
i = i-1 # 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
l = l-1 # 此时数组长度-1
i = i+1
return l
(二)双指针法/快慢指针法【一个for循环即可】
时间复杂度O(n),空间复杂度O(1)
-
快指针:寻找新数组里所需要的元素,也即删除目标值之后的元素
-
慢指针:为新数组的下标值
把快指针所获得的值赋给慢指针所在的位置
class Solution:
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 = slow +1
return slow
注意该方法并没有改变元素的相对位置
相关题目推荐
26. 删除有序数组中的重复项
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
else:
# 删除重复元素后,也至少有1个元素,所以下标都从1开始
fast = 1
slow = 1
for fast in range(1,len(nums)):
if nums[fast] != nums[fast-1]:
nums[slow] = nums[fast]
slow += 1
return slow
283. 移动零
这题的重点在于交换,设置一个左指针left和一个右指针right,让right从左依次遍历每一个元素,当遇到非0元素时就和left指向的元素交换
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
left = 0
right = 0
for right in range(len(nums)):
if nums[right] != 0:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right += 1
844. 比较含退格的字符串
思路是看Leetcode刷题 844. 比较含退格的字符串 Backspace String Compare_哔哩哔哩_bilibili看懂的
从后往前读字符串,backspaceS和backspaceT是用来记录退格的
三种情况:
-
当前指针遇到退格键,backspaceS变量+1,指针i继续向左走
-
没有遇到退格,但backspaceS变量不为0,此时需要将backspaceS变量-1,指针i继续向左走
-
没有遇到退格,且backspaceS和backspaceT变量都为0,此时将两个字符串的指针所指字符进行比较,若不相等,则不是一个字符串;若相等,两个指针都分别向左走一位,继续比较
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
# 记录两个字符串#的数量
backspaceS = 0
backspaceT = 0
# 两个指针
i = len(s)-1
j = len(t)-1
while i>=0 or j>=0:
while i>=0:
if s[i] == '#': # 第一种情况
backspaceS += 1
i -= 1
elif backspaceS > 0: # 第二种情况
backspaceS -= 1
i -= 1
else:
break # i处理完了,j还没处理。此时i在非退格,且退格数为0的情况下
while j>=0:
if t[j] == '#':
backspaceT += 1
j -=1
elif backspaceT > 0:
backspaceT -= 1
j -=1
else:
break
# 第三种情况,比较两者
if i>=0 and j>=0: # 都指向字符
if s[i] != t[j]:
return False
elif i>=0 or j>=0: # 字符和空比较
return False
i -= 1
j -= 1
return True
977. 有序数组的平方
数组是有序的,只不过负数平方之后可能成为最大数了。那么数组平方的最大值就在数组两端,要么是最左边,要么是最右边,不可能是中间
-
双指针,i指向起始位置,j指向终止位置,逐步向中间合拢
-
定义一个新数组result,和A数组一样大小,让k指向result数组终止位置
时间复杂度为O(n)
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
ans = [0] * len(nums)
pos = len(nums) - 1 # 由大到小更新
i = 0
j = len(nums) - 1
while i<=j:
if nums[i]*nums[i] > nums[j]*nums[j]:
ans[pos] = nums[i]*nums[i]
i += 1
else:
ans[pos] = nums[j]*nums[j]
j -= 1
pos -= 1
return ans
209. 长度最小的子数组【middle】
(一)暴力解法
两个for循环,一个控制起始位置,一个控制终止位置,时间复杂度O(n^2)
超时了,略
(二)滑动窗口
重点:若只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置
-
窗口内的元素:保持窗口内数值总和 ≥s 的长度最小的连续子数组
-
移动窗口的起始位置:如果当前窗口的值≥s,则窗口向前移动(即缩小窗口,是一个持续向后移动的过程,而不是单次移动)
-
移动窗口的结束位置:for循环遍历数组的指针
时间复杂度O(n),空间复杂度O(1)
- 不要以为for里放一个while时间复杂度就是O(n^2),主要是看每一个元素被操作的次数:每个元素在滑动窗口进来操作一次,出去操作一次,即每个元素都是被操作两次,时间复杂度是 2 × n 也就是O(n)
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
result = float("inf") # 定义一个无限大的数
i = 0 # 起始位置
sum = 0
for j in range(len(nums)): # j为终止位置
sum += nums[j]
while sum >= target: # 是while,不是if,是一个持续向后移动的过程
sublength = j-i+1 # 此时子数组的长度
result = min(result, sublength)
sum -= nums[i] # 移动起始位置,缩小窗口
i += 1
return 0 if result == float("inf") else result
相关题目推荐
904. 水果成篮【middle】
翻译一下题目:给定一个数组,要求我们从这个数组中选取一个子数组,条件是这个子数组的长度最长,并且这个子数组中元素种类不能超过2
思路:滑动窗口 Leetcode(Sliding windows)-Python-904-水果成篮
-
用两个指针,一个是left,一个是right,初始都为0
-
right++,如果没有违背题目要求的性质(子数组中元素种类不能超过2),就计算一下目前子数组的长度right-left+1
-
直到违背性质,left++,直到不违背性质为止
-
整个过程中记录子数组的最大长度max_length
这一题与209. 长度最小的子数组的差别在于:(1)209是求长度最小,而本题是求长度最长,所以初始化时,长度最小的话初始化应是float("inf"),长度最长的话初始化是float("-inf");(2)209的要求就是长度,而本题的要求是窗口里数字的种类,通过一个dict来记录数字的种类(key为数字,value为该数字出现次数)
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
max_length = float("-inf")
left = 0
dic = {}
for right in range(len(fruits)):
if fruits[right] not in dic:
dic[fruits[right]] = 1
else:
dic[fruits[right]]+=1
while len(dic)>2: # 超过2类
dic[fruits[left]] -= 1
if dic[fruits[left]]==0:
del dic[fruits[left]] # 同时删掉key和value
left+=1
max_length = max(max_length, right-left+1)
return 0 if max_length == float("-inf") else max_length
求窗口长度min,那么一开始初始化为正无穷大float("inf")
求窗口长度max,那么一开始初始化为负无穷大float("-inf")
3. 无重复字符的最长子串【middle】
这题跟904的区别在于:904是要统计每个数字及对应的出现次数,用的是dict类型;而本题是要求字符无重复,用的是set类型
当滑动窗口的右边right移动时,若它指向的元素已经在集合里,则将滑动窗口左边left指向的元素从集合里remove掉,直到右边right指向的元素不在集合里了为止(是一个持续remove的过程,故为while,而不是if)。此时比较一下长度,并将right指向的元素add进集合里
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
left = 0
max_length = float("-inf")
mark = set() # 集合不能有重复元素
for right in range(len(s)):
while s[right] in mark:
mark.remove(s[left])
left+=1
max_length = max(max_length, right-left+1)
mark.add(s[right])
return 0 if max_length == float("-inf") else max_length
76. 最小覆盖子串【hard】
这题好难,明白了思路但是实际写的时候还是无从下手,还是得多写几遍才能熟...
要求:两个字符串,一个s,一个t,要求返回s中涵盖t所有字符的最小子串
思路:
-
通过collections库里的Counter类,生成一个字典template_dict,统计t里的字符及出现次数
-
生成一个字典window_dict,该字典的key和template_dict的key是一样的,value通过后续遍历s字符串的时候再统计
-
定义一个函数isContains,该函数的目的是看window_dict里每个key的值是不是≥template_dict里每个key的值,若不是就直接返回False
-
最后就是滑动窗口的老步骤,end指针在s字符串中逐步往右遍历,若遍历到的字符在字典template_dict里(即是t字符串里的字符),则对应value加1;直到window_dict里每个key的值都≥template_dict里每个key的值;再移动左指针start
class Solution:
def minWindow(self, s: str, t: str) -> str:
from collections import Counter
template_dict = Counter(t) # 统计一个字符串里的字符及出现次数
# 初始化一个窗口,key和template_dict的key是一样的
window_dict = {}
for each_key in template_dict:
if each_key not in window_dict:
window_dict[each_key] = 0
def isContains(cur_dict, tmp_dict):
for each_key in tmp_dict:
if cur_dict[each_key] < tmp_dict[each_key]:
return False
return True
start = 0
min_len = float('inf')
res = ""
for end in range(len(s)):
if s[end] in template_dict:
window_dict[s[end]] += 1
# 判断window_dict是否包含template_dict
while isContains(window_dict, template_dict):
if min_len > end-start+1:
min_len = end-start+1
res=s[start:end+1]
if s[start] in window_dict:
window_dict[s[start]]-=1
start+=1
return res
59. 螺旋矩阵 II
看这个视频才看懂思路:每日一题——leecode59( 螺旋矩阵 II)讲解最详细零基础——python_哔哩哔哩_bilibili,对应文章:每日一题——leecode59( 螺旋矩阵 II)
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
matrix = [[0]*n for _ in range(n)] # 构造正方形矩阵
right,left,up,down = n-1,0,0,n-1 # 定义四个指针
number = 1 # 要填充的数,从1开始填充
while right>left and down>up:
for x in range(left,right):
matrix[up][x] = number
number += 1
for y in range(up,down):
matrix[y][right] = number
number += 1
for x in range(right,left,-1):
matrix[down][x] = number
number += 1
for y in range(down,up,-1):
matrix[y][left] = number
number += 1
# 缩圈
up += 1
right -= 1
left += 1
down -= 1
if n%2 != 0: # 奇数的话,中间的数没填充上,是0
matrix[n//2][n//2] = n**2 # 若是3的话,中间的位置是(1,1)
return matrix
相关题目推荐
54. 螺旋矩阵
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
m = len(matrix)
n = len(matrix[0])
left, right, up, down = 0, n-1, 0, m-1
nums = []
while right>=left and down>=up:
for j in range(left,right+1): # left到right
nums.append(matrix[up][j])
for i in range(up+1,down+1): # up+1到down
nums.append(matrix[i][right])
if left<right and up<down:
for j in range(right-1,left,-1): # right-1到left+1
nums.append(matrix[down][j])
for i in range(down,up,-1): # down到up+1
nums.append(matrix[i][left])
left+=1
right-=1
up+=1
down-=1
return nums
剑指 Offer 29. 顺时针打印矩阵
跟上题54基本一模一样,有个小差别:
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix:
return []
m = len(matrix)
n = len(matrix[0])
left, right, up, down = 0, n-1, 0, m-1
nums = []
while right>=left and down>=up:
for j in range(left,right+1):
nums.append(matrix[up][j])
for i in range(up+1,down+1):
nums.append(matrix[i][right])
if left<right and up<down:
for j in range(right-1,left,-1):
nums.append(matrix[down][j])
for i in range(down,up,-1):
nums.append(matrix[i][left])
left+=1
right-=1
up+=1
down-=1
return nums