1.链表两数之和
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
昨天的这个题,用自己的办法写的麻烦的要死,然后刚才一看chat归类的办法,感觉自己像个智障。
class ListNode:
def __init__(self,val=0,next=None):
self.val=val
self.next=next
class Solution1:
def addTwoNumbers(self,l1:Optional[ListNode],l2:Optional[ListNode])->Optional[ListNode]:
ans=ListNode(-1)
head=ans
carry=0
while l1 or l2 or carry:
sum_val=carry
if l1:
sum_val+=l1.val
l1=l1.next
if l2:
sum_val+=l2.val
l2=l2.next
carry=sum_val//10
ans.next=ListNoed(sum_val%10)
ans=ans.next
return head.next
代码逻辑:我的代码中的各种冗余都删掉了,就是说,公共的操作是可以提出来的,在我的代码中,我可以把ans.next的计算放到后面,然后设定sum_val,有谁就加谁,然后最后处理carry,还有sum_val这样的处理。也就是while l1 and l2:还有while l1,while l2.这样的三个情况直接分开。
2.链表翻转:
class ListNode:
def __init__(self,val=0,next=None):
self.val=val
self.next=next
class Solution2:
def reverseList(self,head:Optional[ListNode])->Optional[ListNode]:
prev=None
current=head
while current:
next_node= current.next #1.以current.next.val为头的全部链表存入next_node
current.next=prev #2.也就是说此时的current只剩下了头,next都变成了空
prev=current #3.prev成了current的第一个指针
current=next_node #4.current现在没了第一个值,只剩current.next以后得部分了
return prev
假设链表为1->2->3那么,四步操作,next_node就等于2->3,current.next=None, prev=1, current=2->3
第二次,next_node=3,current.next=1, prev2->1,current=3,以此类推变成3->2->1
代码逻辑如下:
1.存后:先存之后的值,目的是将头和next分离
2.剪接:next重新定义为之前处理过的数据,剪接操作,让current成为新的链表。
3.存新:这时候current已经是新的链表了,那么把新的链表存进prev中,current就可以被释放了
4.读复:用current去覆盖最开始没有头的链表,进入下一个循环
3.链表两数相加 II
输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]
示例2:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[8,0,7]
示例3:
输入:l1 = [0], l2 = [0]
输出:[0]
这个题就是1的翻转,那么完全可以用2加1的办法进行计算我这里直接调用:
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
sol1 = Solution1()
sol2 = Solution2()
l1 = sol2.reverseList(l1)
l2 = sol2.reverseList(l2)
ans = sol1.addTwoNumbers(l1, l2)
ans = sol2.reverseList(ans)
return ans
这是这个题的调用选项,没什么好说的,就是把第一题和第二题合起来用了,那么接下来我将给出一个专属于这个题的解法,这个题中需要学会给头部创建新的结点这个用法,可以直接用于链表的创建。以及divmod这个函数的使用
class Solution:
def addTwoNumbers(self,l1:Optional[ListNode],l2:Optional[ListNode])->Optional[ListNode]:
s1,s2=[],[]
while l1:
s1=s1.append(l1.val)
l1=l1.next
while l2:
s2=l2.append(l2.val)
l2.l2.next
carry=0
dummy=ListNode(0)
while s1 or s2 or carry:
sum=(s1.pop() if s1 else 0) + (s2.pop() if s2 else 0) +carry
val,carry=divmod(sum,10)
dummy.next=ListNode(val,dummy.next)
return dummy.next
代码逻辑如下:
1.首先是用两个数组去存,用append动态存,存进去以后就没l1什么事了
2.定义好新的链表,然后当数组还有carry任意一个不为空的时候就进入循环,然后算sum,算商算余数,其中divmod函数是被除数和除数等于商和余数,非常的符合直觉非常的好用
3.其中dummy.next这个代码非常的好,他是这样的理解的相当于node=ListNode (val, dummy.next)然后再是dummy.next=node,其中的逻辑是,用ListNode这样写,就可以在dummy.next的前面建立一个新头,然后把dummy.next的指针移动到现在头的位置,为下一次循环做基础,也就是说,通过这个代码,我们可以继续改写第二个题。
4.翻转链表:
class Solution:
def reverseList(self,head:Optional[ListNode])->Optional[ListNode]:
dummy=ListNode()
while head:
dummy.next=ListNode(head.val,dummy.next)
head=head.next
非常的完美,非常非常的完美,就是这样
5.二叉树,在8.19的时候第一次提出
class TreeNode:
def __init__(self,val=0,left=None,right=None):
self.val=val
self.left=left
self.right=right
class Solution:
def maxDepth(self,root:Optional[TreeNode])->int:
if not root:
return 0
else:
left_height=self.Depth(root.left)
left_height=self.Depth(root.right)
return max(left_height,right_height)+1
讲道理,再看二叉树确实有种秒杀了的感觉,如果是链表的话,我们可能会考虑用while去数数,但是你现在要考虑一个事情,在链表里,你永远只有一种可能,所以你可以使用while来进行搜索,但是现在你变成二叉树了以后,不是变成了两种可能性,而是直接变成了2的次方的可能性,所以就必须要使用递归来进行可能性的所有遍历,而这就是回溯算法的优势所在,也就是说你不知道要做多少次while的嵌套的时候,那就使用回溯算法来嵌套函数
if not root这个还是很重要,回溯算法的尽头,根节点的时候,下一个已经是none了,那么就不用再加了。
最小深度的代码如下:
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
# 计算左子树和右子树的最小深度
left_depth = self.minDepth(root.left)
right_depth = self.minDepth(root.right)
# 如果其中一个子树为空,则返回不为空的子树的深度+1
if not root.left or not root.right:
return left_depth + right_depth + 1
# 否则,返回左右子树中较小的深度,并加一
return min(left_depth, right_depth) + 1
8.19的文章并没能说清楚这个代码的逻辑,我将在今天的文章中详细解释这个代码逻辑
1.基础的逻辑和最大深度类似,不过是找最小深度的叶子结点,前面用的是max,现在就要用min,但是相比于max的话,min的限制就会稍微多一些,max里我根本不需要考虑一个节点是单个子节点的情况,因为max的过程中会自动跳过那个没有子节点的一侧,但是在最小深度中,如果你不判断,那么他就会在min的时候把0选出来,所以加if not root.left or not root.right的目的就是为了避免出现0
2. if not root就是,上一个节点直接就是子节点了,也就是说上一个节点没有任何一个子节点
3. left_depth函数和right_depth函数和上一个办法类似,没什么好说的
4. if not这里就是我刚说的,如果一个节点有null也有子节点,那么他就得继续下去,所以他的处理就是return left_depth+right_depth+1,因为他并不知道是谁有谁没有,索性直接统一算得了。
5. return min+1这步没什么好说的。
6.二叉树的前序中序后序遍历
这个题中我将详细的介绍每一种遍历的代码,其中每一种遍历的顺序请参考哔哩哔哩的视频,视频地址如下:
https://www.bilibili.com/video/BV1Ub4y147Zv/?spm_id_from=333.337.search-card.all.click
关于代码,我是学习的LeetCode的【Python3】二叉树所有遍历模板及知识点总结(参考大佬们)链接如下:
https://leetcode.cn/problems/binary-tree-inorder-traversal/solutions/324347/python3-er-cha-shu-suo-you-bian-li-mo-ban-ji-zhi-s
**方法一:**递归,控制顺序从而实现遍历
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
# 前序递归
return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)
# # 中序递归
return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)
# # 后序递归
return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val]
代码逻辑:
1.前序遍历是根,左,右,中序遍历是左,根,右,后序遍历是左,右,根。根据哔哩哔哩那个视频进行记忆,就非常的快。
2.not root还是用来停止最后代码的,然后最后的叶子结点,因为带入root.left和root.right的时候,就会return []所以根本不用管他。最后就只输出val
**方法二:**递归,深度优先搜索
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
def dfs(cur):
if not cur:
return
# 前序递归
res.append(cur.val)
dfs(cur.left)
dfs(cur.right)
# 中序递归
dfs(cur.left)
res.append(cur.val)
dfs(cur.right)
# 后序递归
dfs(cur.left)
dfs(cur.right)
res.append(cur.val)
res = []
dfs(root)
return res
**方法三:**迭代,使用栈来模拟递归的过程,该方法仅适用于前后序遍历,是前后序的最简单的方法
class Solution:
def preorderTraversal(self,root:TreeNode)->List[int]:
if not root:
return []
res=[]
stack=[root]
while stack:
cur=stack.pop()
res.append(cur.val)
if cur.right:
stack.append(cur.right)
if cur.left:
stack.append(cur.left)
return res
这个方法我需要评论一下,这个压栈操作非常的amazing,同样的,也是先给stack存一个val,然后进循环以后,就抛一个val出来给res,append,cur.val就是数据,然后本来应该是先左后右的,但是栈是放盘子,所以抛出的时候是反着来的,所以就先右入后左入,抛的时候就是正确的顺序
最后return res
专业术语叫,先将右子节点压栈,然后是左子结点,确保每次处理都是先根再左然后右,这样的好处是,与递归方法相比,迭代法避免了函数调用的开销,适合处理深度较大的树,避免了递归深度过大导致栈溢出的问题。
后序遍历代码如下:
后序遍历代码不写了,没意义,就是把前序代码镜像一下,然后倒序输出,注意倒序输出就是res[::-1]
**方法四:**前中后序通用模板,主要是为了解决中序遍历没有简单办法的说法,中序都能写了,那前序后序就是随便写
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
stack = []
cur = root
# 中序,模板:先用指针找到每颗子树的最左下角,然后进行进出栈操作
while stack or cur:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
# # 前序,相同模板
# while stack or cur:
# while cur:
# res.append(cur.val)
# stack.append(cur)
# cur = cur.left
# cur = stack.pop()
# cur = cur.right
# return res
# # 后序,相同模板
# while stack or cur:
# while cur:
# res.append(cur.val)
# stack.append(cur)
# cur = cur.right
# cur = stack.pop()
# cur = cur.left
# return res[::-1]
N叉树简洁递归:
class Solution:
def preorder(self,root:'Node')->List[int]:
if not root: return []
res=[root.val]
for node in root.children:
res.extend(self.preorder(node))
return res
N叉树通用递归
class Solution:
def preorder(self, root: 'Node') -> List[int]:
res = []
def helper(root):
if not root:
return
res.append(root.val)
for child in root.children:
helper(child)
helper(root)
return res
N叉树迭代
class Solution:
def preorder(self, root: 'Node') -> List[int]:
if not root:
return []
s = [root]
# s.append(root)
res = []
while s:
node = s.pop()
res.append(node.val)
# for child in node.children[::-1]:
# s.append(child)
s.extend(node.children[::-1])
return res
7.翻转二叉树
class Solution:
def invertTree(self,root:Optional[TreeNode])->Optional[TreeNode]:
if not root:
return root
left=self.invertTree(root.left)
right=self.invertTree(root.right)
root.left,root.right=right,left
return root