1 数组
图、知识点、大部分题解源代码随想录
704 二分查找
二分查找的前提:有序数组、无重复元素
二分查找注意事项:
1、中间值mid取法
应该用 mid = l + (r-l)/ 2 ,而不是 mid = (l + r) / 2,因为如果 l 和 r都很大,那么(l + r)将会溢出整数范围。
2、递归结束条件
左边界>有边界
3、边界更新
left=mid+1或right=mid-1
(第二种递归结束和边界更新:while (left >= right)时结束边界,left=mid或right=mid)
我的解法:
时间复杂度 O(logN): 其中 N 为数组 nums 长度。二分查找使用对数级别时间。
空间复杂度 O(1) : 变量 i , j 使用常数大小空间。
class Solution:
def search(self, nums: List[int], target: int) -> int:
right = len(nums) - 1
left = 0
self.nums = nums
self.target = target
return self.Recursion(left, right)
def Recursion(self, left, right):
mid = int(left + (right-left)/2)
# 注意递归结束条件
if left > right:
return -1
else:
if self.nums[mid] > self.target:
right = mid - 1
# 一定要注意递归要return啊!!!!!
return self.Recursion(left, right)
elif self.nums[mid] < self.target:
left = mid + 1
return self.Recursion(left, right)
elif self.nums[mid] == self.target:
return mid
☆☆☆题解1:更简洁
原来一个while就可以递归quq...
class Solution:
def search(self, nums: List[int], target: int) -> int:
i, j = 0, len(nums) - 1
while i <= j:
# //整数除法,向下取整
m = (i + j) // 2
if nums[m] < target: i = m + 1
elif nums[m] > target: j = m - 1
else: return m
return -1
35 搜索插入位置
☆☆☆我的解法:二分查找
关键在于不存在时最后的return,最后一轮循环时一定存在left=right=mid,然后分成两种情况讨论,最后的结果都是刚好在left上
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 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 left
69 x的平方根
☆☆☆我的解法:二分查找
分析思路同35
class Solution:
def mySqrt(self, x: int) -> int:
left, right = 1, x
while left <= right:
mid = left + (right - left) // 2
mid2 = mid * mid
if mid2 < x:
left = mid + 1
elif mid2 > x:
right = mid - 1
else:
return mid
return right
367 有效的完全平方数
☆☆☆我的解法:二分查找
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left, right = 1, num
while left <= right:
mid = left + (right - left) // 2
mid2 = mid * mid
if mid2 < num:
left = mid + 1
elif mid2 > num:
right = mid - 1
else:
return True
return False
34 在排序数组中查找元素的第一个和最后一个位置
☆☆☆我的解法:二分查找
# 1、首先,在 nums 数组中二分查找 target;
# 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
# 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left, right = 0, len(nums) - 1
index = -1
while left <= right:
mid = left + (right - left) // 2
if target < nums[mid]:
right = mid - 1
elif target > nums[mid]:
left = mid + 1
else:
index = mid
break
if index == -1:
return [-1, -1]
i, j = index, index
# 当i-1没有超出边界,且i-1也符合条件时
while i - 1 >= 0 and nums[i-1] == target:
i -= 1
while j + 1 <= len(nums) - 1 and nums[j+1] == target:
j += 1
return [i, j]
题解:两个二分查找,分别找左右边界
考虑 target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target 的位置」(记为 leftIdx)和「第一个大于 target 的位置减一」(记为 rightIdx)。
寻找target在数组里的左右边界,有如下三种情况:
- 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
- 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
- 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
# 解法4
# 1、首先,在 nums 数组中二分查找得到第一个大于等于 target的下标leftBorder;
# 2、在 nums 数组中二分查找得到第一个大于等于 target+1的下标, 减1则得到rightBorder;
# 3、如果开始位置在数组的右边或者不存在target,则返回[-1, -1] 。否则返回[leftBorder, rightBorder]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) //2
# 这里是>=说明,左边可能还会有target(最差情况也是mid表示的接下来的右边界至少一个相等的),左边还有,所以往左缩小,找左边第一个
if nums[middle] >= target:
right = middle - 1
else: # nums[middle] < target说明左边没有target,左边界也在右边,所以left右移
left = middle + 1
return left # 若存在target,则返回第一个等于target的值
leftBorder = binarySearch(nums, target) # 搜索左边界
rightBorder = binarySearch(nums, target+1) -1 # 搜索右边界
if leftBorder == len(nums) or nums[leftBorder]!= target: # 情况一和情况二
return [-1, -1]
return [leftBorder, rightBorder]
27 移除元素
我的解法:双向指针
思路是双指针指向两边,当左边的元素为目标值,而右边不是目标值时,交换两个元素,当左右下标相遇说明遍历了一遍数组。
需要单独判断空数组是因为最后return时的判断条件nums[left]超出边界了
时间复杂度:O(n),其中 n 为序列的长度。我们只需要遍历该序列至多一次。
空间复杂度:O(1)。我们只需要常数的空间保存若干变量。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
if len(nums)==0:
return 0
left, right = 0, len(nums)-1
while left < right:
if nums[left] == val:
if nums[right] != val:
# 交换两个元素
nums[left], nums[right] = nums[right], nums[left]
# python没有自增自减运算符!!!
left = left + 1
right = right - 1
else :
right = right - 1
else:
left = left + 1
return left+1 if nums[left]!=val else left
☆☆☆题解:双指针法(快慢指针法)
也是双指针但是都是从前往后移动:
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
让 a 一直往后移动,相当于 nums[a] 从数组第一个数遍历到最后一个数。
当且仅当我们发现 nums[a] != val 的时候,我们把这个数拷贝到 b 指向的位置,默认 b 是从 0 开始的,然后 b += 1 指向下一个位置。
时间复杂度:O(n),其中 n 为序列的长度。我们只需要遍历该序列至多两次。
空间复杂度:O(1)。我们只需要常数的空间保存若干变量。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 快慢指针
fast = 0 # 快指针
slow = 0 # 慢指针
size = len(nums)
while fast < size: # 不加等于是因为,a = size 时,nums[a] 会越界
# slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
☆☆☆题解2:更好的双向指针法
无需单独判断空数组
# 相向双指针法
# 时间复杂度 O(n)
# 空间复杂度 O(1)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
left, right = 0, n - 1
while left <= right:
while left <= right and nums[left] != val:
left += 1
while left <= right and nums[right] == val:
right -= 1
if left < right:
nums[left] = nums[right]
left += 1
right -= 1
return left
26 删除排序数组中的重复项
我的解法:pop
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
i = 0
while i < len(nums):
if i+1 < len(nums) and nums[i+1] == nums[i]:
nums.pop(i+1)
elif i+1 < len(nums) and nums[i+1] != nums[i]:
i += 1
else:
return len(nums)
return 0
☆☆☆题解:双指针
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
n = len(nums)
# 从1下标开始,因为如果数组只有一个元素,它自己肯定不会重复
fast = slow = 1
while fast < n:
# 注意是和fast-1比
if nums[fast] != nums[fast - 1]:
nums[slow] = nums[fast]
slow += 1
fast += 1
# 返回slow,slow是下标,下标-1+1表示元素个数,即slow
return slow
283 移动零
☆☆☆我的解法:双指针(不简洁danwon方法)
图源:
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
i, j = 0, 0
while j < len(nums):
# 找到最左边的0
if i + 1 < len(nums) and nums[i] != 0:
i += 1
# j一定在i的右侧
j = i + 1
continue
# 找到0右边的非零数,交换
if nums[j] != 0:
nums[i], nums[j] = nums[j], nums[i]
j += 1
i += 1
else:
j += 1
844 比较含退格的字符串
我的解法:求最终s和t然后比较
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
i, j = 0, 0
s = list(s)
t = list(t)
while i < len(s):
if s[i] == "#" and i>0:
s.pop(i)
s.pop(i-1)
i = i - 1
elif s[i] == "#" and i==0:
s.pop(i)
else:
i += 1
while j < len(t):
if t[j] == "#" and j>0:
t.pop(j)
t.pop(j-1)
j = j - 1
elif t[j] == "#" and j==0:
t.pop(j)
else:
j += 1
return True if s == t else False
☆☆☆题解:双指针从后往前遍历
class Solution:
def backspaceCompare(self, S: str, T: str) -> bool:
i, j = len(S) - 1, len(T) - 1
skipS = skipT = 0
while i >= 0 or j >= 0:
while i >= 0:
if S[i] == "#":
skipS += 1
i -= 1
elif skipS > 0:# 不是退格且skip不空,则当前元素应该被跳过
skipS -= 1
i -= 1
else:# 不是空格,且skip空,当前元素应该保留去做对比
break
while j >= 0:# T同理
if T[j] == "#":
skipT += 1
j -= 1
elif skipT > 0:
skipT -= 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
作者:御三五 🥇
链接:https://leetcode.cn/problems/backspace-string-compare/solutions/683776/shuang-zhi-zhen-bi-jiao-han-tui-ge-de-zi-8fn8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
977 有序数组的平方
我的解法:暴力排序
先平方后排序,时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度
时间复杂度:O(nlogn),其中 n 是数组 nums 的长度。
空间复杂度:O(logn)。除了存储答案的数组以外,我们需要 O(logn) 的栈空间进行排序。
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
for i in range(len(nums)):
nums[i] = nums[i] ** 2
nums.sort()
return nums
☆☆☆题解:双指针——归并排序
数组其实是有序的, 只不过负数平方之后可能成为最大数了。
那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
如果A[i] * A[i] < A[j] * A[j]
那么result[k--] = A[j] * A[j];
。
如果A[i] * A[i] >= A[j] * A[j]
那么result[k--] = A[i] * A[i];
。
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。除了存储答案的数组以外,我们只需要维护常量空间。
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
n = len(nums)
# 需要提前定义列表,用来存放结果
ans = [0] * n
i, j = 0, n - 1
# for循环:n-1,n-2,n-3...0,即从n-1到-1的左闭右开,即n-1到0的左闭右闭区间
for p in range(n - 1, -1, -1):
x = nums[i] * nums[i]
y = nums[j] * nums[j]
if x > y: # 更大的数放右边
ans[p] = x
# x是统计负数的,指针从左向右走
i += 1
else:
ans[p] = y
# x是统计正数的,指针从右向左走
j -= 1
return ans
题解2:暴力排序+列表推导法
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
return sorted(x*x for x in nums)
209 长度最小的子数组
我的解法:暴力法
两个指针,一个记录子数组首元素下标,另一个记录子数组尾元素下标,两个for循环
时间复杂度:O(n2),其中 n 是数组的长度。需要遍历每个下标作为子数组的开始下标,对于每个开始下标,需要遍历其后面的下标得到长度最小的子数组。
空间复杂度:O(1)。
未通过,18测试用例会超时...
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
Min = 1000
Sum = 0
i,j = 0,0
for i in range(len(nums)):
for j in range(i, len(nums)):
Sum += nums[j]
if Sum >= target
if j-i+1 < Min:
Min = j-i+1
break
else:
continue
Sum = 0
return Min if Min<1000 else 0
☆☆☆题解:滑动窗口
时间复杂度:O(n),其中 n 是数组的长度。指针 start 和 end 最多各移动 n 次。
空间复杂度:O(1)。
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = len(nums)
left = 0
right = 0
min_len = float('inf')
cur_sum = 0 #当前的累加值
while right < l:# 遍历表示结束的指针
cur_sum += nums[right]
while cur_sum >= s: # 当前累加值大于目标值
min_len = min(min_len, right - left + 1)
cur_sum -= nums[left]# 窗口累加值缩小
left += 1# 窗口缩小,缩小后继续判断cur_sum >= s,直到不满足为止再前移结束指针
right += 1# 前移结束指针
return min_len if min_len != float('inf') else 0
滑动窗口:
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了),然后判断是否满足窗口,若满足,继续缩小(起始位置向前移动),若不满足,结束位置前移(即窗口扩大)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
为什么时间复杂度是O(n)。
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
题解2:前缀+二分查找(不如滑动窗口)
方法一的时间复杂度是 O(n2),因为在确定每个子数组的开始下标后,找到长度最小的子数组需要 O(n)的时间。如果使用二分查找,则可以将时间优化到 O(logn)。
为了使用二分查找,需要额外创建一个数组 sums 用于存储数组 nums 的前缀和,其中 sums[i] 表示从 nums[0]到 nums[i−1] 的元素和。得到前缀和之后,对于每个开始下标 i,可通过二分查找得到大于或等于 i 的最小下标 bound\,使得 sums[bound]−sums[i−1]≥s,并更新子数组的最小长度(此时子数组的长度是 bound−(i−1))。
因为这道题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性。如果题目没有说明数组中每个元素都为正,这里就不能使用二分来查找这个位置了。
现成的库和函数来为我们实现这里二分查找大于等于某个数的第一个位置的功能,Python 中的 bisect.bisect_left
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
if not nums:
return 0
n = len(nums)
ans = n + 1
sums = [0]
for i in range(n):
sums.append(sums[-1] + nums[i])
for i in range(1, n + 1):
target = s + sums[i - 1]# s是目标值,target是满足 目标值 + i-1前缀和 的bound前缀和
bound = bisect.bisect_left(sums, target)
if bound != len(sums):# 下标没有len(nums)
ans = min(ans, bound - (i - 1))
return 0 if ans == n + 1 else ans
904 水果成篮
☆☆☆我的解法:滑动窗口
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
length = 0
left, right = 0, 0
kind = dict()
while right < len(fruits):
kind[fruits[right]] = kind.get(fruits[right],0) + 1
while len(kind.keys()) > 2:
if kind[fruits[left]] > 1:
kind[fruits[left]] -= 1
else:
kind.pop(fruits[left])
left += 1
if right-left+1>length:
length = right-left+1
right += 1
return length
79 最小覆盖子串
我的解法:滑动窗口
class Solution:
def minWindow(self, s: str, t: str) -> str:
left, right = 0, 0
son = dict()
ton = list(t)
MIN = s+'A'
td = dict()
for i in ton:
td[i] = td.get(i,0)+1
while right < len(s):
son[s[right]] = son.get(s[right],0) + 1
# 左一定要小于右 并且(左不在t中 或者 左在t中但数量超了)
while left<right and ((s[left] not in ton) or son[s[left]]>td[s[left]]):
if son[s[left]] > 1:
son[s[left]] -= 1
else:
son.pop(s[left])
left += 1
# 长度小 并且 t中的每个都有
if right - left +1 < len(MIN) and all(son.get(i,0)>=td[i] for i in ton):
MIN = "".join(s[left:right+1])
right +=1
return MIN if len(MIN)<=len(s) else ""
题解:滑动窗口
class Solution:
def minWindow(self, s: str, t: str) -> str:
ans_left, ans_right = -1, len(s)
left = 0
# counter计数器,是dict的子类
cnt_s = Counter() # s 子串字母的出现次数
cnt_t = Counter(t) # t 中字母的出现次数
for right, c in enumerate(s): # 移动子串右端点
cnt_s[c] += 1 # 右端点字母移入子串
# 这里居然能直接比较!!!(python 3.10 以上才行)
# 考虑满足的时候就滑出直到不满足 而不是滑到满足
while cnt_s >= cnt_t: # 涵盖
if right - left < ans_right - ans_left: # 找到更短的子串
ans_left, ans_right = left, right # 记录此时的左右端点
cnt_s[s[left]] -= 1 # 左端点字母移出子串
left += 1 # 移动子串左端点
return "" if ans_left < 0 else s[ans_left: ans_right + 1]
作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimum-window-substring/solutions/2713911/liang-chong-fang-fa-cong-o52mn-dao-omnfu-3ezz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
☆☆☆题解2:滑动窗口(优化)
看代码就懂了,less指的是t的元素,在s的子串中数量不够的有几个,比如s的子串是abcdef,t是aaack,那么less=2,因为a和k数量不够。
class Solution:
def minWindow(self, s: str, t: str) -> str:
ans_left, ans_right = -1, len(s)
left = 0
cnt_s = Counter() # s 子串字母的出现次数
cnt_t = Counter(t) # t 中字母的出现次数
less = len(cnt_t) # 有 less 种字母的出现次数 < t 中的字母出现次数
for right, c in enumerate(s): # 移动子串右端点
cnt_s[c] += 1 # 右端点字母移入子串
if cnt_s[c] == cnt_t[c]:
less -= 1 # c 的出现次数从 < 变成 >=
while less == 0: # 涵盖:所有字母的出现次数都是 >=
if right - left < ans_right - ans_left: # 找到更短的子串
ans_left, ans_right = left, right # 记录此时的左右端点
x = s[left] # 左端点字母
if cnt_s[x] == cnt_t[x]:
less += 1 # x 的出现次数从 >= 变成 <(下一行代码执行后)
cnt_s[x] -= 1 # 左端点字母移出子串
left += 1 # 移动子串左端点
return "" if ans_left < 0 else s[ans_left: ans_right + 1]
作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimum-window-substring/solutions/2713911/liang-chong-fang-fa-cong-o52mn-dao-omnfu-3ezz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
59 螺旋矩阵II
我的解法:找规律(左开右闭)
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
# 创建二维空列表
l1 = [[] for i in range(n)]
for i in range(n):
l1[i] = [None] * n
i, j, k = 0, 0, 1
while k <= n ** 2:
# →
while i + j <= n - 1 :
l1[i][j] = k
k += 1
j += 1
j -= 1
i += 1
# ↓
while i <= j :
l1[i][j] = k
k += 1
i += 1
i -= 1
j -= 1
# ←
while i + j >= n - 1 :
l1[i][j] = k
k += 1
j -= 1
j += 1
i -= 1
# ↑
while i > j :
l1[i][j] = k
k += 1
i -= 1
i += 1
j += 1
return l1
☆☆☆题解:左闭右开原则
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
这也是坚持了每条边左闭右开的原则。
- 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
- 空间复杂度 O(1)
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
nums = [[0] * n for _ in range(n)]
startx, starty = 0, 0 # 起始点
loop, mid = n // 2, n // 2 # 迭代次数、n为奇数时,矩阵的中心点
count = 1 # 计数
for offset in range(1, loop + 1) : # 每循环一层偏移量加1,偏移量从1开始
for i in range(starty, n - offset) : # 从左至右,左闭右开
nums[startx][i] = count
count += 1
for i in range(startx, n - offset) : # 从上至下
nums[i][n - offset] = count
count += 1
for i in range(n - offset, starty, -1) : # 从右至左
nums[n - offset][i] = count
count += 1
for i in range(n - offset, startx, -1) : # 从下至上
nums[i][starty] = count
count += 1
startx += 1 # 更新起始点
starty += 1
if n % 2 != 0 : # n为奇数时,填充中心点
nums[mid][mid] = count
return nums
题解2:定义四个边界
好像和我的思路差不多,左开右闭,for循环比我写的while好...
class Solution(object):
def generateMatrix(self, n):
if n <= 0:
return []
# 初始化 n x n 矩阵
matrix = [[0]*n for _ in range(n)]
# 初始化边界和起始值
top, bottom, left, right = 0, n-1, 0, n-1
num = 1
while top <= bottom and left <= right:
# 从左到右填充上边界
for i in range(left, right + 1):
matrix[top][i] = num
num += 1
top += 1
# 从上到下填充右边界
for i in range(top, bottom + 1):
matrix[i][right] = num
num += 1
right -= 1
# 从右到左填充下边界
for i in range(right, left - 1, -1):
matrix[bottom][i] = num
num += 1
bottom -= 1
# 从下到上填充左边界
for i in range(bottom, top - 1, -1):
matrix[i][left] = num
num += 1
left += 1
return matrix
54 螺旋矩阵
我的解法:左闭右开
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
res = []
row, column = len(matrix), len(matrix[0])
starx, stary = 0, 0
count, offset = 0, 0
while count < row* column-1:
offset += 1
for i in range(stary, column-offset):
res.append(matrix[starx][i])
count += 1
if count == row * column:
break
for i in range(starx, row-offset):
res.append(matrix[i][column-offset])
count += 1
if count == row * column:
break
for i in range(column-offset, stary, -1):
res.append(matrix[row-offset][i])
count += 1
if count == row * column:
break
for i in range(row-offset, starx, -1):
res.append(matrix[i][stary])
count += 1
if count == row * column:
break
starx += 1
stary += 1
if column == row and row %2!=0:
res.append(matrix[row//2][row//2])
# while len(res)>row* column:
# res.pop()
return res
☆☆☆题解:左开右闭(除第一行),比较边界相遇情况
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix: return []
l, r, t, b, res = 0, len(matrix[0]) - 1, 0, len(matrix) - 1, []
while True:
for i in range(l, r + 1): res.append(matrix[t][i]) # left to right
t += 1
if t > b: break
for i in range(t, b + 1): res.append(matrix[i][r]) # top to bottom
r -= 1
if l > r: break
for i in range(r, l - 1, -1): res.append(matrix[b][i]) # right to left
b -= 1
if t > b: break
for i in range(b, t - 1, -1): res.append(matrix[i][l]) # bottom to top
l += 1
if l > r: break
return res
作者:Krahets
链接:https://leetcode.cn/problems/spiral-matrix/solutions/2362055/54-luo-xuan-ju-zhen-mo-ni-qing-xi-tu-jie-juvi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
LCR 146 螺旋遍历二维数组 (和54一模一样)
我的解法:
class Solution:
def spiralArray(self, array: List[List[int]]) -> List[int]:
if not array:
return []
left, right = 0, len(array[0])-1
top, bottom = 0, len(array)-1
res = []
while True:
for i in range(left, right+1):
res.append(array[top][i])
top += 1
if top > bottom:
break
for i in range(top,bottom+1):
res.append(array[i][right])
right -= 1
if left > right:
break
for i in range(right, left-1,-1):
res.append(array[bottom][i])
bottom -= 1
if top > bottom:
break
for i in range(bottom,top-1,-1):
res.append(array[i][left])
left += 1
if left > right:
break
return res
2 链表
tips:
- 许多操作需要单独处理头结点,或设置虚拟头结点统一解决
知识点:
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上。
- 单链表:
单链表中的指针域只能指向节点的下一个节点。
- 双链表:
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
- 循环链表
链表首尾相连。循环链表可以用来解决约瑟夫环问题。
- 删除节点
只要将C节点的next指针 指向E节点就可以了。
Python有自己的内存回收机制,不用自己手动释放D。
要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。
- 添加节点
- 与数组对比
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
- python定义链表:
用类
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
203 移除链表元素
我的题解:单独处理头结点
写的太复杂了
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
while head:
if head.val == val:
head = head.next
else:
break
n_node = head
if n_node:
while n_node.next:
if n_node.next.val != val:
n_node = n_node.next
else:
n_node.next = n_node.next.next
return head
else:
return None
☆☆☆题解:虚拟头结点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 创建虚拟头部节点以简化删除过程
dummy_head = ListNode(next = head)
# 遍历列表并删除值为val的节点
current = dummy_head
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy_head.next
707 设计链表
我的题解:单链表,创建了虚拟头结点
注意查询、添加、删除的题目要求,需要比较index和链表长度
class ListNode:
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
self.len = 0
def get(self, index: int) -> int:
current = self.dummy_head
if index < self.len:
for i in range(0, index):
current = current.next
return current.next.val
else:
return -1
def addAtHead(self, val: int) -> None:
new_head = ListNode(val, self.dummy_head.next)
self.dummy_head.next = new_head
self.len += 1
def addAtTail(self, val: int) -> None:
current = self.dummy_head
while current.next:
current = current.next
new_tail = ListNode(val, None)
current.next = new_tail
self.len += 1
def addAtIndex(self, index: int, val: int) -> None:
current = self.dummy_head
if index <= self.len:
for i in range(0, index):
current = current.next
new_node = ListNode(val, current.next)
current.next = new_node
self.len += 1
def deleteAtIndex(self, index: int) -> None:
current = self.dummy_head
if index < self.len:
for i in range(0, index):
current = current.next
current.next = current.next.next
self.len -= 1
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
☆☆☆题解:单链表
这个对index的判断更加严谨,创建新节点直接赋值更方便
(版本一)单链表法
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
current = self.dummy_head.next
for i in range(index):
current = current.next
return current.val
def addAtHead(self, val: int) -> None:
self.dummy_head.next = ListNode(val, self.dummy_head.next)
self.size += 1
def addAtTail(self, val: int) -> None:
current = self.dummy_head
while current.next:
current = current.next
current.next = ListNode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val, current.next)
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -= 1
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
☆☆☆题解2:双链表
class ListNode:
def __init__(self, val=0, prev=None, next=None):
self.val = val
self.prev = prev
self.next = next
class MyLinkedList:
def __init__(self):
# 要同时记住头和尾巴是哪个节点
self.head = None
self.tail = None
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
# 判断离头近还是离尾近,节约耗时
if index < self.size // 2:
current = self.head
for i in range(index):
current = current.next
else:
current = self.tail
for i in range(self.size - index - 1):
current = current.prev
return current.val
def addAtHead(self, val: int) -> None:
# 创建自身指针
new_node = ListNode(val, None, self.head)
if self.head:# 已经有头结点
# 更新后节点的指针
self.head.prev = new_node
else:# 没有头结点,新加入的既是头也是尾
self.tail = new_node
# 更新头结点
self.head = new_node
self.size += 1
def addAtTail(self, val: int) -> None:
# 创建自身指针
new_node = ListNode(val, self.tail, None)
if self.tail:
# 更新前节点的指针
self.tail.next = new_node
else:
self.head = new_node
# 更新尾巴结点
self.tail = new_node
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
if index == 0:
self.addAtHead(val)
elif index == self.size:
self.addAtTail(val)
else:
if index < self.size // 2:
current = self.head
for i in range(index - 1):
current = current.next
else:
current = self.tail
for i in range(self.size - index):
current = current.prev
# 创建自身指针
new_node = ListNode(val, current, current.next)
# 更新前后节点的指针
current.next.prev = new_node
current.next = new_node
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
if index == 0:
# 删除当前节点
self.head = self.head.next
if self.head:
# 后面还有其他节点,更新后节点指针
self.head.prev = None
else:
self.tail = None
elif index == self.size - 1:
# 删除当前节点
self.tail = self.tail.prev
if self.tail:
# 前面还有其他节点,更新前节点指针
self.tail.next = None
else:
self.head = None
else:
if index < self.size // 2:
current = self.head
for i in range(index):
current = current.next
else:
current = self.tail
for i in range(self.size - index - 1):
current = current.prev
# 前后节点相连
current.prev.next = current.next
current.next.prev = current.prev
self.size -= 1
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
206 反转链表
我的题解:双指针迭代
从前往后反转,head单独处理(看题解1吧,我这个写的好多此一举)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
current = head
pre = None
if head:
while current.next:
ne = current.next
current.next = pre
# 顺移pre和current
pre = current
current = ne
current.next = pre
return current
else:
return head
☆☆☆题解1:双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while cur:
temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur.next = pre #反转
#更新pre、cur指针
pre = cur
cur = temp
return pre
题解2:递归
从前往后翻,同双指针法
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
return self.reverse(head, None)
def reverse(self, cur: ListNode, pre: ListNode) -> ListNode:
if cur == None:
return pre
temp = cur.next
cur.next = pre
return self.reverse(temp, cur)
24 两两交换链表中的节点
我的题解:
注意:每次交换有三个指针需要改变,第一个节点的next、第二个节点的next、前面节点的next
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head = ListNode(next = head)
current = head
i = 0
while current:
if current.next:
current = self.changeTwo(current, current.next)
if i == 0:
# 更新头结点
dummy_head.next = current
i = 1
else:
# 更新前节点的指针
pre.next = current
pre = current.next
current = current.next.next
else:
break
return dummy_head.next
def changeTwo(self, fir, sec):
fir.next = sec.next
sec.next = fir
return sec
☆☆☆题解:更简洁
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy_head = ListNode(next=head)
current = dummy_head
# 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
while current.next and current.next.next:
temp = current.next # 防止节点修改
temp1 = current.next.next.next
# crr指向2
current.next = current.next.next
# 2指向1
current.next.next = temp
# 1指向3
temp.next = temp1
# curr顺移
current = current.next.next
return dummy_head.next
题解2:递归!!!!
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None:
return head
# 待翻转的两个node分别是pre和cur
pre = head
cur = head.next
next = head.next.next
cur.next = pre # 交换
pre.next = self.swapPairs(next) # 将以next为head的后续链表两两交换
# 返回的是交换后的位于前面的节点
return cur
19 删除链表的倒数第N个节点
我的题解:笨方法
先遍历计算链表长度,再遍历找到目标节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
virtual_head = ListNode(next = head)
current = head
num = 0
while current:
num += 1
current = current.next
if n <= num:
current = virtual_head
for i in range(num - n):
current = current.next
current.next = current.next.next
return virtual_head.next
☆☆☆题解:双指针
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
注意:先让fast先走n步,再让slow出发,这样保证fast走到结尾时,slow刚好位于要删掉的n处,但删除操作需要用到前一个节点,因此slow应该再晚一步出发,即fast先走n+1步
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
# 创建一个虚拟节点,并将其下一个指针设置为链表的头部
dummy_head = ListNode(0, head)
# 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
slow = fast = dummy_head
# 快指针比慢指针快 n+1 步
for i in range(n+1):
fast = fast.next
# 移动两个指针,直到快速指针到达链表的末尾
while fast:
slow = slow.next
fast = fast.next
# 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
slow.next = slow.next.next
return dummy_head.next
☆☆☆题解2:栈
在遍历链表的同时将所有节点依次入栈。根据栈「先进后出」的原则,我们弹出栈的第 nnn 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。这样一来,删除操作就变得十分方便了。
用list实现栈!!!
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0, head)
stack = list()
cur = dummy
while cur:
stack.append(cur)
cur = cur.next
for i in range(n):# 弹出n个元素
stack.pop()
prev = stack[-1]# 最后一个就是第n-1个元素
prev.next = prev.next.next
return dummy.next
面试题 02.07. 链表相交
我的题解:
分别统计两个链表长度,然后将长的推进,再同时向前比较节点是否相等,注意是比较节点,而不是节点的值和指针
(注释的是哈希表方法,43用例超时了)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# hash_Table = dict()
# currentA = headA
# i = 0
# while currentA:
# hash_Table[i] = currentA
# i += 1
# currentA = currentA.next
# currentB = headB
# while currentB:
# if currentB in hash_Table.values():
# return currentB
# currentB = currentB.next
# return None
nA, nB = 0, 0
currentA = headA
while currentA:
nA += 1
currentA = currentA.next
currentB = headB
while currentB:
nB += 1
currentB = currentB.next
if nA >= nB:
currentLang = headA
currentShort = headB
n = nA -nB
else:
currentLang = headB
currentShort = headA
n = nB -nA
for i in range(n):
currentLang = currentLang.next
while currentShort:
if currentLang == currentShort:
return currentLang
currentLang = currentLang.next
currentShort = currentShort.next
return None
☆☆☆题解:代码复用精简
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
dis = self.getLength(headA) - self.getLength(headB)
# 通过移动较长的链表,使两链表长度相等
if dis > 0:
headA = self.moveForward(headA, dis)
else:
headB = self.moveForward(headB, abs(dis))
# 将两个头向前移动,直到它们相交
while headA and headB:
if headA == headB:
return headA
headA = headA.next
headB = headB.next
return None
def getLength(self, head: ListNode) -> int:
length = 0
while head:
length += 1
head = head.next
return length
def moveForward(self, head: ListNode, steps: int) -> ListNode:
while steps > 0:
head = head.next
steps -= 1
return head
题解2:等比例法,没懂
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 处理边缘情况——任一个是空链表
if not headA or not headB:
return None
# 在每个链表的头部初始化两个指针
pointerA = headA
pointerB = headB
# 遍历两个链表直到指针相交
while pointerA != pointerB:
# 将指针向前移动一个节点
# ?????
pointerA = pointerA.next if pointerA else headB
pointerB = pointerB.next if pointerB else headA
# 如果相交,指针将位于交点节点,如果没有交点,值为None
return pointerA
142 环形链表ii
我的题解:字典哈希表
慢的要死
时间复杂度:O(N),其中 N 为链表中节点的数目。我们恰好需要访问链表中的每一个节点。
空间复杂度:O(N),其中 N 为链表中节点的数目。我们需要将链表中的每个节点都保存在哈希表当中。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
hash_Table = dict()
current = head
i = 0
while current:
if current in hash_Table.values():
return current
hash_Table[i] = current
i += 1
current = current.next
return