概述
目录
- Day1 栈与队列(简单)
- 09 两个栈实现队列
- 30 包含min函数的栈
- Day2 链表(简单)
- 06 从尾到头打印链表
- 24 反转链表
- 35 复杂链表的复制
- Day3 字符串(简单)
- 05 替换空格
- 58-II 旋转字符串
- Day4 查找算法(简单)
- 03 数组中重复的数字
- @二分查找
- 53-I 在排序数组中查找数字 I
- 53-II 0~n-1中缺失的数字
- Day5 查找算法(中等)
- 04 二维数组中的查找
- *11 旋转数组中的最小数字
- 50 第一个只出现一次的字符
- Day6 搜索与回溯算法(简单)
- @层序遍历
- 32-I 从上到下打印二叉树I
- 32-II 从上到下打印二叉树II
- 32-III 从上到下打印二叉树III
- Day7 搜索与回溯算法
- 26 树的子结构
- 27 二叉树的镜像
- 28 对称的二叉树
- Day8 动态规划(简单)
- 10-I 斐波那契数列
- 10-I 青蛙跳台阶问题
- 63 股票的最大利益
- Day9 动态规划(中等)
- 42 连续子数组的最大和(23字节真题)
- 47 礼物的最大价值
- Day10 动态规划(中等)
- 46 把数字翻译成字符串
- 48 最长不含重复字符的字符串
- Day 11 双指针(简单)
- 18 删除链表得的节点
- 22 链表中的倒数第k个节点
- Day11 双指针(简单)
- 25 合并两个排序的链表
- 52 两个链表的第一个公共节点
- Day13 双指针(简单)
- 21 调整数组顺序使奇数位于偶数前面
- 57 和为s的两个数
- 58-I 翻转单词顺序
- Day14 搜索与回溯算法(中等)
- 12 矩阵中的路径*
- 13 机器人的运动范围
- Day15 搜索与回溯算法(中等)
- 34 二叉树中和为某一值的路径
- @打印中序遍历
- 36 二叉搜索树与双向链表
- 54 二叉搜索树的第k大节点
- Day16 排序(简单)
- 45把数组排成最小的数
- 61 扑克牌中的顺子
- Day17 排序(中等)
- @快排
- 40 最小的k个数
- 大小堆
- 41 数据流中的中位数
- Day18 搜索与回溯算法(中等)
- 55-I 二叉树的深度
- 55-II 平衡二叉树
- Day19 搜索与回溯算法(中等)
- 64 求1+2+...+n
- 68-I 二叉搜索树的最近公共节点
- *68-II 二叉树的最近公共节点
- Day20 分治算法(中等)
- 7 重建二叉树
- 16数值的整数次方
- 33二叉搜索树的后续遍历序列
- Day21 位运算
- 15 二进制中1的个数
- 65 不用加减乘除做加法
Day1 栈与队列(简单)
09 两个栈实现队列
栈只能在栈顶添加或删除元素(对应list中的append和pop都是在列表的尾部进行操作的),而将栈A中的元素转移到栈B可对A中的元素进行倒序。B为空时才将A压入B顺序才不会乱。
class CQueue:
def __init__(self):
self.A = []
self.B = []
def appendTail(self, value: int) -> None:
self.A.append(value)
def deleteHead(self) -> int:
if self.B: return self.B.pop()
while self.A:
self.B.append(self.A.pop())
return self.B.pop() if self.B else -1
30 包含min函数的栈
#辅助栈的长度可以变得更短,只有该值比辅助栈中所有的值都小才压入辅助栈
#只有push和pop改变栈的元素,push的时候只有B为空或者该值小于B顶元素才压入B(因为只要最小的,前面有比该元素小就不会返回该值);
#pop的时候,B顶元素只有等于要被pop的值才会弹出B,不然当前最小值还是B顶元素
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.A = []
self.B = []
def push(self, x: int) -> None:
self.A.append(x)
if (self.B and self.B[-1] >= self.A[-1]) or not self.B:
self.B.append(x)
def pop(self) -> None:
if self.A[-1] == self.B[-1]: self.B.pop()
return self.A.pop()
def top(self) -> int:
return self.A[-1]
def min(self) -> int:
return self.B[-1]
Day2 链表(简单)
06 从尾到头打印链表
方法一:遍历,在列表头用list.insert(index, obj)插入
或用append在尾部插入,然后res.reverse()、res[::-1]
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
res = []
while head:
res.append(head.val)
head = head.next
return res[::-1]
方法二:递归
def reversePrint(self, head: ListNode) -> List[int]:
if not head: return []
return self.reversePrint(head.next) + [head.val]
24 反转链表
方法一:双指针。用cur指向pre,然后pre指向新的链表头(cur),cur指向cur.next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
方法二:递归
#递归,知道遇到None才返回,返回时修改指针指向
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
def recur(cur, pre): #*
if not cur: return pre # 终止条件
res = recur(cur.next, cur) #** # 递归后继节点
cur.next = pre # 修改节点引用指向,这里的cur是*中的cur,不是**中的cur
return res # 返回反转链表的头节点
return recur(head, None) # 调用递归并返回
#注意递归函数不是循环,在**中返回后,会不断执行cur.next = pre(和return res,返回的res始终是5)
35 复杂链表的复制
方法一:哈希表。遍历原链表,复制其节点的val生成新的节点,保存在哈希表。然后再根据原链表的顺序,修改新节点next和random的指向
方法二:复制每一个节点,将其插到对应节点的下一个。以便random找得到,最后再拆分
每个操作基本都涉及链表的遍历
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head: return
dic = {}
cur = head
while cur:
dic[cur] = Node(cur.val) #字典的键是原节点,字典的值是新节点
cur = cur.next
cur = head
while cur:
dic[cur].next = dic.get(cur.next) #字典的键是原节点,字典的值是新节点 #dic.get()返回指定键的值
dic[cur].random = dic.get(cur.random)
cur = cur.next
return dic[head]
#方法二:复制每一个节点,并连接到对应节点的下一个。以便random找得到,最后再拆分
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head: return
cur = head
# 1. 复制各节点,并构建拼接链表
while cur:
tmp = Node(cur.val)
tmp.next = cur.next
cur.next = tmp
cur = tmp.next #或者cur.next.next 注意不是cur.next
# 2. 连接random
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next #cur.next.random = cur.random是错的,cur.random是原来的,下一个才是新建复制的节点
cur = cur.next.next
# 3. 拆分链表,提取出结果
res = cur= head.next
while cur.next:
cur.next = cur.next.next
cur = cur.next
return res
Day3 字符串(简单)
05 替换空格
方法一:遍历,替换
方法二:使用str.split()函数。python的split()方法通过指定的分隔符对字符串进行分割,并将分割后的元素保存在一个列表中
"%20".join(s.split(' '))
58-II 旋转字符串
方法一:切片
return s[n:len(s)+1] + s[0:n] # 或s[n:] + s[:n]
方法二:列表遍历(若不能用切片)
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
lst = []
for i in range(n,len(s)):
lst.append(s[i])
for i in range(n):
lst.append(s[i])
return ''.join(lst)
方法三:字符串遍历拼接。
字符串是 “不可变对象” 。因此,每轮遍历拼接字符时,都需要新建一个字符串;因此,系统 需申请 N*N* 次内存 ,数据量较大时效率低下
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
st = str()
for i in range(n,len(s)):
st += s[i]
for i in range(n):
st += s[i]
return st
Day4 查找算法(简单)
03 数组中重复的数字
方法一:利用set()。设置一个set(),一个个加入,遇到相同的则返回
方法二:排序后比较相同的两个数
方法三:将索引与数值对应
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
i = 0
while i < len(nums):
if nums[i] == i:
i += 1
continue #目的就是让索引等于num[i]
if nums[i] == nums[nums[i]] :return nums[i] #当前值,等于另一个以当前值为索引的值,即冲突了
tmp = nums[nums[i]]
nums[nums[i]] = nums[i]
nums[i] = tmp
# i += 1 #这里不能加一,要交换到当前值等于索引才能加一
return -1
@二分查找
53-I 在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。
方法一:二分法
基础二分查找:
def binary_search(nums, target):
i, j = 0, len(nums) - 1
while i <= j: #没数了才停,有一个数都还要处理
m = (i+j) // 2
if nums[m] == target: return m
elif nums[m] < target: i = m + 1
else: j = m - 1
return None
my_nums = [1,3,5,7,12]
print(binary_search(my_nums,12))
#i=0,j=4,m=2;5<12,执行i=3
#i=3,j=4,m=3;7<12,执行i=4
#i=4,j=4,m=4;12==12,返回4 ,循环条件要有等于号才会执行到这一步
本题用两次二分法找到左右边界
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
#这里是“小于等于”,目的是为了确定右边界,就是说当mid等于target时,因为不确定后面还有没有target,所以同样需要左边收缩范围
else: j = m -1
right = i
i = 0 #注意这里i要重新等于0
while i <= j:
m = (i + j) // 2
if nums[m] >= target: j = m - 1
else: i = m + 1
left = j
return right - left -1
#先找左边界也一样
34 在排序数组中查找元素的第一个和最后一个位置
同样的做法!
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if not nums: return [-1, -1]
i, j = 0, len(nums) -1
while i <= j: #找左边界
m = (i+j) // 2
if nums[m] >= target: j = m - 1 #等于的时候还要继续减
else:i = m + 1
left = j + 1
j = len(nums) -1
while i <= j: #找右边界
m = (i+j) // 2
if nums[m] <= target: i = m + 1
else: j = m - 1
right = i - 1
return [-1, -1] if left >=len(nums) or nums[left] != target else [left, right]
53-II 0~n-1中缺失的数字
方法一:二分法。递增排序数组,想到用二分法
[0,1,2,3,4,5,6,7,9,10] 右子数组9,10的首位元素9的索引就是结果
class Solution:
def missingNumber(self, nums: List[int]) -> int:
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2
if nums[m] == m: i = m + 1
else: j = m - 1
return i #或者return j + 1
Day5 查找算法(中等)
04 二维数组中的查找
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
i, j = len(matrix) - 1, 0
while i >= 0 and j < len(matrix[0]): #注意这里i是逐渐减小的,所以边界是>=0
if matrix[i][j] == target: return True
elif matrix[i][j] < target: j += 1
else: i -= 1
return False
*11 旋转数组中的最小数字
方法一:遍历比较相邻的两个数
方法二:二分法。也是有序的数组
注意这里不用number[m] 与左边比较
#二分法。排序数组用二分查找!
class Solution:
def minArray(self, numbers: List[int]) -> int:
i, j = 0, len(numbers)-1
while i< j: #没有等号
m = (i + j) // 2
#注意都是和numbers[j]比较!
if numbers[m] > numbers[j]: i = m + 1 #此时m一定在左排序数组中,即旋转点x一定在[m+1,j]闭区间内
#注意不是m+1
elif numbers[m] < numbers[j]: j = m #此时m一定在右排序数组中,即旋转点x一定在[i,m]闭区间内
else: j -= 1 #numbers[m]==numbers[j]时,去掉j对结果没影响
return numbers[i]
50 第一个只出现一次的字符
方法一:用字典标记键是否在字典里面
class Solution:
def firstUniqChar(self, s: str) -> str:
dic = {}
for c in s:
dic[c] = not c in dic
for k, v in dic.items():
if v: return k
return ' '
Day6 搜索与回溯算法(简单)
@层序遍历
32-I 从上到下打印二叉树I
方法一:层序遍历(广度优先搜索)
借助队列先入先出的特性来实现
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
if not root: return []
res, queue = [], [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
32-II 从上到下打印二叉树II
方法一:层序遍历(广度优先搜索)
区别是,再设置一层循环来处理同一层的节点
#要动手逐步写才能想到细节是怎样的
#凭空很难把整个过程都想出来,大脑没能模拟出完整的层先过程,不代表这个方法不行,动手做才知道
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root: return []
queue, res = [root], []
while queue:
tmp = []
num = []
for node in queue:
num.append(node.val)
if node.left: tmp.append(node.left)
if node.right: tmp.append(node.right)
queue = tmp
res.append(num)
return res
#写法二:
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root: return []
res, queue = [], [root]
while queue:
tmp = []
for _ in range(len(queue)): #一开始就得到当前层的长度,即使下面再添加节点也不影响
node = queue.pop(0)
tmp.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
res.append(tmp)
return res
32-III 从上到下打印二叉树III
方法一:层序遍历
用flag标记层数,偶数层翻转在append到结果
方法二:层序遍历
奇数层在尾部插入,偶数层在头部插入
#添加tmp的元素时,奇数层添加到尾部,偶数层添加到头部
#1.双端队列tmp = collections.deque(), tmp.appendleft(val), tmp.append(val)
#2.列表也可以,tmp = [], tmp.insert(0,val)
#勿臆断:偶数层不一定要从右到左遍历节点,从左到右遍历,记录数据时放头或放尾部就有不一样的顺序
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root: return []
res, queue = [], collections.deque() #queue=[]也可
queue.append(root)
while queue:
# tmp = collections.deque()
tmp = []
for _ in range(len(queue)):
node = queue.popleft()
if len(res) & 1:
# tmp.appendleft(node.val)
tmp.insert(0,node.val)
else:
tmp.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
# res.append(list(tmp)) #注意这里要将队列转化为list
res.append(tmp)
return res
Day7 搜索与回溯算法
26 树的子结构
判断B是不是A的子结构
方法一:递归
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
#recur函数是确定起点为A,B后开始比
#最后递归调用self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B),是为了遍历不同的节点作为比较起点
def recur(A, B): #判断如果A、B根节点相同,B是不是A的子结构
if not B: return True
if not A or A.val != B.val: return False
return recur(A.left, B.left) and recur(A.right, B.right)
#根节点相同直接进入比较,根节点不同,看B是不是A的左、右子树的子结构
return bool(A and B) and (recur(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B))
#bool(A and B),A为空或B为则直接返回False
#B为空直接返回False因为空树不为子结构,注意后面的几个or条件要用括号括起来
#满足以下条件之一即可
#1.recur(A, B):根节点相同的话直接进入比较,从A、B开始有子结构
#2.isSubStructure(A.left, B):A左子树的根与B的根相同,从A.left、B开始有子结构
#3.isSubStructure(A.right, B): A右子树的根与B的根相同,从A.left、B开始有子结构
27 二叉树的镜像
输入一个二叉树,输出一个它的的镜像
方法一:递归
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
if not root: return
tmp = root.left
root.left = self.mirrorTree(root.right) #root的左子树为右子树的镜像
root.right = self.mirrorTree(tmp) #root的右子树为左子树的镜像
return root #最后的结果要返回当前的root
方法二:层序遍历,设置一个栈来逐个遍历、处理每个节点,将节点的左右子树交换
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
if not root: return
stack = [root]
while stack:
node = stack.pop()
#将节点的左右子树都压入栈来遍历每个节点,并进行左右子树的交换
if node.left: stack.append(node.left)
if node.right: stack.append(node.right)
tmp = node.left
node.left = node.right
node.right = tmp
return root
28 对称的二叉树
判断一个树是不是对称的
方法一:递归
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def recur(L, R):
#终止条件:遍历完所有节点、空了返回True,遇到不相等的直接返回False
if not L and not R: return True #终止条件1:当 L和 R同时越过叶节点(都为空),此树从顶至底的节点都对称,因此返回 true
if not L or not R or L.val != R.val: return False #终止条件2:其中一个为空、或者不相等
#如果L/R不为空且相等,则进入递归,判断子树的节点是否相等
return recur(L.left, R.right) and recur(L.right, R.left) #如果前面的节点都满足,后面遇到一个返回False的,最终结果返回的正是这个False
#注:没有判断两者相等,然后返回True,因为不相等时返回False,若所有对应节点都相等,最后会返回True
return recur(root.left, root.right) if root else True
Day8 动态规划(简单)
10-I 斐波那契数列
方法一:动态规划:
#写法一:
class Solution:
def fib(self, n: int) -> int:
a, b = 0, 1
for _ in range(n):
# a, b = b, a + b #这里是同时赋值的,即a+b的a是原来的a,而不是a=b后的a
# a等于上一个b,要求的新b等于上一个b加当前b
tmp = a
a = b
b = tmp + b #tmp保存的a是上一个b,F(N) = F(N-2) + F(N-1)
return a % 1000000007
#写法二:
class Solution:
def fib(self, n: int) -> int:
a, b = 0, 1
if n == 0: return 0
if n == 1: return 1
for i in range(2, n+1):
res = a + b
a = b
b = res
return res%1000000007
方法二:递归,计算量大
class Solution:
def fib(self, n: int) -> int:
if n == 0:return 0
elif n == 1:return 1
else:
return (self.fib(n-1) + self.fib(n-2))%(10**9+7)
方法三:设置一个list计算,实际上也是动态规划
class Solution:
def fib(self, n: int) -> int:
if n == 0:return 0
if n == 1:return 1
lst = [0,1]
for i in range(2,n+1):
lst.append((lst[i-1]+lst[i-2])%1000000007)
return lst[n]
10-I 青蛙跳台阶问题
一次可以跳1阶也可以跳两个台阶,问有多少中跳法
方法一:递归,超出时间限制
class Solution:
def numWays(self, n: int) -> int:
if n == 0 or n == 1: return 1
return (self.numWays(n-1) + self.numWays(n-2))%1000000007
方法二:动态规划
class Solution:
def numWays(self, n: int) -> int:
if n == 0 or n == 1: return 1
fn = [1,1]
for i in range(2, n+1):
fn.append((fn[i-1] + fn[i-2]) % 1000000007)
return fn[n]
class Solution:
def numWays(self, n: int) -> int:
a, b = 1,1
for i in range(n):
tmp = a
a = b
b = (tmp + b) % 1000000007
return a
63 股票的最大利益
方法一:动态规划
前 i日最大利润 dp[i]等于前i-1日的利益dp[i-1]和第i日卖出的最大利润中的最大值
不需要找出具体在哪一天买入,那一天卖出
考虑当前日的利润就好,要么等于dp[i-1], 要么等于当前日的价格减去之前价格的最低值
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices: return 0
cost = prices[0]
res = 0
for i in range(1,len(prices)):
cost = min(cost, prices[i])
res = max(res, prices[i] - cost)
return res
#k神:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
cost, profit = float("+inf"), 0
for price in prices:
cost = min(cost, price)
profit = max(profit, price - cost)
return profit
Day9 动态规划(中等)
42 连续子数组的最大和(23字节真题)
方法一:动态规划
当前和为sum,对应dp[i],dp[i]要么等于dp[i-1]+当前值,要么等于当前值,取决于dp[i]是否为负数(是否对当前和有贡献)
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
sum = maxSum = nums[0]
for i in nums[1:]:
if sum + i < i: #这里的sum相当于dp[i],根据i的不同情况来更新
sum = i
else: sum += i
if sum > maxSum:
maxSum = sum
return maxSum
47 礼物的最大价值
方法一:动态规划
f(i,j)=max[f(i,j−1), f(i−1,j)] + grid(i,j)
class Solution:
def maxValue(self, grid: List[List[int]]) -> int:
#直接修改grid来保存dp
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == 0 and j == 0: continue
elif i == 0: grid[i][j] += grid[i][j-1]
elif j == 0: grid[i][j] += grid[i-1][j]
else: grid[i][j] += max(grid[i-1][j], grid[i][j-1])
return grid[-1][-1]
Day10 动态规划(中等)
46 把数字翻译成字符串
求多少中翻译方式
方法一:动态规划
不用具体想怎么划分,应该想递推方式
class Solution:
def translateNum(self, num: int) -> int:
s = str(num)
dp = [0] * (len(s)+1)
dp[0] = 1
dp[1] = 1
for i in range(2, len(s) + 1): #2~len(s)
tmp = s[i-2: i]
if "10" <= tmp <= "25":
dp[i] = dp[i-2] + dp[i-1]
else:
dp[i] = dp[i-1]
return dp[len(s)]
48 最长不含重复字符的字符串
求最长不含重复字符串的子字符串的长度
方法一:动态规划
设当前位置是j,s[j]左边最靠近且相同的字符所在的位置是i,dp[j]要么等于j - i,要么等于d[j-1] + 1。取决于s[j]是否在dp[j-1]对应的子字符串内
通过字典记录,检查当前元素在前面出现的最近的位置
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
dic = {}
res = tmp = 0
for j in range(len(s)):
i = dic.get(s[j],-1) #dic.get获取键s[j]的值,若不存在则返回第二参数-1
dic[s[j]] = j
tmp = tmp + 1 if j - i > tmp else j - i
res = max(tmp, res)
return res
Day 11 双指针(简单)
18 删除链表得的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
if head.val == val: return head.next
n1 = head
n2 = head.next
while n2:
if n2.val == val:
n1.next = n2.next
return head
n1 = n1.next #或n1 = n2
n2 = n2.next
22 链表中的倒数第k个节点
方法一:遍历统计长度n,然后走n-k步
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
cur = head
n = 0
while cur:
n += 1
cur = cur.next
for _ in range(n - k):
head = head.next
return head
方法二:双指针
前指针 former
先向前走 kk 步(结束后,双指针 former
和 latter
间相距 kk 步)。
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
forward = later = head
for _ in range(k):
forward = forward.next
while forward:
forward = forward.next
later = later.next
return later
Day11 双指针(简单)
25 合并两个排序的链表
要使合并后的链表还是递增的
方法一:双指针
对两个链表各设置一个指针来遍历,并比较大小
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
res = 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 res.next
52 两个链表的第一个公共节点
方法一:设置两个指针分别指向headA和headB,第一个走完后交替指向headB,第二个走完后指向headB,最终回相遇到共同节点
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
node1, node2 = headA, headB
while node1 != node2:
node1 = node1.next if node1 else headB #注意不是if node1.next
node2 = node2.next if node2 else headA
return node1
Day13 双指针(简单)
21 调整数组顺序使奇数位于偶数前面
方法一:双指针
两个指针分别指向左右两端,交换位置
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
if not nums: return []
i, j = 0, len(nums)-1
while i != j:
if nums[i] % 2:
i += 1
continue
if not nums[j] % 2:
j -= 1
continue
nums[i], nums[j] = nums[j], nums[i]
return nums
57 和为s的两个数
排序数组中找出和为target的两个数
方法一:双指针
分别指向两端。如果能想到双指针,则变得非常简单
合理性:最大的加最小的都比target大,所以最大的数舍弃;最小的加最大的都比target小,所以最小的舍弃
#错解
# class Solution:
# def twoSum(self, nums: List[int], target: int) -> List[int]:
# i = 0
# while i <= len(nums)-1 and nums[i] <= target:
# res = [nums[i]]
# j = i + 1
# while j <= len(nums)-1 and nums[j] <= target:
# if (nums[i] + nums[j]) == target:
# res.append(nums[j])
# return res
# j += 1
# i += 1
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
i, j = 0, len(nums) - 1
while i < j:
if nums[i] + nums[j] == target:
return [nums[i], nums[j]]
elif nums[i] + nums[j] < target:
i += 1
else:
j -= 1
return
58-I 翻转单词顺序
方法一:分割 + 倒序
s.strip()去掉首尾的空格
split(’ ')解决不了单词间多空格的问题,用split();
反转的时候不要一个个append,直接用reverse 或者lst[::-1]
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip()
lst = s.split()
return ' '.join(lst[::-1])
方法二:双指针找边界
从后面往前遍历
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip()
i = j = len(s)-1
res = []
while i >= 0:
while i >= 0 and s[i] != ' ': i -= 1 #注意这里也要保证i >= 0 ,否则出大问题
res.append(s[i+1:j+1])
while s[i] ==' ': i -= 1
j = i
return ' '.join(res)
Day14 搜索与回溯算法(中等)
12 矩阵中的路径*
查找矩阵中是否有给出的word
方法一:递归+剪枝(不符合条件则返回False)
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def dfs(i, j, k):
if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k]: return False
board[i][j] = ' '
if k == len(word) - 1: return True
res = dfs(i-1, j, k+1) or dfs(i+1, j, k+1) or dfs(i, j-1, k+1) or dfs(i, j+1, k+1)
board[i][j] = word[k]
return res
for i in range(len(board)): #需要枚举单词的起点
for j in range(len(board[0])):
if dfs(i,j,0): return True
return False
13 机器人的运动范围
方法一:深度优先搜索DFS
递归朝一个方向搜索再回溯
暴力模拟所有路径,判断是否满足条件,不满足则返回0,满足的则最后+1
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def dfs(i, j, si, sj):
if i >= m or j >= n or si + sj > k or (i,j) in visited: return 0
visited.add((i, j))
return 1 + dfs(i+1, j ,si + 1 if (i+1) % 10 else si - 8, sj) +dfs(i, j + 1, si, sj + 1 if (j + 1) % 10 else sj - 8)
visited = set()
return dfs(0, 0, 0, 0)
方法二:广度优先搜索BFS
队列用list实现,从首端pop出,在末尾append
判断是否满足条件,满足条件的放到集合里,最后统计集合长度
把每格的坐标i,j及其位数之和si,sj作为元组放到队列中
向下向右搜索
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
queue = [(0, 0, 0 , 0)]
visited = set() #满足条件的放到集合里,最后统计集合长度
while queue:
i, j, si, sj = queue.pop(0)
if i >= m or j >= n or si + sj > k or (i,j) in visited: continue
visited.add((i, j))
queue.append((i + 1, j ,si + 1 if (i + 1) % 10 else si - 8, sj))
queue.append((i, j + 1, si, sj + 1 if (j + 1) % 10 else sj - 8))
return len(visited)
Day15 搜索与回溯算法(中等)
34 二叉树中和为某一值的路径
方法一:递归。
肯定是要遍历的
先序遍历每个节点的值,并记录为path,检查path是否满足条件,满足的话添加到res;
然后处理左右节点
class Solution:
def pathSum(self, root: TreeNode, target: int) -> List[List[int]]:
res, path = [], []
def recur(root, tar):
if not root: return #无返回值的return,作用是强制结束函数的调用过程,并且返回调用者现场
path.append(root.val)
tar -= root.val
if tar == 0 and not root.left and not root.right:
res.append(list(path)) #注意这里的list
recur(root.left, tar)
recur(root.right, tar)
path.pop() #没有return语句的函数或者方法,结束之后返回值是None.不需要用到返回值,关键是结束当前函数调用过程,并返回调用现场
recur(root, target)
return res
@打印中序遍历
36 二叉搜索树与双向链表
def dfs(root):
if not root: return
dfs(root.left) # 左
print(root.val) # 根 (对数据的访问处理)
dfs(root.right) # 右
方法一:递归。dfs中的中序遍历
使用中序遍历访问树的各节点 cur
;并在访问每个节点时构建 cur
和前驱节点 pre
的引用指向;中序遍历完成后,最后构建头节点和尾节点的引用指向即可
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node': #类的方法
def dfs(cur): #在方法成员里面定义的函数
if not cur: return
dfs(cur.left) #递归左子树
if self.pre:
cur.left = self.pre #当前指针的左指向pre
self.pre.right = cur #pre的右指向当前
else:
self.head = cur #一开始pre还是None,此时头结点指向cur
self.pre = cur #cur变为pre,再处理下一个
dfs(cur.right)
if not root: return
self.pre = None #类的数据成员
dfs(root)
self.head.left = self.pre #处理头结点和尾结点的指针
self.pre.right = self.head
return self.head
54 二叉搜索树的第k大节点
方法一:递归。中序遍历的倒序
递归右子树,根(访问处理),递归左子树
#中序遍历的倒序
def dfs(root):
if not root: return
dfs(root.right) # 右
print(root.val) # 根 (对数据的访问处理)
dfs(root.left) # 左
#答案
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
def dfs(root):
if not root:return
dfs(root.right)
if self.k == 0: return #这里是为了提前返回。若 k = 0k=0 ,代表已找到目标节点,无需继续遍历,因此直接返回
self.k -= 1
if self.k == 0: self.res = root.val
dfs(root.left)
self.k = k
dfs(root)
return self.res
#后台判题的时候把参数给kthlargest
#错解:
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
def dfs(root, k):
if not root: return
dfs(root.right, k)
k -= 1
if k == 0:
self.res = root.val
return
dfs(root.left, k)
if not root: return
dfs(root, k)
return self.res
#参数k改为self.k,正确
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
def dfs(root,):
if not root: return
dfs(root.right)
self.k -= 1
if self.k == 0:
self.res = root.val
return
dfs(root.left)
if not root: return
self.k = k
dfs(root)
return self.res
Day16 排序(简单)
45把数组排成最小的数
方法一:自定义排序
快速排序
class Solution:
def minNumber(self, nums: List[int]) -> str:
def quick_sort(nums, l, r):
if l >=r: return
i, j = l, r
while i < j:
while i < j and str(nums[j]) + str(nums[l]) >= str(nums[l]) + str(nums[j]): j -= 1
while i < j and str(nums[i]) + str(nums[l]) <= str(nums[l]) + str(nums[i]): i += 1
nums[i], nums[j] = nums[j], nums[i]
nums[l], nums[i] = nums[i], nums[l]
quick_sort(nums, l, i-1)
quick_sort(nums, i+1, r)
#错误写法:
# nums_sort = quick_sort(nums, 0, len(nums)-1) 注意quick_sort没有返回值,而是直接改变nums的顺序
# res = [str(i) for i in nums_sort]
quick_sort(nums, 0, len(nums)-1)
res = [str(i) for i in nums]
return ''.join(res)
#答案写法:
class Solution:
def minNumber(self, nums: List[int]) -> str:
def quick_sort(l , r):
if l >= r: return
i, j = l, r
while i < j:
while strs[j] + strs[l] >= strs[l] + strs[j] and i < j: j -= 1
while strs[i] + strs[l] <= strs[l] + strs[i] and i < j: i += 1
strs[i], strs[j] = strs[j], strs[i]
strs[i], strs[l] = strs[l], strs[i]
quick_sort(l, i - 1)
quick_sort(i + 1, r)
strs = [str(num) for num in nums]
quick_sort(0, len(strs) - 1)
return ''.join(strs)
61 扑克牌中的顺子
根据题意,此 55 张牌是顺子的 充分条件 如下:
1.除大小王外,所有牌 无重复 ;
2.设此 55 张牌中最大的牌为max ,最小的牌为min (大小王除外),则需满足:
max - min < 5
方法一:排序+查看相邻的是否重复
class Solution:
def isStraight(self, nums: List[int]) -> bool:
nums.sort()
i = 0
n0 = 0
while i < len(nums):
if nums[i] == 0: n0 += 1
else: break
i += 1
dic = set()
j = i
while j < len(nums):
if nums[j] in dic: return False
dic.add(nums[j])
j += 1
return True if nums[len(nums)-1] - nums[i] == 4 else False
#答案改进:
class Solution:
def isStraight(self, nums: List[int]) -> bool:
joker = 0
nums.sort() # 数组排序
for i in range(4):
if nums[i] == 0: joker += 1 # 统计大小王数量
elif nums[i] == nums[i + 1]: return False # 若有重复,提前返回 false
return nums[4] - nums[joker] < 5 # 最大牌 - 最小牌 < 5 则可构成顺子
方法二:集合
#方法二:集合+遍历
class Solution:
def isStraight(self, nums: List[int]) -> bool:
dic = set()
ma, mi = 0, 14
for num in nums:
if num in dic and num != 0: return False
dic.add(num)
if num != 0:
ma = max(ma, num)
mi = min(mi, num)
return ma - mi < 5
#答案写法:
class Solution:
def isStraight(self, nums: List[int]) -> bool:
repeat = set()
ma, mi = 0, 14
for num in nums:
if num == 0: continue # 跳过大小王
ma = max(ma, num) # 最大牌
mi = min(mi, num) # 最小牌
if num in repeat: return False # 若有重复,提前返回 false
repeat.add(num) # 添加牌至 Set
return ma - mi < 5 # 最大牌 - 最小牌 < 5 则可构成顺子
Day17 排序(中等)
@快排
40 最小的k个数
方法一:快速排序。
选第一个数为基准,数字交换到左右两个子数组,递归进行
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
def quick_sort(arr, l, r):
if l >= r: return #记得终止条件
i, j = l, r
while i < j:
#先从后面j开始检查,因为此时i指向小于pivot,跳出时是因为j来到i的位置相等了,此时arr[l]换arr[i]正确
#如果i先右移动,指向j的位置而跳出,此时i指向是大于pivot的数
#例如,设pivot = arr[l] = 5
#最后几步:
#[9, 4],j指向4,i指向9,交换,得
#[4, 9],i指向4,j指向9
#[i, j]
#此时j应该先左移到达跳出的条件,后面arr[l]和arr[i]交换才是正确
while i < j and arr[j] >= arr[l]: j -= 1
while i < j and arr[i] <= arr[l]: i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[l], arr[i] = arr[i], arr[l]
quick_sort(arr, l, i-1)
quick_sort(arr, i+1, r)
quick_sort(arr, 0, len(arr)-1)
return arr[:k]
大小堆
41 数据流中的中位数
方法一:最大最小堆
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.A = []
self.B = []
def addNum(self, num: int) -> None:
if len(self.A) != len(self.B):#A为最小堆,保存数值大的部分,且为较长的堆
# heappush(self.A, num)
# heappush(self.B, -heappop(self.A))
heappush(self.B, -heappushpop(self.A,num))
else:
# heappush(self.B, -num) #Python 中 heapq 模块是小顶堆。实现 大顶堆 方法: 小顶堆的插入和弹出操作均将元素 取反 即可
# heappush(self.A, -heappop(self.B))
heappush(self.A, -heappushpop(self.B, -num))
def findMedian(self) -> float:
return self.A[0] if len(self.A) != len(self.B) else (self.A[0] - self.B[0]) / 2 #这里-self.B[0]有负号才是最大堆
Day18 搜索与回溯算法(中等)
55-I 二叉树的深度
方法一:递归,后续遍历
关键点:此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度 与 右子树的深度 中的 最大值 +1
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root: return 0
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
方法二:层序遍历
要统计深度,难免要遍历所有节点
#写法一:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root: return 0
res, queue = 0, [root]
while queue:
res += 1
for _ in range(len(queue)):
node = queue.pop(0)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
return res
#答案写法:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root: return 0
queue, res = [root], 0
while queue:
tmp = [] #保存下一层的节点
for node in queue: #在循环中将下一层的节点都压入tmp
if node.left: tmp.append(node.left)
if node.right: tmp.append(node.right)
queue = tmp
res += 1
return res
55-II 平衡二叉树
判断一棵树是不是平衡二叉树,即左右子树的深度相差是否超过1
方法一:递归,后续遍历 + 剪枝
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def recur(root):
if not root: return 0
left = recur(root.left)
if left == -1: return -1
right = recur(root.right)
if right == -1: return -1
return 1 + max(left, right) if abs(left -right) <= 1 else -1
return recur(root) != -1
方法二:先序遍历 + 判断深度
复杂度高一些
isBalanced(root) 函数: 判断树 root 是否平衡
特例处理: 若树根节点 root 为空,则直接返回 truetrue ;
返回值: 所有子树都需要满足平衡树性质,因此以下三者使用与逻辑 &&&& 连接;
1、abs(self.depth(root.left) - self.depth(root.right)) <= 1 :判断 当前子树 是否是平衡树;
2、self.isBalanced(root.left) : 先序遍历递归,判断 当前子树的左子树 是否是平衡树;
3、self.isBalanced(root.right) : 先序遍历递归,判断 当前子树的右子树 是否是平衡树
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def depth(root):
if not root: return 0
return 1 + max(depth(root.left), depth(root.right))
def dfs(root):
if not root: return True
if abs(depth(root.left) - depth(root.right)) > 1: return False
return dfs(root.left) and dfs(root.right)
return dfs(root)
#答案写法:
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if not root: return True
return abs(self.depth(root.left) - self.depth(root.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
Day19 搜索与回溯算法(中等)
64 求1+2+…+n
不能用for,while, if, case等关键词
class Solution:
def sumNums(self, n: int) -> int:
if n == 1:
return 1
else:
return n + self.sumNums(n-1)
#不能用if:
class Solution:
def sumNums(self, n: int) -> int:
def recur(n):
n > 1 and recur(n-1) #如果n>1为False就不执行后面,直接返回了
self.res += n #如果用res += n,在函数recur里面没有定义局部变量res就使用是不行的
return self.res
self.res = 0 #需要定义成员数据self.res
return recur(n)
#答案写法:
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
68-I 二叉搜索树的最近公共节点
关键是观察二叉搜索特点和题意,代码不难写
从根节点开始找,如果是最近公共节点,该节点的值必位于pq之间
例如0和5
6比两者都大,是公共节点但不是最近的
4位于两者之间,但不是找到的第一个位于两者之间的,不是公共节点
方法一:循环迭代
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
while root:
if root.val < p.val and root.val < q.val:
root = root.right
elif root.val > p.val and root.val > q.val:
root = root.left
else:
break
return root
方法二:递归
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
elif root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left, p, q) #满足这个条件分支,最终是在这里返回的,不会执行到下面的代码(内层会执行)
else:
return root
*68-II 二叉树的最近公共节点
方法一:递归
关键是找出在各种什么条件下要返回
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root: return #如果树为空,直接返回null
if root == p or root == q: return root #如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
left = self.lowestCommonAncestor(root.left, p, q) #递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁
right = self.lowestCommonAncestor(root.right, p, q)
# if not left and not right: return
if not left: return right #如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
if not right: return left # 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
return root #否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root
Day20 分治算法(中等)
7 重建二叉树
方法一:递归分治
left和right分别是中序遍历的左右边界,分治,分成左右子树后,对子树仍然采用同样的方法处理,left,right作为左右子树中序遍历的边界
分治的思想,递归地创建节点,node、node.left、node.right
看题解的图比较清楚
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
#第一个参数root在前序遍历中考虑;left、right是中序遍历中的边界,都是指序号
def recur(root,left,right):
if left > right: return #在递归过程中,left、right是要改变的
Node = TreeNode(preorder[root])
i = dic [preorder[root]] #得到中序中的序号
Node.left = recur(root+1,left,i-1)#第一个参数是作为preoder的索引;
#传入的left也是左子树的left;左子树的right是中序的root的左边,因为访问完左子树的所有元素才会访问root
Node.right = recur(root+i-left+1,i+1,right)
#右子树的根等于root加上左子树的长度(因为先序中是先遍历根,然后到左子树,再到右子树)
#而左子树的长度在在中序遍历中就是left到root之间的长度:i-left+1
#这里的i-1、i+1使得递归到一定的次数就终止、并回溯
return Node
dic = {}
preorder = preorder
for i in range(len(inorder)):
dic[inorder[i]] = i #将取值和对应的序号保存到字典中
return recur(0,0,len(inorder)-1)
16数值的整数次方
方法一:快速幂
分治思想,结果每次与结果本身相乘,而不是乘以原来的x
#快速幂,x^n = (x^2)^n//2,每次把幂降为一般
class Solution:
def myPow(self, x: float, n: int) -> float:
if x == 0: return 0
res = 1
if n < 0: x, n = 1/x, -n
while n:
if n & 1: res *= x
x *= x # x = x^2 这里x每次是变为x的平方;不同于循环相乘res *= x, x不变
n >>= 1 # n//2 注意别写成n>>1
return res
33二叉搜索树的后续遍历序列
判断一个序列是不是某二叉搜索树的后续遍历结果、
方法一:分治。
找到比根节点大的第一个元素,将数组分为左右子数组,递归分治操作
性质1:左子树<根<右子树
性质2:后续遍历的结果是[左子树|右子树|根],根是知道的,就是最后一个元素
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
def recur(i, j):
if i >= j: return True
p = i #设置指针用于遍历
while postorder[p] < postorder[j]: p += 1
m = p #找到第一个比根节点大的元素,则可以将序列分为左右子树,[i, m-1], [m, j]
while postorder[p] > postorder[j]: p += 1 #判断后面是否满足都大于根节点,如果是则可以遍历到p == j
return p == j and recur(i, m-1) and recur(m, j-1)#分为左右子树后,递归分治!
return recur(0, len(postorder)-1)
Day21 位运算
15 二进制中1的个数
#错解。注意输入还是n=11,只是位运算时会看成1011
#所以这里输入控制台输入1011,返回错误结果2,因为len(str())==2
class Solution:
def hammingWeight(self, n: int) -> int:
res = 0
for _ in range(len(str(n))):
if n & 1: res += 1
n >>= 1
return res
class Solution:
def hammingWeight(self, n: int) -> int:
res = 0
while n:
res += n & 1
n >>= 1
return res
65 不用加减乘除做加法
# class Solution:
# def add(self, a: int, b: int) -> int:
# x = 0xffffffff
# a, b = a & x, b & x #python的整数范围是无穷大,统一为32位
# while b: #b是进位
# a, b = (a ^ b), (a & b) << 1 & x
# return a if a <= 0x7fffffff else ~(a ^ x)
#等价于
class Solution:
def add(self, a: int, b: int) -> int:
x = 0xffffffff
a, b = a & x, b & x #python的整数范围是无穷大,统一为32位
while b: #b是进位
s = a ^ b #非进位和
c = (a & b) << 1 & x
a = s
b = c
return a if a <= 0x7fffffff else ~(a ^ x)
最后
以上就是舒服酸奶为你收集整理的剑指offer分类代码总结的全部内容,希望文章能够帮你解决剑指offer分类代码总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复