递归
数据结构第一题按惯例看了题解再写~🤭
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<>();
preorder(root, res);
return res;
}
public void preorder(TreeNode root, List<Integer> res){
if(root == null){
return;
}
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
}
原来二叉树的题要先再定义一个函数做递归然后再调用这个函数,嗯,学废了🤔
copy下卡哥的递归三要素:
每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
同上~
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<>();
postorder(root, res);
return res;
}
public void postorder(TreeNode root, List<Integer> res){
if(root == null){
return;
}
postorder(root.left, res);
postorder(root.right, res);
res.add(root.val);
}
}
同上~
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root == null){
return;
}
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
}
迭代
第一回写果断看题解,感觉还是golandscape大佬的思路最好懂,跟着写了一遍。emmm,不过不知道为啥这个题解没有返回值?👇是有返回值的版本,只要加个List存一下每回pop出来的结点值就行。我没有定义新的变量存储出入栈的结点,直接修改root的,也没什么问题~
思路:由于递归本来就隐含一个栈的操作,迭代就直接把栈定义出来。栈的目的就是实现前序遍历根-左-右的遍历方式,但是因为栈是后进先出的数据结构,因此想要先让左子结点出栈,就要后进左子节点,那么结点进栈的顺序就应该是根-右-左。每次循环都弹出一个根节点,并且加入一个右子节点和一个左子节点,下一次循环把该左子节点再作为根节点弹出,并加入该左子结点的右、左子节点,…
欸,还是直接看叭:史上最全遍历二叉树详解 - 二叉树的前序遍历 - 力扣(LeetCode)
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
Stack<TreeNode> st = new Stack<>();
List<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
st.push(root);
while(!st.isEmpty()){
root = st.pop();
res.add(root.val);
if(root.right != null){
st.push(root.right);
}
if(root.left != null){
st.push(root.left);
}
}
return res;
}
}
记录下自己踩的坑:
1. 第一次是在while循环里先add,再pop了,这样会多加一次根节点,看输出就是错的。
2. 第二个是一开始么有判断树是否为空,如果是空的就不存在下面的操作了。
放一个一开始写的错误代码。。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> st = new Stack<>();
List<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
st.push(root);
while(!st.isEmpty()){
while(root.left != null){
st.push(root.left);
root = root.left;
}
root = st.pop();
res.add(root.val);
if(root.right != null){
st.push(root.right);
root = root.right;
}
}
return res;
}
}
就很呆。。问题很严重。。会重复走左子树。。
题解:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> st = new Stack<>();
List<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
while(!st.isEmpty() || root != null){
while(root != null){
st.push(root);
root = root.left;
}
root = st.pop();
res.add(root.val);
root = root.right;
}
return res;
}
}
让root = root.right以及使用判断条件root != null我的理解是可以防止root走到左子树那里去,造成上面那个代码的错误。
这个跟前序遍历差不多,前序遍历是根-左-右,后续遍历是左-右-根,因此只需要按根-右-左的遍历方式进栈再反转结果就可以了。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
Stack<TreeNode> st = new Stack<>();
List<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
st.push(root);
while (!st.isEmpty()) {
root = st.pop();
res.add(root.val);
if (root.left != null) {
st.push(root.left);
}
if (root.right != null) {
st.push(root.right);
}
}
Collections.reverse(res);
return res;
}
}
还有一种统一迭代的方法…见代码随想录 (programmercarl.com)