刷题笔记系列
【刷题笔记01】- 数组
【刷题笔记02】-链表
【刷题笔记03 -哈希表】
【刷题笔记04】- 字符串
【刷题笔记05】-栈与队列
文章目录
1.树的遍历
1.1 二叉树前中后序遍历
1.1.1 前序遍历
https://leetcode.cn/problems/binary-tree-preorder-traversal/
-
- 递归法的代码(其他顺序就是换下add处理节点的位置
class Solution {
List<Integer> result = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
if(root != null){
result.add(root.val);
preorderTraversal(root.left);
preorderTraversal(root.right);
}
return result;
}
}
-
- 迭代法的代码
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
st.push(root);
while(!st.empty()){
TreeNode cur = st.pop();
if(cur != null){
result.add(cur.val);
st.push(cur.right);
st.push(cur.left);
}
}
return result;
}
}
优化:push前判空,为空就不push了,减少入栈出栈的次数。
1.1.2 中序遍历
https://leetcode.cn/problems/binary-tree-inorder-traversal/
-
- 递归法
class Solution {
List<Integer> result = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root != null){
preorderTraversal(root.left);
result.add(root.val);
preorderTraversal(root.right);
}
return result;
}
}
-
- 迭代法
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
if(root != null) st.push(root);
TreeNode cur = root;
while(cur != null || !st.empty()){
if(cur!= null){
if(cur.left != null) st.push(cur.left);
cur = cur.left;
}else{
cur = st.pop();
result.add(cur.val);
if(cur.right != null) st.push(cur.right);
cur = cur.right;
}
}
return result;
}
中序push时加上了判空反而不合适,也没必要分两个分支都去push。这样逻辑上挺乱的。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
TreeNode cur = root;
while(cur != null || !st.empty()){
if(cur!= null){
st.push(cur);
cur = cur.left;
}else{
cur = st.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
这样优化后入栈和出栈存结果集就分到两个分支中处理了。
中序遍历:左中右
我们访问节点其实都是从父节点开始,通过父节点就能找到子节点。
cur指针的移动 结合 栈中的数据,就可以判断出当前节点的位置。
当cur不等于null时,往栈中push节点并cur = cur.left 通过left一直往左找,直到 cur = cur.left 后 cur为null,就会从栈中取元素,加入结果集,并将cur指向它的right。
1.1.3 后序遍历
https://leetcode.cn/problems/binary-tree-postorder-traversal/
-
- 递归法
class Solution {
List<Integer> result = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
if(root != null){
preorderTraversal(root.left);
preorderTraversal(root.right);
result.add(root.val);
}
return result;
}
}
-
- 迭代法
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
TreeNode cur = root;
TreeNode lastVist = root;
while(cur != null || !st.isEmpty()){
if(cur.left != null){
st.push(cur);
cur = cur.left;
}else{
TreeNode temp = st.peek();
if(temp.right != null && temp.right != lastVist){
cur = temp.right;
}else{
result.add(temp.val);
st.pop();
lastVist = temp;
}
}
}
return result;
}
1.1.4 二叉树三种遍历的统一迭代法——标记法
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
TreeNode cur = root;
if(cur != null) st.push(cur);
while(!st.empty()){
cur = st.pop();
if(cur != null){
//根据遍历顺序不同存入
st.push(cur);
st.push(null);
if(cur.right != null) st.push(cur.right);
if(cur.left != null) st.push (cur.left);
}else{
result.add(st.pop().val);
}
}
return result;
}
这个写法,push节点时就必须要判空了,因为这个写法的核心就是用null标记。
拓展:树的遍历可以把栈换成队列吗?
不可以。
树是一种递归的数据结构,用队列无法达到一条路径上的回溯。
举个例子,对于前序遍历二叉树[1,4,3,2]
若在处理了节点1后往队列中放入节点4 和 节点3
处理节点4时将其子节点2放入队列,而此时队列中节点3在节点2之前,按队列的存取规则,显然先取3再取2,显然不符合dfs的结果。
总结:
前序遍历因为访问和处理顺序一致,所以可以先把root push了,while循环只要判断栈/队列是否为空,取出节点就行;
但是中序和后序都是先要访问到最左子节点处,再处理节点;所以cur指向root,
1.1.5 N叉树的遍历
N叉树只有前序遍历和后序遍历,没有中序遍历的说法,因为无法确定哪几枝是左和右
- N叉树的前序遍历
https://leetcode.cn/problems/n-ary-tree-preorder-traversal/
public List<Integer> preorder(Node root) {
List<Integer> result = new ArrayList<>();
Stack<Node> st = new Stack<>();
if(root != null) st.push(root);
while(!st.isEmpty()){
Node cur = st.pop();
result.add(cur.val);
for(int i = cur.children.size()-1; i >= 0; i--){
st.push(cur.children.get(i));
}
}
return result;
}
- N叉树的后序遍历
https://leetcode.cn/problems/n-ary-tree-postorder-traversal/
class Solution {
public List<Integer> postorder(Node root) {
List<Integer> result = new ArrayList<>();
Stack<Node> st = new Stack<>();
if(root != null) st.push(root);
while(!st.isEmpty()){
Node cur = st.pop();
if(cur != null){
st.push(cur);
st.push(null);
for(int i = cur.children.size()-1; i >= 0; i--){
st.push(cur.children.get(i));
}
}else{
result.add(st.pop().val);
}
}
return result;
}
}
套路和二叉树的遍历类似的,掌握了二叉树前中后序统一迭代法,N叉也类似。
2. LeetCode236 二叉树的公共祖先
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
-
思路:
首先确定遍历顺序:后序遍历 (因为需要根据子节点的返回情况判断当前节点是否满足条件;所以要先遍历再处理节点;若用先序或中序,则无法收集结果) -
代码
-
- ×错误解法×
这里又让我联想起快慢指针移除数组重复元素。有时候条件正向不好处理的,不如用排除法做,下面正解就是巧妙运用了这个思路。
- ×错误解法×
-
- √ 正解
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q)
return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left!=null && right!=null)
return root;
if(left!=null && right==null)
return left;
if(right!=null && left==null)
return right;
return null;
}
-
- √ 优化
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(right == null)
return left;
if(left == null)
return right;
return root;
}
3、LeetCode129 求根节点到叶子节点组成的数字的和
https://leetcode.cn/problems/sum-root-to-leaf-numbers/description/
-
代码
-
- × 错误解法 ×
- × 错误解法 ×
-
- √ 正解
class Solution {
public int sumNumbers(TreeNode root) {
return dfs(root, 0);
}
public int dfs(TreeNode root, int prevSum) {
if(root == null){
return 0;
}
// 每往下一层就要乘10,先做这个处理比较方便
int sum = prevSum*10 + root.val;
if(root.left == null && root.right == null){
return sum;
}else{
return dfs(root.left, sum) + dfs(root.right, sum);
}
}
}
总结
- 树的遍历、图的遍历有时候需要借助标记法标记处理过左右孩子、访问过相邻节点 的 节点
- 在所有可能的情况很明确的情况下,用逆向思考、排除法或许是个不错的选择。