参考链接:
剑指Offer(上)01-35题实现 python版本
剑指Offer(下)36-75题实现 Python版本
数组、哈希表、排序
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
res = collections.Counter(nums)
for key in res:
if res[key] > 1:
return key
数组、二分查找、分治、矩阵
class Solution:
# 二分查找,使得每一次都可以分出两个相反的选择。
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if not matrix: # 考虑边界情况,特殊情况
return False
n, m = len(matrix), len(matrix[0]) # 从右上角开始,大的往下找,小的往左找
i, j = 0, m - 1
while 0 <= i < n and 0 <= j < m:
if matrix[i][j] == target:
return True
elif matrix[i][j] < target:
i += 1
else:
j -= 1
return False
字符串
class Solution:
def replaceSpace(self, s: str) -> str:
return s.replace(' ', "%20")
栈、递归、链表、双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
res = []
while head:
res.append(head.val)
head = head.next
return res[::-1]
树、数组、哈希表、分治
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 分治思想,从前序遍历中找到根节点,从中序遍历中定位根节点计算出子树的长度
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
# 前序遍历:根左右, 中序遍历:左右根
self.preRootDict = {}
self.pre = preorder
for i in range(len(inorder)):
self.preRootDict[inorder[i]] = i # 记录中序结点的位置
return self.getRoot(0, 0, len(inorder) - 1) # 根节点,中序遍历范围
def getRoot(self, preRoot, inorderLeft, inorderRight):
if inorderLeft > inorderRight: # 中序遍历为空
return
root = TreeNode(self.pre[preRoot]) # 建立根结点
i = self.preRootDict[self.pre[preRoot]] # 找到前序遍历中根结点在中序中的位置
root.left = self.getRoot(preRoot + 1, inorderLeft, i - 1)
# preRoot当前的根 左子树的长度 = 左子树的右边-左边 (i-1-inorderLeft+1) 。最后+1就是右子树的根了
root.right = self.getRoot(preRoot + i - inorderLeft + 1, i + 1, inorderRight)
return root
栈、设计、队列
class CQueue:
def __init__(self):
self.stack1 = []
self.stack2 = []
def appendTail(self, value: int) -> None:
self.stack1.append(value)
def deleteHead(self) -> int:
if self.stack2:
return self.stack2.pop()
else:
if self.stack1:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
return -1
记忆化搜索、数学、动态规划
class Solution:
# # 效率不太高的递归
# def fib(self, n: int) -> int:
# if n <= 0:
# return 0
# if n == 1:
# return 1
# return self.fib(n - 1) + self.fib(n - 2)
# # 效率提升的动态规划
# def fib(self, n: int) -> int:
# if n <= 0:
# return 0
# if n == 1:
# return 1
# fibMinusOne = 0
# fibMinusTwo = 1
# fibN = 0
# for i in range(2, n + 1):
# fibN = fibMinusOne + fibMinusTwo
# fibMinusOne = fibMinusTwo
# fibMinusTwo = fibN
# return fibN % 1000000007
# 效率提升的动态规划
def fib(self, n: int) -> int:
if n <= 0:
return 0
if n == 1:
return 1
arr = [0, 1]
fibN = 0
for i in range(2, n + 1):
arr[i % 2] = arr[0] + arr[1]
return arr[i % 2] % 1000000007
记忆化搜索、数学、动态规划
class Solution:
# 动态规划
def numWays(self, n: int) -> int:
if n == 1 or n == 0:
return 1
arr = [1, 1]
for i in range(2, n + 1):
arr[i % 2] = arr[0] + arr[1]
return arr[i % 2] % 1000000007
数组、二分查找
class Solution:
# 部分有序,二分查找。找出右排序数组中的第一个数字,也就是旋转点
# 时间复杂度:O(log2N), 空间复杂度:O(1)
def minArray(self, numbers: List[int]) -> int:
i, j = 0 , len(numbers) - 1
while i < j: # 已知j一定在右排序数组中
m = (i + j) // 2 # 当前的中心点
if numbers[m] > numbers[j]: i = m + 1 # 若右侧的点小于中心位置的点,m一定在左排序区间内,则说明旋转点一定在中心位置的右侧
elif numbers[m] < numbers[j]: j = m # 若右侧的点大于中心位置的点,m一定在右排序区间内,则说明旋转点一定在中心位置的左侧
else: j = j - 1 # 若相等,则需要进一步判断, 可参考某大佬评论
'''
若旋转点 x < j, 则上述肯定往左区间找
若旋转点 x = j, 则容易错过旋转点,但可以证明,nums[i]=nums[x]=nums[中心点]=...=nums[j],则最终当i=j时返回的num[i]值等于旋转点的值
还有不用nums[m] 和 nums[i]做比较,因为无法判断 m 在哪个排序数组中
本质上,是因为 j 初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。
对于以下两示例,当 i = 0, j = 4, m = 2 时,有 nums[m] > nums[i] ,而结果不同。
[1, 2, 3, 4, 5] 旋转点 x = 0: m 在右排序数组(此示例只有右排序数组);
[3, 4, 5, 1, 2] 旋转点 x = 3: m 在左排序数组。
'''
return numbers[i]
数组、回溯、矩阵
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def dfs(r, c, k):
if not 0 <= r < len(board) or not 0 <= c < len(board[0]) or board[r][c] != word[k]:
return False
if k == len(word) - 1:
return True
temp, board[r][c] = board[r][c], "/" # 用一个临时变量用于存储值,用于回溯
res = dfs(r - 1, c, k + 1) or dfs(r + 1, c, k + 1) or dfs(r, c - 1, k + 1) or dfs(r, c + 1, k + 1)
board[r][c] = temp
return res
for row in range(len(board)):
for col in range(len(board[0])):
if dfs(row, col, 0):
return True
return False
深度优先搜索、广度优先搜索
class Solution:
# # 广度优先遍历, BFS
# def movingCount(self, m: int, n: int, k: int) -> int:
# road = set() # 用一个集合存储所有的路径, 防止后面重复添加
# queue = []
# queue.append((0, 0))
# while queue:
# i, j = queue.pop()
# if (i, j) not in road and self.getSum(i, j) <= k:
# road.add((i, j))
# for di, dj in [[1, 0], [0, 1]]: # 仅考虑往右下方走就可以
# if 0 <= (i + di) < m and 0 <= (j + dj) < n:
# queue.append((i + di, j + dj))
# return len(road)
# def getSum(self, row, col): #计算当前方格的各个位上的和
# temp = 0
# while row > 0:
# temp += row % 10
# row = row // 10
# while col:
# temp += col % 10
# col = col // 10
# return temp
# 深度优先遍历, DFS
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def dfs(i, j):
if i >= m or j >= n or (i, j) in road or self.getSum(i, j) > k:
return
road.add((i, j)) # 加入路径中
dfs(i + 1, j) # 递归调用
dfs(i, j + 1) # 递归调用
road = set()
dfs(0, 0)
return len(road)
def getSum(self, row, col): #计算当前方格的各个位上的和
temp = 0
while row > 0:
temp += row % 10
row = row // 10
while col:
temp += col % 10
col = col // 10
return temp
数学、动态规划
class Solution:
# 动态规划
def cuttingRope(self, n: int) -> int:
dp = [0 for _ in range(n + 1)]
dp[2] = 1 # 初始化
res = -1
for i in range(3, n + 1):
for j in range(1, i):
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])) # 三种情况, 1.不剪;2.从j处剪下来,剩下的i-j不再剪了;3.从j处剪下来,剩下的i-j继续剪
return dp[n]
数学、动态规划
class Solution:
# 应尽可能的拆成多个3,当 n 小于等于 4 时,要特殊考虑
def cuttingRope(self, n: int) -> int:
if n < 4: return n - 1
a, b, p = n // 3, n % 3, 1000000007 # 把绳子尽可能切为多个长度为 3 的片段,留下的最后一段绳子的长度可能为 0,1,2 三种情况。
if b == 0: return 3 ** a % p # 0. 若最后一段绳子长度为 0
if b == 1: return 3 ** (a - 1) * 4 % p # 1. 若最后一段绳子长度为 2 ;则保留,不再拆为 1+1
return 3 ** a * 2 % p # 2. 若最后一段绳子长度为 1 ;则应把一份 3 + 1 替换为 2 + 2
位运算
class Solution:
# n & (n - 1)的运算会把最后一个1消去
def hammingWeight(self, n: int) -> int:
res = 0
while n != 0:
res += 1
n = n & (n - 1)
return res
递归、数学
class Solution:
# 分治法
def myPow(self, x: float, n: int) -> float:
if n < 0: # 当 n < 0时,把问题转化至 n >= 0的范围内
x = 1 / x
n = -n
res = 1 # 初始化res = 1
while n:
if n & 1:
res *= x # 奇数位先乘以多出来的1
x *= x # 执行 x = x ^ 2
n >>= 1 # 执行n右移一位,缩小1半
return res
数组、数学
class Solution:
# # 1.python自带的函数
# def printNumbers(self, n: int) -> List[int]:
# return list(range(1, 10**n))
# # 2.
# def printNumbers(self, n: int) -> List[int]:
# res = []
# for i in range(1, 10 ** n):
# res.append(i)
# return res
# 3.为正确表示大数
def printNumbers(self, n: int) -> List[int]:
def dfs(x):
if x == n: # 终止条件:已固定完所有位
s = ''.join(num[self.start:]) # 拼接 num
if s != '0': res.append(int(s)) # 不为 0 ,则添加至 res 尾部
if n - self.start == self.nine: self.start -= 1 # 如果此时除了 0 就是 9, 则下一次递归 0 的个数减少 1
return
for i in range(10): # 遍历 0 - 9
if i == 9: self.nine += 1 # 9 的个数加 1
num[x] = str(i) # 固定第 x 位为 i
dfs(x + 1) # 开启固定第 x + 1 位
self.nine -= 1
num, res = ['0'] * n, []
self.nine = 0 # 表示 9 的个数
self.start = n - 1 # 表示开始的 0 的个数
dfs(0)
return res
链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
if head.val == val: # 判断若头节点就是所找的数值
head = head.next
front = head
p = head.next
while p:
if p.val == val:
front.next = p.next
front = front.next
p = p.next
return head
递归、字符串、动态规划
class Solution:
# # 动态规划, 时间复杂度:O(mn), 空间复杂度:O(mn)
# def isMatch(self, s: str, p: str) -> bool:
# n, m = len(s), len(p)
# res = [[False] * (m + 1) for _ in range(n + 1)] # 状态转移的初始化
# for i in range(n + 1):
# for j in range(m + 1):
# if j == 0: # 第一种情况:空正则
# res[i][j] = (i == 0)
# else: # 第二种情况:非空正则
# if p[j - 1] != '*': # 非空正则情况之一: 非*
# if i > 0 and (s[i - 1] == p[j - 1] or p[j - 1] == '.'):
# res[i][j] = res[i - 1][j - 1]
# else: # 非空正则情况之一: *
# if j >= 2: # 不看a*的组合
# res[i][j] |= res[i][j - 2]
# if i >= 1 and j >= 2 and (s[i - 1] == p[j - 2] or p[j - 2] == '.'): # 看a*的组合
# res[i][j] |= res[i - 1][j] # 正则串不动, 主串前移一个
# return res[n][m]
# 递归, 时间复杂度:O(mn), 空间复杂度:O(mn)
def isMatch(self, s: str, p: str) -> bool:
# 如果字符串长度为0,需要检测下正则串
if len(s) == 0:
if len(p) % 2 != 0:
return False # 如果正则串长度为奇数,必定不匹配,比如 "."、"ab*",必须是 a*b*这种形式,*在奇数位上
i = 1
while i < len(p):
if p[i] != '*': return False
i += 2
return True
# 如果字符串长度不为0,但是正则串没了,return false
if len(p) == 0: return False
c1, c2, c3 = s[0], p[0], 'a' # c1 和 c2 分别是两个串的当前位,c3是正则串当前位的后一位,如果存在的话,就更新一下
if len(p) > 1:
c3 = p[1]
# 和dp一样,后一位分为是 '*' 和不是 '*' 两种情况
if c3 != '*': # 不是'*'
if c1 == c2 or c2 == '.':
return self.isMatch(s[1:], p[1:])
else: # 否则不匹配
return False
else: # 是'*'
if c1 == c2 or c2 == '.': # 如果该位字符一样,或是正则串该位是 '.',和dp一样,有看和不看两种情况
return self.isMatch(s[1:], p) or self.isMatch(s, p[2:])
else:
return self.isMatch(s, p[2:]) # 不一样,那么正则串这两位就废了,直接往后走
字符串
class Solution:
# 状态转移,有限状态自动机
def isNumber(self, s: str) -> bool:
states = [
{' ': 0, 's': 1, 'd': 2, '.': 4}, # 0. start with blank
{'d': 2, '.': 4}, # 1.sign before e
{'d': 2, '.': 3, 'e': 5, ' ': 8}, # 2.digit before dot
{'d': 3, 'e': 5, ' ': 8}, # 3.digit after dot
{'d': 3}, # 4.digit after dot (is blank before dot)
{'s': 6, 'd': 7}, # 5.e
{'d': 7}, # 6.sign after e
{'d': 7, ' ': 8}, # 7.digit after e
{' ': 8}, # 8.end with blank
]
p = 0 # 以状态0开始
for c in s:
if '0' <= c <= '9': t = 'd' # digit
elif c in "+-": t = 's' # sign
elif c in "eE": t = 'e' # e or E
elif c in ". ": t = c # dot or blank
else: t = "?" # unkown
if t not in states[p]: return False
p = states[p][t]
return p in (2,3,7,8)
数组、双指针、排序
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
pBegin, pEnd = 0, len(nums) - 1
while pBegin < pEnd:
# 当前pBegin指向奇数,向后移动pBegin
while pBegin < pEnd and (nums[pBegin] & 0x1) != 0:
pBegin += 1
# 当前pBegin指向偶数,向前移动pEnd
while pBegin < pEnd and (nums[pEnd] & 0x1) == 0:
pEnd -= 1
nums[pBegin], nums[pEnd] = nums[pEnd], nums[pBegin]
return num
链表、双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
if not head:
return None
front = second = head
for i in range(k): # 前面的指针先走k步
if not front: # 链表长度小于k,则返回None
return None
else:
front = front.next
while front: # 链表未到达末尾,所以前后指针分别前进
front = front.next
second = second.next
return second
递归、链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 用两个指针辅助,原链表逆转
# 时间复杂度:O(n), 空间复杂度:O(1)
def reverseList(self, head: ListNode) -> ListNode:
if not head: # 边缘情况
return None
pre = None
cur = head
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
递归、链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 迭代实现
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
new_node = cur = ListNode(0)
while l1 and l2:
if l1.val < l2.val:
cur.next = l1
l1 = l1.next
else:
cur.next = l2
l2 = l2.next
cur = cur.next
cur.next = l1 if l1 else l2
return new_node.next
# # 递归实现
# def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
# if not l1:
# return l2
# elif not l2:
# return l1
# pMergedHead = ListNode(0)
# if l1.val < l2.val:
# pMergedHead.next = l1
# pMergedHead.next.next = self.mergeTwoLists(l1.next, l2)
# else:
# pMergedHead.next = l2
# pMergedHead.next.next = self.mergeTwoLists(l1, l2.next)
# return pMergedHead.next
树、深度优先搜索、二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 深度优先遍历,递归
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
if A == None or B == None:
return False
return self.dfs(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
def dfs(self, A, B):
if B == None:
return True
if A == None:
return False
return A.val == B.val and self.dfs(A.left, B.left) and self.dfs(A.right, B.right)
树、深度优先搜索、广度优先搜索
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# # 递归实现
# def mirrorTree(self, root: TreeNode) -> TreeNode:
# if root == None:
# return None
# if root.left != None or root.right != None: # 交换左右子树
# root.left, root.right = root.right, root.left
# self.mirrorTree(root.left)
# self.mirrorTree(root.right)
# return root
# 迭代实现, 用栈实现
def mirrorTree(self, root: TreeNode) -> TreeNode:
stack = []
if not root:
return
stack.append(root)
while stack: # 将结点出栈,然后将该结点的左右子树入栈,交换左右子树。直到栈为空时结束。
cur_node = stack.pop()
if cur_node.left:
stack.append(cur_node.left)
if cur_node.right:
stack.append(cur_node.right)
cur_node.left, cur_node.right = cur_node.right, cur_node.left
return root
树、深度优先搜索、广度优先搜索
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# # 递归
# def isSymmetric(self, root: TreeNode) -> bool:
# return self.help(root.left, root.right) if root else True
# def help(self, left, right):
# if not left and not right: # 如果左右两边都没有结点了,跳出条件之一
# return True
# if not left or not right or left.val != right.val: # #左没有结点或者右没有结点或者两个值不相等,跳出条件之二
# return False
# return self.help(left.left, right.right) and self.help(left.right, right.left)
# 迭代,利用树的层次遍历, 双端队列
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
queue = collections.deque()
queue.appendleft(root.left)
queue.append(root.right)
while queue:
left_node = queue.popleft()
right_node = queue.pop()
if not left_node and not right_node:
continue
if not left_node or not right_node or left_node.val != right_node.val:
return False
queue.appendleft(left_node.right)
queue.appendleft(left_node.left)
queue.append(right_node.left)
queue.append(right_node.right)
return True
数组、矩阵、模拟
class Solution:
# 矩阵的遍历
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix:
return matrix
top, bottom, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1
res = []
while True:
for i in range(left, right + 1): # 从左到右遍历
res.append(matrix[top][i])
top += 1 # 上边界向下收敛
if top > bottom: # 是否打印完毕
break
for j in range(top, bottom + 1): # 从上到下遍历
res.append(matrix[j][right])
right -= 1 # 右边界收敛
if right < left: # 是否打印完毕
break
for m in range(right, left - 1, -1): # 从右到左遍历
res.append(matrix[bottom][m])
bottom -= 1 # 下边界收敛
if top > bottom: # 是否打印完毕
break
for n in range(bottom, top - 1, -1): # 从下到上遍历
res.append(matrix[n][left])
left += 1 # 左边界收敛
if left > right: # 是否打印完毕
break
return res
栈、设计
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stackA = []
self.stackB = []
def push(self, x: int) -> None:
self.stackA.append(x)
if not self.stackB or self.stackA[-1] <= self.stackB[-1]:
self.stackB.append(x)
def pop(self) -> None:
if self.stackA.pop() == self.stackB[-1]:
self.stackB.pop()
def top(self) -> int:
return self.stackA[-1]
def min(self) -> int:
return self.stackB[-1]
栈、数组、模拟
class Solution:
# 模拟栈弹出,压入
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
stack = [] # 辅助栈
j = 0
for num in pushed:
stack.append(num) # 按照压入栈的顺序压元素进入辅助栈
while stack and stack[-1] == popped[j]: # 辅助栈不为空,并且栈顶元素等于弹出栈中元素,则弹出
stack.pop()
j += 1 # 弹出栈中元素后移
if not stack: # 最终如果辅助栈为空,说明可以按照新相应的压入顺序得到弹出栈顺序
return True
return False # 否则不可以
树、广度优先搜索、二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# # 二叉树的层次遍历,用队列
# def levelOrder(self, root: TreeNode) -> List[int]:
# if root is None:
# return []
# queue1, queue2, res = [], [], []
# queue1.append(root)
# while queue1:
# node = queue1.pop(0)
# res.append(node.val)
# if node.left:
# queue2.append(node.left)
# if node.right:
# queue2.append(node.right)
# if len(queue1) == 0:
# queue1 = queue2
# queue2 = []
# return res
# 二叉树的层次遍历,用队列
def levelOrder(self, root: TreeNode) -> List[int]:
if root is None:
return []
queue, res = [], []
queue.append(root)
while queue:
node = queue.pop(0)
res.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res
树、广度优先搜索、二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 层次遍历,队列
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if root is None:
return []
queue1, queue2, res, temp = [], [], [], []
queue1.append(root)
while queue1:
node = queue1.pop(0)
temp.append(node.val)
if node.left:
queue2.append(node.left)
if node.right:
queue2.append(node.right)
if len(queue1) == 0:
res.append(temp)
temp = []
queue1 = queue2
queue2 = []
return res
树、广度优先搜索、二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if root is None:
return []
queue1, queue2, res, temp, count = [], [], [], [], 0
queue1.append(root)
while queue1:
node = queue1.pop(0)
temp.append(node.val)
if node.left:
queue2.append(node.left)
if node.right:
queue2.append(node.right)
if len(queue1) == 0:
if count % 2 == 0:
res.append(temp)
temp = []
else:
res.append(temp[::-1])
temp = []
count += 1
queue1 = queue2
queue2 = []
return res
栈、树、二叉搜索树、递归
class Solution:
# 分治法,遍历左右根,运用递归精神
def verifyPostorder(self, postorder: List[int]) -> bool:
i = 0
j = len(postorder) - 1
return self.veriftyRes(postorder, i, j)
def veriftyRes(self, porder, i, j):
if i >= j:
return True
left = i
while porder[left] < porder[j]: # 左子树上的结点值应该小于根节点的值
left += 1
right = left # 划分左右子树,right位置为右子树起点
while porder[left] > porder[j]: # 右子树上的结点值应该大于根节点的值
left += 1
return left == j and self.veriftyRes(porder, i, right - 1) and self.veriftyRes(porder, right, j - 1)
树、深度优先搜索、回溯、二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
# 先序遍历
def pathSum(self, root: TreeNode, target: int) -> List[List[int]]:
res = [] # 最后的结果
path = [] # 遍历的路径
def preOrder(node, cur_Sum):
if not node: # 递归出口
return
path.append(node.val)
cur_Sum -= node.val # 算出之后遍历要得到的数字之和
if cur_Sum == 0 and not node.left and not node.right: # 保证此时数字之和就是所求,且是叶子节点
res.append(list(path)) # 此处因为list是可改变的格式, 需要复制保存
preOrder(node.left, cur_Sum) # 遍历左子树
preOrder(node.right, cur_Sum) # 遍历右子树
path.pop() # 恢复到上一层
preOrder(root, target)
return res
哈希表、链表
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
# # 深拷贝,图的深度优先搜索
# def copyRandomList(self, head: 'Node') -> 'Node':
# visited = {} # 哈希表,字典
# return self.dfs(head, visited)
# def dfs(self, head, visited):
# if not head:
# return None
# if head in visited:
# return visited[head]
# copy = Node(head.val, None, None)
# visited[head] = copy
# copy.next = self.dfs(head.next, visited) # 先复制next结点
# copy.random = self.dfs(head.random, visited) # 再复制random结点
# return copy
# # 深拷贝,图的广度优先搜索
# def copyRandomList(self, head: 'Node') -> 'Node':
# visited = {} # 哈希表,字典
# return self.bfs(head, visited)
# def bfs(self, head, visited):
# if not head:
# return None
# copy = Node(head.val, None, None) # 创建新的结点
# queue = [] # 利用队列记录
# queue.append(head)
# visited[head] = copy
# while queue:
# node = queue.pop(0)
# if node.next and node.next not in visited:
# visited[node.next] = Node(node.next.val, None, None)
# queue.append(node.next)
# if node.random and node.random not in visited:
# visited[node.random] = Node(node.random.val, None, None)
# queue.append(node.random)
# visited[node].next = visited.get(node.next, None)
# visited[node].random = visited.get(node.random, None)
# return copy
# 迭代这种方法比较直接,就是分别拷贝next结点和random结点。
def copyRandomList(self, head: 'Node') -> 'Node':
visited = {} #构建一个链表来记录
def getCloneNode(node):
if node:
if node not in visited:
visited[node] = Node(node.val, None, None)
return visited[node]
else:
return visited[node]
if not head:
return head
old_node = head
new_node = Node(old_node.val, None, None)
visited[old_node] = new_node
while old_node:
new_node.next = getCloneNode(old_node.next)
new_node.random = getCloneNode(old_node.random)
old_node = old_node.next
new_node = new_node.next
return visited[head]
栈、树、深度优先搜索
"""
# Definition for a Node.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
"""
class Solution:
# 对二叉搜索树进行排序,相当于中序遍历二叉树,然后放入链表
def treeToDoublyList(self, root: 'Node') -> 'Node':
def dfs(cur):
if not cur: # 递归出口
return
dfs(cur.left) # 中序遍历,左右根
if self.pre: # pre不为None, 说明找到了头结点
self.pre.right = cur
cur.left = self.pre
else:
self.head = cur # 此时找到头结点
self.pre = cur
dfs(cur.right)
if not root: # 特殊输入的处理
return None
self.pre = None # 保存前一结点,最终是尾结点
dfs(root)
self.head.left = self.pre # 头结点的左孩子是尾结点
self.pre.right = self.head # 尾结点的右孩子是头结点
return self.head
树、深度优先搜索、广度优先搜索
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
import collections
class Codec:
# # 树的层次遍历, 广度优先遍历
# def serialize(self, root):
# if not root:
# return "[]"
# queue = collections.deque()
# queue.append(root)
# res = []
# while queue:
# node = queue.popleft()
# if node:
# res.append(str(node.val))
# queue.append(node.left)
# queue.append(node.right)
# else:
# res.append("null")
# return '[' + ','.join(res) + ']'
# def deserialize(self, data):
# if data == "[]":
# return
# vals, i = data[1: -1].split(','), 1
# root = TreeNode(int(vals[0]))
# queue = collections.deque()
# queue.append(root)
# while queue:
# node = queue.popleft()
# if vals[i] != 'null':
# node.left = TreeNode(int(vals[i]))
# queue.append(node.left)
# i += 1
# if vals[i] != 'null':
# node.right = TreeNode(int(vals[i]))
# queue.append(node.right)
# i += 1
# return root
# 前序遍历,深度优先搜索
def serialize(self, root):
return self.rserialize(root, "")
def rserialize(self, root, strs):
if root == None:
strs += "None,"
else:
strs += str(root.val) + ","
strs = self.rserialize(root.left, strs)
strs = self.rserialize(root.right, strs)
return strs
def deserialize(self, data):
dataArray = data.split(",")
dataList = list(dataArray)
return self.rdeseralize(dataList)
def rdeseralize(self, dataList):
if dataList[0] == "None":
dataList.pop(0)
return None
root = TreeNode(int(dataList[0]))
dataList.pop(0)
root.left = self.rdeseralize(dataList)
root.right = self.rdeseralize(dataList)
return root
字符串、回溯
class Solution:
# # 方法一:递归 + 回溯
# def permutation(self, s: str) -> List[str]:
# strs, n, res = list(s), len(s), []
# def backtrace(num):
# if num == n: # 跳出条件
# res.append("".join(strs))
# return
# jihe = set()
# for i in range(num, n):
# if strs[i] in jihe:
# continue
# jihe.add(strs[i])
# strs[i], strs[num] = strs[num], strs[i] # 固定num的位置
# backtrace(num + 1) # 开始固定num + 1的位置
# strs[i], strs[num] = strs[num], strs[i] # 恢复
# backtrace(0)
# return res
# 方法二:利用回溯模板
def permutation(self, s: str) -> List[str]:
if not s:
return
strs, res = list(sorted(s)), []
def backtrace(strs, tmp):
if not strs:
res.append(''.join(tmp))
for i in range(len(strs)):
if i > 0 and strs[i] == strs[i - 1]: # 去重
continue
backtrace(strs[:i] + strs[i + 1:], tmp + [strs[i]])
backtrace(strs, [])
return res
数组、哈希表、分治、计数、排序
class Solution:
# # 摩尔投票法
# def majorityElement(self, nums: List[int]) -> int:
# if not nums: # 边界情况
# return 0
# target = nums[0]
# res = 1 # 控制一个计数量res
# for i in range(1, len(nums)):
# if nums[i] == target: # 如果跟前一个数相同,就加1
# res += 1
# else: # 如果跟前一个数不相同,则减1
# res -= 1
# if res == 0:
# target = nums[i] # res为0时,重新赋值
# res = 1
# return target # 最后剩下的数就是超过一半的数
# 字典哈希
def majorityElement(self, nums: List[int]) -> int:
if not nums: # 边界情况
return 0
target = len(nums) // 2
numDict = {}
for num in nums:
if num in numDict:
numDict[num] += 1
else:
numDict[num] = 1
if numDict[num] > target:
return num
数组、分治、快速选择、排序
import heapq
class Solution:
# # 排序方法:大根堆, 不改变数组中数的位置
# # 时间复杂度:O(logK), 空间复杂度:O(k)
# def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
# if k == 0: # 处理边界情况
# return list()
# hp = [-x for x in arr[:k]] # 因为python语言中的堆为小根堆,所以需要对数组中所有的值取反,才能使用小根堆维护前k个数
# heapq.heapify(hp) # 将列表list转换为heap堆,在现行时间内,重新排列表
# for i in range(k, len(arr)): # 从第k+1个数开始遍历
# if -hp[0] > arr[i]: # 如果当前遍历到的数比大根堆的堆顶的数要小
# heapq.heappop(hp) # 弹出堆顶的数
# heapq.heappush(hp, -arr[i]) # 往堆中插入当前遍历到的数
# res = [-x for x in hp] # 最后将大根堆的数存入数组返回
# return res
# 排序方法:快速排序, 改变数组中数的位置
# 时间复杂度:O(logK), 空间复杂度:O(k)
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
length = len(arr)
if k <= 0 or k > length: # 边界情况
return list()
left, right = 0, length - 1 # 左右指针
while left < right:
res = self.partition(arr, left, right) # 确定left的数在arr中位置
if res == k - 1: # 如果刚好是k - 1的位置,说明此时找到前k个数字
break
elif res < k - 1: # 如果位置偏左,则调整左指针
left = res + 1
else:
right = res - 1 # 如果位置偏右,则调整右指针
return arr[: k]
def partition(self, arr, left, right):
cur_res = arr[left]
while left < right:
while left < right and arr[right] >= cur_res:
right -= 1
arr[left] = arr[right]
while left < right and arr[left] <= cur_res:
left += 1
arr[right] = arr[left]
arr[left] = cur_res
return left
设计、双指针、数据流、排序
from heapq import *
class MedianFinder:
# # 自带函数sort()的排序, 时间复杂度:O(nlogn)
# def __init__(self):
# """
# initialize your data structure here.
# """
# self.store = []
# def addNum(self, num: int) -> None:
# self.store.append(num)
# def findMedian(self) -> float:
# self.store.sort()
# n = len(self.store)
# if n & 1:
# return self.store[n // 2]
# else:
# return (self.store[n // 2 - 1] + self.store[n // 2]) / 2
# 堆排序, 时间复杂度:O(n)
def __init__(self):
"""
initialize your data structure here.
"""
self.left = [] # 大顶堆,保存左半部分小于中位数的数据
self.right = [] # 小顶堆,保存右半部分大于中位数的数据
def addNum(self, num: int) -> None:
if len(self.left) == len(self.right): # 当左右两顶堆长度相同时,插入左边的大顶堆
heapq.heappush(self.right, num) # 此时数据大于右边小顶堆的最小值,需要插入右边的小顶堆
heapq.heappush(self.left, -heapq.heappop(self.right)) # 并把右边小顶堆的最小值插入左边大顶堆
else: # 当左右两顶堆长度不相同时,肯定是左边大顶堆数目多于右边小顶堆的数目,插入右边的小顶堆
heapq.heappush(self.left, -num) # 此时数据小于左边大顶堆的最大值,需要插入大边的大顶堆
heapq.heappush(self.right, -heapq.heappop(self.left)) # 并把左边大顶堆的最大值插入右边小顶堆
def findMedian(self) -> float:
if len(self.left) == 0:
return 0
if len(self.left) == len(self.right):
mid = (self.right[0] - self.left[0]) / 2.0
else:
mid = -self.left[0]
return mid
数组、分治、动态规划
class Solution:
# 动态规划,维护以当前数字为结尾的子数组的和
def maxSubArray(self, nums: List[int]) -> int:
if not nums: # 边界情况
return 0
nCurSum = 0
nGreatestSum = nums[0]
for num in nums:
if nCurSum <= 0: # num之前的最大子数组的和 比0小就更新为 num --- f(i) = num 当i=0或者f(i - 1) <= 0
nCurSum = num
else: # num之前的最大子数组的和 比0大就更新为 加上num --- f(i) = f(i-1) + num 当i!=0或者f(i - 1) > 0
nCurSum += num
if nCurSum > nGreatestSum:
nGreatestSum = nCurSum
return nGreatestSum
递归、数学、动态规划
class Solution:
# 数学
def countDigitOne(self, n: int) -> int:
k, mulk = 0, 1 # mulk 表示 10 ** k
res = 0
while n >= mulk:
res += (n // (mulk * 10)) * mulk + min(max(n % (mulk * 10) - mulk + 1, 0), mulk) # 计算结果
k += 1
mulk *= 10
return res
数学、二分查找
class Solution:
# 数学
def findNthDigit(self, n: int) -> int:
digit, start, count = 1, 1, 9 # 分别初始化 位数, 起始, 数位
while n > count: # 1. 确定 n 所在数字的位数 digit
n -= count
digit += 1 # 1, 2, 3, ...
start *= 10 # 1, 10, 100, ...
count = 9 * digit * start # 9, 180, 2700, ...
num = start + (n - 1) // digit # 2. 确定所求数位所在的数字 num
return int(str(num)[(n - 1) % digit]) # 3. 确定所求数位在num中的哪一位
贪心、字符串、排序
class Solution:
# 类似于快速排序的的思想
# 时间复杂度:O(nlogn), 最差为O(n^2). 空间复杂度:O(n)
def minNumber(self, nums: List[int]) -> str:
strs = [str(num) for num in nums]
def quickSort(left, right):
if left >= right: # 递归出口
return
i, j = left, right
while i < j:
# 字符串的比较是比较ASCII码值。
# 从字符串的第一个字符开始比较,谁的ASCII码大谁就大。如果第一个相同,则比较第二个。以此类推。如果都相同则相等。
while i < j and strs[j] + strs[left] >= strs[left] + strs[j]: # 保证位置j右边的数都应该在left位置右边
j -= 1
while i < j and strs[i] + strs[left] <= strs[left] + strs[i]: # 保证位置i左边的数都应该在left位置左边
i += 1
strs[i], strs[j] = strs[j], strs[i] # 调整此时的i,j位置的数,放在应该放在的位置
strs[i], strs[left] = strs[left], strs[i] # 把left放在应该排序的位置
quickSort(left, i - 1) # 递归排序left左边的数, 直到递归出口
quickSort(i + 1, right) # 递归排序right右边的数, 直到递归出口
quickSort(0, len(nums) - 1) # 递归开始
return "".join(strs)
字符串、动态规划
class Solution:
# 动态规划,时间复杂度: O(n) ,空间复杂度:O(1)
# def translateNum(self, num: int) -> int:
# str_num = str(num)
# dp_i_1, dp_i_2 = 1, 1 # 当字符长度是0和1,都分别只有一种翻译方法
# for i in range(2, len(str_num) + 1):
# if "10" <= str_num[i - 2: i] <= "25": # 这个期间的数字可以拆成两种
# dp_i = dp_i_1 + dp_i_2 # dp[i] = dp[i - 1] + dp[i - 2]
# else: # dp[i] = dp[i - 1]
# dp_i = dp_i_1
# dp_i_2 = dp_i_1 # 像车轮一样翻滚,更新它们
# dp_i_1 = dp_i
# return dp_i_1
# 动态规划,时间复杂度: O(n) ,空间复杂度:O(n)
# def translateNum(self, num: int) -> int:
# str_num = str(num)
# dp = [1] * (len(str_num) + 1)
# for i in range(2, len(str_num) + 1):
# if "10" <= str_num[i - 2: i] <= "25": # 这个期间的数字可以拆成两种
# dp[i] = dp[i - 1] + dp[i - 2]
# else:
# dp[i] = dp[i - 1]
# return dp[len(str_num)]
# 动态规划,时间复杂度: O(n) ,空间复杂度:O(1) 优化上述的空间效率
def translateNum(self, num: int) -> int:
str_num = str(num)
dp = [1] * 2
for i in range(2, len(str_num) + 1):
if "10" <= str_num[i - 2: i] <= "25": # 这个期间的数字可以拆成两种
dp[i % 2] = dp[(i - 1) % 2] + dp[(i - 2) % 2]
else:
dp[i % 2] = dp[(i - 1) % 2]
return dp[len(str_num) % 2]
数组、动态规划、矩阵路径
class Solution:
# # 动态规划,时间复杂度O(mn), 空间复杂度O(mn)
# def maxValue(self, grid: List[List[int]]) -> int:
# m, n = len(grid), len(grid[0])
# res = [[0] * n for _ in range(m)]
# res[0][0] = grid[0][0]
# for i in range(1, m):
# res[i][0] = res[i - 1][0] + grid[i][0]
# for j in range(1, n):
# res[0][j] = res[0][j - 1] + grid[0][j]
# for i in range(1, m):
# for j in range(1, n):
# res[i][j] = max(res[i - 1][j], res[i][j - 1]) + grid[i][j]
# return res[m - 1][n - 1]
# # 动态规划,时间复杂度O(mn), 空间复杂度O(n) 两行
# def maxValue(self, grid: List[List[int]]) -> int:
# m, n = len(grid), len(grid[0])
# res = [[0] * n for _ in range(2)]
# res[0][0] = grid[0][0]
# for j in range(1, n):
# res[0][j] = res[0][j - 1] + grid[0][j]
# for i in range(1, m):
# res[i % 2][0] = res[(i - 1) % 2][0] + grid[i][0]
# for j in range(1, n):
# res[i % 2][j] = max(res[(i - 1) % 2][j], res[i % 2][j - 1]) + grid[i][j]
# return res[(m - 1) % 2][n - 1]
# 动态规划,时间复杂度O(mn), 空间复杂度O(n) 一行
def maxValue(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
res = [0] * n
res[0] = grid[0][0]
for j in range(1, n):
res[j] = res[j - 1] + grid[0][j]
for i in range(1, m):
res[0] = res[0] + grid[i][0]
for j in range(1, n):
res[j] = max(res[j], res[j - 1]) + grid[i][j]
return res[n - 1]
哈希表、字符串、滑动窗口
class Solution:
# # 蛮力法
# def lengthOfLongestSubstring(self, s: str) -> int:
# if not s:
# return 0
# longest = 0
# for start in range(len(s)):
# for end in range(start, len(s)):
# count = end - start + 1
# substring = s[start: end + 1]
# position = dict()
# if not self.hasDuplication(substring, position):
# if count > longest:
# longest = count
# else:
# break
# return longest
# def hasDuplication(self, substring, position):
# for i in range(len(substring)):
# indexInposition = ord(substring[i]) - ord('a')
# if indexInposition in position:
# return True
# position[indexInposition] = indexInposition
# return False
# 动态规划
def lengthOfLongestSubstring(self, s: str) -> int:
if not s: # 边界情况
return 0
temp = dict() # 保存与当前字符一样的下标
dp = [0] * len(s) # 用来记录以j为结尾的最长不重复子字符串
for j in range(len(s)):
i = temp.get(s[j], -1) #获取与当前字符s[j]一样的字符的下标
temp[s[j]] = j # 更新字典
if dp[j - 1] < j - i : # 状态转移方程 i < 0 或者 dp[j - 1] < j - i
dp[j] = dp[j - 1] + 1
else:
dp[j] = j - i
return max(dp)
哈希表、数学、动态规划
class Solution:
# 动态规划
def nthUglyNumber(self, n: int) -> int:
dp, a, b, c = [1] * n, 0, 0, 0
for i in range(1, n):
n2, n3, n5 = dp[a] * 2, dp[b] * 3, dp[c] * 5
dp[i] = min(n2, n3, n5)
if dp[i] == n2: a += 1
if dp[i] == n3: b += 1 # 必须要是if,如果既等于n2,又等于n3,则都跳过
if dp[i] == n5: c += 1
return dp[-1]
队列、哈希表、字符串、计数
class Solution:
# 利用哈希特征来进行高效查找
def firstUniqChar(self, s: str) -> str:
if not s:
return " "
haxi = dict()
for num in s:
if num in haxi: haxi[num] += 1
else: haxi[num] = 1
for num in haxi:
if haxi[num] == 1:
return num
return " "
树状数组、线段树、数组
class Solution:
# 归并排序, 左右指针, 时间复杂度:O(n), 空间复杂度:O(n)
def mergeSort(self, nums, tmp, l, r):
if l >= r:
return 0
mid = (l + r) // 2
inv_count = self.mergeSort(nums, tmp, l, mid) + self.mergeSort(nums, tmp, mid + 1, r)
i, j, pos = l, mid + 1, l
while i <= mid and j <= r:
if nums[i] <= nums[j]:
tmp[pos] = nums[i]
i += 1
inv_count += (j - (mid + 1)) # 逆序对个数
else:
tmp[pos] = nums[j]
j += 1
pos += 1
for k in range(i, mid + 1): # 左指针没有到达 mid, 则接下来比较
tmp[pos] = nums[k]
inv_count += (j - (mid + 1))
pos += 1
for k in range(j, r + 1): # 右指针没有达到 r, 则接下来比较
tmp[pos] = nums[k]
pos += 1
nums[l: r + 1] = tmp[l: r + 1] # 更新下一次归并的数组
return inv_count
def reversePairs(self, nums: List[int]) -> int:
n = len(nums)
tmp = [0] * n # 辅助数组
return self.mergeSort(nums, tmp, 0, n - 1)
哈希表、链表、双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 时间复杂度:O(m+n), 空间复杂度:O(1)
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
nLength1 = self.GetListLength(headA) # 得到两个链表的长度
nLength2 = self.GetListLength(headB)
nLengthDif = nLength1 - nLength2
pListHeadLong = headA
pListHeadShort = headB
if nLength2 > nLength1:
pListHeadLong = headB
pListHeadShort = headA
nLengthDif = nLength2 - nLength1
for i in range(nLengthDif): # 先在长链表上走几步,再同时在两个链表上遍历
pListHeadLong = pListHeadLong.next
while pListHeadLong != None and pListHeadShort != None and pListHeadShort != pListHeadLong:
pListHeadLong = pListHeadLong.next
pListHeadShort = pListHeadShort.next
return pListHeadLong # 得到第一个公共结点
def GetListLength(self, pHead):
nLength = 0
pNode = pHead
while pNode != None:
nLength += 1
pNode = pNode.next
return nLength
数组、二分查找
class Solution:
# # 二分查找,找是target的值的位置
# def search(self, nums: List[int], target: int) -> int:
# left, right = 0, len(nums) - 1
# res = 0
# while left <= right:
# m = (left + right) // 2
# if nums[m] == target: # 若找到目标值后,从中心扩散,寻找上下界
# res += 1
# i = m - 1
# j = m + 1
# while j <= len(nums) - 1 and nums[j] == target:
# res += 1
# j += 1
# while i >= 0 and nums[i] == target:
# res += 1
# i -= 1
# break
# elif nums[m] < target:
# left = m + 1
# else:
# right = m - 1
# return res
# 二分查找,相当于找左右边界,找不是target的值的位置
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想找的是第一个大于target的数
i = m + 1
else:
j = m - 1
right = i # 找右边界
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2
if nums[m] < target:
i = m + 1 # 尝试查找比target小的更大的数
else: # j想找的是第一个小于target的数
j = m - 1 # 尝试查找大于等于target的更小的数
left = j # 找左边界
return right - left - 1
位运算、数组、哈希表、数学
class Solution:
# 二分查找
def missingNumber(self, nums: List[int]) -> int:
i, j = 0, len(nums) - 1
while i <= j:
mid = (i + j) // 2
if nums[mid] == mid:
i = mid + 1
else:
j = mid - 1
return i
树、深度优先搜索、二叉搜索树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 二叉搜索树的中序遍历是从小到大排序,如果中序遍先右后左,每次k值减小1,当k值减小到0时, root.val即为所求
def kthLargest(self, root: TreeNode, k: int) -> int:
self.k = k
def dfs(node):
if not node:
return
dfs(node.right) # 遍历右子树
self.k -= 1 # 减一
if self.k == 0: # 保存结果
self.res = node.val
return
dfs(node.left) # 遍历左子树
dfs(root)
return self.res
树、深度优先搜索、广度优先搜索
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# # 后序遍历,循环
# def maxDepth(self, root: TreeNode) -> int:
# if not root:
# return 0
# return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
# 层次遍历,循环
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
queue, res = [root], 0
while queue:
tmp = []
for node in queue:
if node.left: tmp.append(node.left)
if node.right: tmp.append(node.right)
queue = tmp
res += 1
return res
树、深度优先搜索、二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# # 递归,从顶开始,判断深度
# def isBalanced(self, root: TreeNode) -> bool:
# if not root:
# return True
# return abs(self.depth(root.left) - self.depth(roo t.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)
# def depth(self, root):
# if not root:
# return 0
# return max(self.depth(root.left), self.depth(root.right)) + 1
# 迭代,后序遍历 + 剪枝
def isBalanced(self, root: TreeNode) -> bool:
def lastSort(root):
if not root:
return 0
left = lastSort(root.left) # 判断左子树是否满足平衡二叉树, 满足的话返回左子树的深度
if left == -1: # 左子树,不满足,返回-1
return -1
right = lastSort(root.right) # 判断右子树是否满足平衡二叉树, 满足的话返回右子树的深度
if right == -1: # 右子树,不满足,返回-1
return -1
if abs(left - right) <= 1: # 当前左右子树的父节点是否满足平衡二叉树,满足的话返回其父节点的深度
return max(left, right) + 1
else:
return -1
return lastSort(root) != -1
位运算、数组
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
res = 0 # 保留异或结果
a, b = 0, 0 # 分组异或
for num in nums:
res ^= num # 所有数字异或, 最终表示的是两个不同的数字的结果
position = 1 # 开始找第一位不是0的数,也就是第一位两个数开始不同的位置
while res & position == 0:
position <<= 1
for num in nums: # 开始分组
if position & num == 0: # a记录的是第一位不为0的数
a ^= num
else:
b ^= num
return [a, b]
位运算、数组
class Solution:
# 应用位操作,若某一位上1出现的次数不是3的倍数,则唯一出现的数字该位上为1
def singleNumber(self, nums: List[int]) -> int:
res = 0
for i in range(32):
cnt = 0 # 记录该位置上1的个数
bit = 1 << i # 记录当前要操作的bit
for num in nums:
if num & bit != 0:
cnt += 1 # 记录该位置上1出现的次数0
if cnt % 3 != 0:
res |= bit # 保留该位上的结果
return res
数组、双指针、二分查找
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
left, right = 0, len(nums) - 1
while left < right: # 二分查找
if nums[left] + nums[right] > target:
right -= 1
elif nums[left] + nums[right] < target:
left += 1
else:
return [nums[left], nums[right]]
return []
数学、双指针、枚举
class Solution:
# 滑动窗口解题, 双指针
def findContinuousSequence(self, target: int) -> List[List[int]]:
left, right = 1, 2
res = []
while right <= target // 2 + 1: # 最大也是target的二分之一
cur_num = []
s = (left + right) * (right - left + 1) // 2 # 求窗口中连续数的和
if s < target: # 如果小于目标值,右指针增长
right += 1
elif s > target: # 如果大于目标值,左指针增长
left += 1
else: # 如果为目标值,保存结果
for num in range(left, right + 1):
cur_num.append(num)
res.append(cur_num)
right += 1
return res
双指针、字符串
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip() # 删除首位空格
left = right = len(s) - 1 # 两个指针的初始位置
res = []
while left >= 0: # 从尾部开始往前移动
while left >= 0 and s[left] != ' ': # 搜索直到遇到空格
left -= 1
res.append(s[left + 1: right + 1]) # 把之前的单词加入到结果中
while s[left] == " ": # 当指针遇到空格,跳过
left -= 1
right = left # right指向下一个单词尾部
return " ".join(res)
数学、双指针、字符串
class Solution:
# 字符串切片
def reverseLeftWords(self, s: str, n: int) -> str:
s1 = s[:n]
s2 = s[n:]
return s2 + s1
# # 旋转操作
# def reverseLeftWords(self, s: str, n: int) -> str:
# s = list(s) # 因为字符串是不可变序列,还有元组
# length = len(s) - 1
# self.reverse(s, 0, n - 1)
# self.reverse(s, n, length)
# self.reverse(s, 0, length)
# return "".join(s)
# def reverse(self, s, start, end):
# while start < end:
# s[start], s[end] = s[end], s[start]
# start += 1
# end -= 1
队列、滑动窗口、单调队列
import collections
import heapq
class Solution:
# # 优先队列,时间复杂度: O(nlogn), 空间复杂度: O(n)
# def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# if not nums:
# return []
# n = len(nums)
# q = [[-nums[i], i] for i in range(k)] # 注意python默认的优先队列是最小堆
# heapq.heapify(q)
# res = [-q[0][0]] # 记录当前最大值
# for i in range(k, n):
# heapq.heappush(q, [-nums[i], i])
# while q[0][1] <= i - k: # 堆中的值不在当前窗口中
# heapq.heappop(q)
# res.append(-q[0][0])
# return res
# 双端队列,时间复杂度: O(n), 空间复杂度: O(k)
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums or k == 0: # 边界情况
return []
deque = collections.deque() # 构建队列记录当前最大值的序列
for i in range(k): # 在未形成窗口前
while deque and deque[-1] < nums[i]: # 队列为空或者队列尾部元素小于数组中的当前元素
deque.pop() # 此时队尾元素不可能是当前窗口的最大值,弹出
deque.append(nums[i])
res = [deque[0]] # 将第一个窗口中的最大值写入结果栈中
for i in range(k, len(nums)): # 已经形成窗口了
if deque[0] == nums[i - k]: # 队列头部元素
deque.popleft() # 弹出队首元素,维护当前元素是在窗口中
while deque and deque[-1] < nums[i]: # 维护双端队列是单调递减的,不断将新的元素与队尾元素相比较
deque.pop() # 弹出队尾元素
deque.append(nums[i])
res.append(deque[0])
return res
设计、队列、单调队列
class MaxQueue:
# 双队列
def __init__(self):
self.queue = []
self.max_queue = []
def max_value(self) -> int:
if not self.queue:
return -1
return self.max_queue[0]
def push_back(self, value: int) -> None:
self.queue.append(value)
while self.max_queue and value > self.max_queue[-1]:
self.max_queue.pop(-1)
self.max_queue.append(value)
def pop_front(self) -> int:
if not self.queue:
return -1
if self.queue[0] == self.max_queue[0]:
self.max_queue.pop(0)
return self.queue.pop(0)
数学、动态规划、概率与统计
class Solution:
# 动态规划,时间复杂度:O(n^2), 空间复杂度:O(n)
def dicesProbability(self, n: int) -> List[float]:
dp = [1/6] * 6 # 初始化
for i in range(2, n + 1):
tmp = [0] * (5 * i + 1) # 本轮点数之和共有情况: 6*i - (i - 1) = 5i + 1 种
for j in range(len(dp)):
for k in range(6):
tmp[j + k] += dp[j] / 6
dp = tmp
return dp
数组、排序
class Solution:
# # 方法一,不排序,如这五张牌连续,需要满足条件1:这五张牌没有重复,条件2:最大值-最小值<5。
# def isStraight(self, nums: List[int]) -> bool:
# repeat = set()
# max_num, min_num = 0, 14 # 最大值初始化为最小值,最小值初始化为最大值
# for num in nums:
# if num == 0: continue
# max_num = max(max_num, num) # 最大值
# min_num = min(min_num, num) # 最小值
# if num in repeat: # 保证repeat中没有重复的数
# return False
# repeat.add(num) # 集合中添加num
# return max_num - min_num < 5
# 方法二,先排序,判断有没有重复的,判断和最小值之间是不是相差值小于5,和前一种方法不同的点在于,判断重复时直接和上一个位置值判断是不是一样就OK
def isStraight(self, nums: List[int]) -> bool:
nums.sort()
joker = 0 # 计算大小王数量
for i in range(len(nums) - 1):
if nums[i] == 0: # 记录大小王数量
joker += 1
elif nums[i] == nums[i + 1]: # 如果有重复,则直接判断不成立
return False
return nums[4] - nums[joker] < 5 # 第joker数位代表的除0之外最小的数
递归、数学
class Solution:
# # 计算机模拟,暴力超时了
# def lastRemaining(self, n: int, m: int) -> int:
# lists = list(range(n))
# i = 0
# while len(lists) > 1:
# i = (i + m - 1) % len(lists) # 计算出当前要删除第一个元素
# lists.pop(i) # 然后从lists中弹出第几个元素
# return lists[0]
# 数学方法,约瑟夫环问题
def lastRemaining(self, n: int, m: int) -> int:
num = 0
for i in range(2, n + 1):
num = (num + m) % i
return num
数学、动态规划
class Solution:
# 动态规划,记录以当前数字结尾的子数组的最大利润
def maxProfit(self, prices: List[int]) -> int:
if not prices: # 边界情况
return 0
profit = 0
min_price = prices[0]
for price in prices:
min_price = min(min_price, price)
profit = max(price - min_price, profit)
return profit
位运算、递归、脑筋急转弯
class Solution:
def __init__(self):
self.res = 0
# 位运算。逻辑运算符
def sumNums(self, n: int) -> int:
n > 1 and self.sumNums(n - 1)
self.res += n
return self.res
位运算、数学
class Solution:
def add(self, a: int, b: int) -> int:
x = 0xffffffff # 与该数相与,可以舍去数字32位以上的数字
a, b = a & x, b & x # 将a, b从无限长度变成一个32位整数
while b != 0:
a, b = (a ^ b), (a & b) << 1 & x # 异或结果得到非进位和; 相与得到进位和,左移1位是作为下一位,并且需要与x与截获前32位
# python中没有int,long等不同长度变量,既没有变量位数的概念
# 获取负数的补码:与十六进制0xffffffff 相与,舍去此数字32位以上的数字,从无限长度变为一个32位整数
# 返回前数字还原:若补码a为负数(最大的整数补码是0x7fffffff),需执行~(a ^ x), 将1至32位按位取反,再整个数字取反
return a if a <= 0x7fffffff else ~(a ^ x) # 如果是正数,就直接返回
数组、前缀和
class Solution:
# 计算上三角和下三角
def constructArr(self, a: List[int]) -> List[int]:
b = [1] * len(a)
tmp = 1
for i in range(1, len(a)):
b[i] = b[i - 1] * a[i - 1] # 从上到下计算下三角
for i in range(len(a) - 2, - 1, -1):
tmp *= a[i + 1] # 从下到上计算上三角
b[i] *= tmp
return b
字符串
class Solution:
def strToInt(self, strs: str) -> int:
res, i, sign, length = 0, 0, 1, len(strs)
int_max, int_min, bndry = 2 ** 31 - 1, - 2 ** 31, 2 ** 31 // 10
if not strs: return 0 # 空字符串,返回0
while strs[i] == " ":
i += 1
if i== length: return 0 # 字符串全为空格,提前返回
if strs[i] == "-": sign = -1
if strs[i] in "+-": i += 1
for c in strs[i:]:
if not '0' <= c <= '9': break
if res > bndry or res == bndry and c > '7':
return int_max if sign == 1 else int_min
res = 10 * res + ord(c) - ord('0')
return sign * res
树、深度优先搜索、二叉搜索树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 利用二叉搜索树的特点, 利用值来判断
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if p.val > q.val:
p, q = q, p # 保障 p.val < q.val, 减少比较次数
while root:
if root.val < p.val:
root = root.right # 两个值均在右子树中
if root.val > q.val:
root = root.left # 两个值均在左子树中
else:
break
return root
树、深度优先搜索、二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 后序遍历,递归
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q: # 递归出口,1,越过叶节点 2.当root等于p,q
return root
left = self.lowestCommonAncestor(root.left, p, q) # 递归工作,递归左子节点
right = self.lowestCommonAncestor(root.right, p, q) # 递归工作,递归右子节点
if not left: # left 为空,right 不为空
return right
if not right: # right 为空,left 不为空
return left
return root # left, right都不为空