《算法通关之路》学习笔记,记录一下自己的刷题过程,详细的内容请大家购买作者的书籍查阅。
力扣第189题
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
'''
方法一:复制相同的数组
时间复杂度:O(n)
空间复杂度:O(n)
'''
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
copy = nums.copy()
n = len(nums)
for i in range(n):
nums[(k+i)%n] = copy[i]
nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法二:时间换空间(超时)
时间复杂度:O(nk)
空间复杂度:O(1)
'''
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
n = len(nums)
t = None
offset = n - k % n # 右移变左移
if offset == 0:
return
while offset:
t = nums[0]
offset -= 1
for i in range(n-1):
nums[i] = nums[i+1]
nums[n-1] = t
nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法三:空间换时间(不符合题意)
时间复杂度:O(n)
空间复杂度:O(n)
'''
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
n = len(nums)
offset = k % n
nums = nums + nums.copy()
return nums[n - offset : n * 2 - offset]
nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法四:利用python迭代器
时间复杂度:O(k)
空间复杂度:O(1)
'''
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
for i in range(0, -k, -1):
nums.insert(0,nums.pop())
nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法五:三次翻转法
时间复杂度:O(k)
空间复杂度:O(1)
'''
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
def reverse(list: list[int], start: int, end: int) -> None:
while start < end:
t = list[start]
list[start] = list[end]
list[end] = t
start += 1
end -= 1
n = len(nums)
offset = k % n
if offset == 0:
return
reverse(nums, 0, n - offset - 1)
reverse(nums, n - offset, n - 1)
reverse(nums, 0, n - 1)
nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
力扣第61题
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
'''
方法一:右移变左移
时间复杂度:O(n)
空间复杂度:O(1)
'''
from typing import Optional
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head or k == 0 or not head.next:
return head
p1 = head
res = None
n = 1
while p1 and p1.next:
p1 = p1.next
n += 1
cur = 1 # 通过cur计数来寻找断点
p2 = head
while cur < n - k % n:
p2 = p2.next
cur += 1
p1.next = head
res = p2.next
p2.next = None
return res
力扣第72题
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
'''
方法一:记忆化递归
空间复杂度:O(mn)
'''
class Solution:
def minDistance(self, s1: str, s2: str) -> int:
def find_min(s1, i, s2, j, memo):
if i == -1:
return j + 1
if j == -1:
return i + 1
if memo[i][j] != -1:
return memo[i][j]
if s1[i] == s2[j]:
memo[i][j] = find_min(s1, i - 1, s2, j - 1, memo)
else:
memo[i][j] = min(
find_min(s1, i, s2, j - 1, memo) + 1, # 插入
find_min(s1, i - 1, s2, j, memo) + 1, # 删除
find_min(s1, i - 1, s2, j - 1, memo) + 1 # 替换
)
return memo[i][j]
m, n = len(s1), len(s2)
memo = [[-1] * n for _ in range(m)]
res = find_min(s1, m - 1, s2, n - 1, memo)
return res
word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3
'''
方法二:动态规划
时间复杂度:O(mn)
空间复杂度:O(mn)
'''
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
# dp[i][j]表示字符串word[:i]和word[:j]的最小编辑距离
dp = [[0 for j in range(n + 1)] for i in range(m + 1)]
for i in range(1, m + 1):
dp[i][0] = i
for j in range(1, n + 1):
dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1
return dp[m][n]
word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3
'''
方法三:动态规划(空间优化)
时间复杂度:O(mn)
空间复杂度:O(n)
'''
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
pre, cur = [0] * (n + 1), [0] * (n + 1)
for i in range(1, n + 1):
pre[i] = i
for i in range(1, m + 1):
cur[0] = i
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
cur[j] = pre[j - 1]
else:
cur[j] = min(pre[j], pre[j - 1], cur[j - 1]) + 1
pre = cur.copy()
return pre[n] # 解决没有进入循环的情况
word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3
'''
方法四:动态规划(继续优化空间)
时间复杂度:O(mn)
空间复杂度:O(n)
'''
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
cur = [0] * (n + 1)
pre = None
for i in range(1, n + 1):
cur[i] = i
for i in range(1, m + 1):
pre = cur[0]
cur[0] = i
for j in range(1, n + 1):
tmp = cur[j]
if word1[i - 1] == word2[j - 1]:
cur[j] = pre
else:
cur[j] = min(cur[j], cur[j - 1], pre) + 1
pre = tmp
return cur[n]
word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3
力扣第215题
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
'''
方法一:堆排序(调包)
时间复杂度:O(nlogk)
空间复杂度:O(k)
'''
from heapq import nlargest
class Solution:
def findKthLargest(self, nums: list[int], k: int) -> int:
# [3,2,1,5,6,4]
# [6, 5]
return nlargest(k, nums)[-1]
nums, k = [3,2,1,5,6,4], 2
solu = Solution()
solu.findKthLargest(nums, k)
5
其他解法请参考:
《算法通关之路》-chapter13分治法
力扣第703题
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
'''
方法一:堆排序(调包)
时间复杂度:O(nlogk)
空间复杂度:O(k)
'''
import heapq
class KthLargest:
def __init__(self, k: int, nums: list[int]):
# len(nums) >= k-1,通过加一个元素保证查找k大元素时,数组中至少有k个元素
self.nums = heapq.nlargest(k, nums + [float('-inf')])
self.k = k
heapq.heapify(self.nums)
def add(self, val: int) -> int:
heapq.heappushpop(self.nums, val)
return self.nums[0]
kth_largest = KthLargest(3, [4, 5, 8, 2])
kth_largest.add(3) # return 4
kth_largest.add(5) # return 5
kth_largest.add(10) # return 5
kth_largest.add(9) # return 8
kth_largest.add(4) # return 8
8
力扣第230题
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
'''
方法一:二分查找
时间复杂度:O(n),最坏O(n2)
空间复杂度:O(n),最坏O(n2)
'''
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# 返回该节点及所有子节点的个数
def countNodes(node) -> int:
if node == None:
return 0
l = countNodes(node.left)
r = countNodes(node.right)
return l + r + 1
cnt = countNodes(root.left)
if cnt == k - 1:
return root.val
elif cnt > k - 1:
return self.kthSmallest(root.left, k)
return self.kthSmallest(root.right, k - cnt - 1)
力扣第378题
给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
你必须找到一个内存复杂度优于 O(n2) 的解决方案。
'''
方法一:二分查找
时间复杂度:O(nlog(hi-lo))
空间复杂度:O(1)
'''
class Solution:
def kthSmallest(self, matrix: list[list[int]], k: int) -> int:
n = len(matrix)
lo, hi = matrix[0][0], matrix[n - 1][n - 1]
def countNotGreater(target: int) -> int:
i, j = 0, n - 1
cnt = 0
while i < n and j >= 0:
if matrix[i][j] <= target:
cnt += j + 1
i += 1
else:
j -= 1
return cnt
while lo < hi:
mid = (lo + hi) // 2
cnt = countNotGreater(mid)
if cnt < k:
lo = mid + 1
else:
hi = mid
return lo
matrix, k = [[1,5,9],[10,11,13],[12,13,15]], 8
solu = Solution()
solu.kthSmallest(matrix, k)
13
力扣第668题
几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第 k 小的数字吗?
'''
方法一:二分查找
时间复杂度:O(mlogmn)
空间复杂度:O(1)
'''
class Solution:
def findKthNumber(self, m: int, n: int, k: int) -> int:
lo, hi = 1, m * n
def countNotGreater(mid: int, m: int, n: int) -> int:
cnt = 0
for i in range(1, m + 1):
cnt += min(mid // i, n)
return cnt
while lo < hi:
mid = (lo + hi) // 2
if countNotGreater(mid, m, n) < k:
lo = mid + 1
else:
hi = mid
return lo
m, n, k = 3, 3, 5
solu = Solution()
solu.findKthNumber(m, n, k)
3