美文网首页
LC树题目分类详解

LC树题目分类详解

作者: Aaron_Swartz | 来源:发表于2019-10-03 16:16 被阅读0次
  • leetcode 98 验证二叉排序树的递归写法
// 至今尚未弄懂的递归写法
public class Solution98 {
   // 全局变量
   Integer last = - Integer.MAX_VALUE;
   
   public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (isValidBST(root.left)) {
            if (last < root.val) {
                last = root.val;
                System.out.println("root: " + root.val + "  " + "last: " + last);
                return isValidBST(root.right);
            }
        }
        return false;
    }
// 相对简单一些的递归写法
    public boolean isValidBST(TreeNode root) {
        return helper(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }
    
    public boolean helper (TreeNode root, long min, long max) {  // 基本数据类型比较的时候会自动转换
        if (root == null) return true;
        if (root.val <= min || root.val >= max) return false;
        // 每次递归下去的是当前子树中所有节点的最大最小值。
        return helper(root.left, min, root.val) && helper(root.right, root.val, max);
    }
// 自己根据二叉树排序树中序遍历顺序写的
class Solution {
    // 2 中序遍历为严格递增序列
    private List<Integer> ans = new ArrayList<Integer>();
    
    public boolean isValidBST(TreeNode root) {
        helper(root);
        if (ans.isEmpty() || ans.size() == 1) return true;
        Boolean flag = false;
        for (int i = 0;i < ans.size() - 1; ++i) {
            if (ans.get(i + 1) <= ans.get(i)) return false; 
        }
        return true;
    }
    
    // 中序遍历二叉树
    public void helper (TreeNode root) {
        if (root == null) {
            return;
        }
        if (root.left != null) {
            helper(root.left);
        }
        ans.add(root.val);
        if (root.right != null) {
            helper(root.right);
        }
    }
}
  • leetcode 101 树的镜像对称判断
// 执行时间:1ms 消耗内存:38.1M
// 代码繁琐
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null || (root.left == null && root.right == null)) {
            return true;
        }
        if (root.left == null || root.right == null) {
             return false;
        }
        return helper(root.left, root.right);
    }
    
    public boolean helper (TreeNode p, TreeNode q) {
        // 临界条件
        if (p == null && q == null) {
            return true;
        }
        if (p != null && q != null) {
            if (p.val != q.val) {
                 return false;
            }
        }
        if ((p != null && q == null) || (p == null && q != null)) {
            return false;
        }
        // 递归前进
        return helper(p.left, q.right) && helper(p.right, q.left);
    }
}

网上解答思路
递归结束条件:
1 都为空指针返回true
2 只有一个空指针返回fasle、
递归过程
1 判断两个指针当前节点值是否相等
2 判断A的右子树与B的左子树是否对称
3 判断A的左子树与B的右子树是否对称

class Solution {
    public boolean isSymmetric(TreeNode root) {
       return helper(root, root);
    }
    public boolean helper(TreeNode p, TreeNode q) {
        if (p == null && q == null) return true;
        if (p == null || q == null) return false;
        return (p.val == q.val) && helper(p.left, q.right) && helper(p.right, q.left);
    }
  • 102 二叉树的层次遍历
// AC时间过长,大概30min,不符合面试要求
// 首先应该明确这个肯定是两层循环。
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            Queue<TreeNode> tempQueue = new LinkedList<>();
            List<Integer> ans = new ArrayList<>();
            while (!queue.isEmpty()) {
               TreeNode temp = queue.poll();
                ans.add(temp.val);
                if (temp.left != null) tempQueue.add(temp.left);
                if (temp.right != null) tempQueue.add(temp.right);
            }
            res.add(ans);
            queue = tempQueue;
        }
        return res;
    }
}

其实本质思想就是两层,可以用两个链表或者队列替换链表来实现。一层存储当前层节点,一层存储下一层节点。

  • 103 二叉树的锯齿遍历
    解法一:
// j网上C++ 实现的一个算法,其实本质就是层次遍历,不过是用标记位来表示那一层是需要翻转的。
class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>>ans;
        if(root==NULL)return ans;
        queue<TreeNode *>q;
        q.push(root);
        int flag=1;
        while(!q.empty()){
            queue<TreeNode *>qt;
            vector<int>res;
            while(!q.empty()){
                TreeNode *t=q.front();q.pop();
                res.push_back(t->val);
                if(t->left)qt.push(t->left);
                if(t->right)qt.push(t->right);
            }
            if(flag<0)reverse(res.begin(),res.end());
            flag=-flag;//标志是否需要反转
            ans.push_back(res);
            q=qt;
        }
        return ans;
    }
};

解法二:使用java 双端队列解决

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        if (root == null)
            return new ArrayList<>();
        List<List<Integer>> res = new LinkedList<>();
        Deque<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        boolean left = true;
        while (!queue.isEmpty()) {
            int size = queue.size();
            LinkedList<Integer> list = new LinkedList<>();
            while (size-- > 0) {
                TreeNode node = queue.removeFirst();
                if (node != null) {
                    if(node.left != null)
                        queue.addLast(node.left);
                    if(node.right != null)
                        queue.addLast(node.right);
                    if (left)
                        list.addLast(node.val);
                    else
                        list.addFirst(node.val);
                }
            }
            res.add(list);
            left = !left;
        }
        return res;
    }
}

解法三:也是最好想的,直接用双stack方法来解决

// 最后费劲九牛二虎之力的双stack解法
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        Stack<TreeNode> stack1 = new Stack<>(); // 正向记录
        Stack<TreeNode> stack2 = new Stack<>(); // 反向记录
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res;
        stack1.add(root);
        while (true) {
            List<Integer> ans = new ArrayList<>();
            while (!stack1.isEmpty()) {
                TreeNode temp = stack1.pop();
                ans.add(temp.val);
                if (temp.left != null) stack2.add(temp.left);
                if (temp.right != null) stack2.add(temp.right);
            }
            if (!ans.isEmpty()) {
                res.add(new ArrayList(ans));
                ans.clear();
            } else {
              break;  
            }
            
            while (!stack2.isEmpty()) {
                TreeNode temp = stack2.pop();
                ans.add(temp.val);
                if (temp.right != null) stack1.add(temp.right);
                if (temp.left != null) stack1.add(temp.left);    
            }
            if (!ans.isEmpty()) {
                res.add(new ArrayList(ans));
                ans.clear();
            } else {
              break;  
            }
        }
        return res;
    }
}

我的错误解法: 本以为两个队列可以解决,但是其实从逻辑上就应该明白是错误的,两个队列类似于两个链表,还是没有翻转判断的条件,所以显然仅仅这样是解决不了的!!!

// 注意: 这部分代码为错误代码
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        // 双Queue
        Queue<TreeNode> queue1 = new LinkedList<>(); // 正向记录
        Queue<TreeNode> queue2 = new LinkedList<>(); // 反向记录
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res;
        queue1.add(root);
        while (true) {
            List<Integer> ans = new ArrayList<>();
            while (!queue1.isEmpty()) {
                TreeNode temp = queue1.poll();
                ans.add(temp.val);
                if (temp.right != null) queue2.add(temp.right);
                if (temp.left != null) queue2.add(temp.left);    
            }
            if (!ans.isEmpty()) {
                res.add(new ArrayList(ans));
                ans.clear();
            } else {
              break;  
            }
            
            while (!queue2.isEmpty()) {
                TreeNode temp = queue2.poll();
                ans.add(temp.val);
                if (temp.left != null) queue1.add(temp.left);    
                if (temp.right != null) queue1.add(temp.right);
            }
            if (!ans.isEmpty()) {
                res.add(new ArrayList(ans));
                ans.clear();
            } else {
              break;  
            }
        }
        return res;
    }
}
  • leetcode 110 平衡二叉树判断
// 我的解法, 时间通过, 但是内存消耗过多
class Solution {
    public boolean isBalanced(TreeNode root) {
        if (root == null) {
            return true;
        }
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        return (Math.abs(leftDepth - rightDepth) <= 1) && isBalanced(root.left) && isBalanced(root.right);
    }
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
    }
}
// leetcode 上一个解法
class Solution {
    public boolean isBalanced(TreeNode root) {
        return depth(root) != -1;
    }

    private int depth(TreeNode root) {
        if (root == null) return 0;
        int left = depth(root.left);
        if(left == -1) return -1;  // 这里返回-1其实就是在递归中某个结点已经不满足结果,因此已经终止迭代。
        int right = depth(root.right);
        if(right == -1) return -1;
        return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
    }
}

执行用时 :1 ms, 内存消耗 :38.6 MB

显然第二个代码内存消耗更少些,本质就是在实际递归过程中,遇到不满足条件的及时返回-1,终止递归继续进行。

  • 111 二叉树的最小深度: 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

临界条件如下:
叶子节点的定义是左孩子和右孩子都为 null 时叫做叶子节点
当 root 节点左右孩子都为空时,返回 1
当 root 节点左右孩子有一个为空时,返回不为空的孩子节点的深度
当 root 节点左右孩子都不为空时,返回左右孩子较小深度的节点值
这道题目本质上还是要搞清楚临界条件为三种

// 在大神提示了临界条件的基础上写出来的一版
class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        if (root.left == null) {
            return 1 + minDepth(root.right);
        }
        if (root.right == null) {
            return 1 + minDepth(root.left);    
        }
        return 1 + Math.min(minDepth(root.left), minDepth(root.right)); 
    }
}
// 网上解法
class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        //这道题递归条件里分为三种情况
        //1.左孩子和有孩子都为空的情况,说明到达了叶子节点,直接返回1即可
        if(root.left == null && root.right == null) return 1;
        //2.如果左孩子和由孩子其中一个为空,那么需要返回比较大的那个孩子的深度        
        int m1 = minDepth(root.left);
        int m2 = minDepth(root.right);
        //这里其中一个节点为空,说明m1和m2有一个必然为0,所以可以返回m1 + m2 + 1;
        if(root.left == null || root.right == null) return m1 + m2 + 1;
        
        //3.最后一种情况,也就是左右孩子都不为空,返回最小深度+1即可
        return Math.min(m1,m2) + 1; 
    }
}
  • 112 leetcode: 路径总和问题
class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
       // 1 这个临界条件的意思是当前节点已经为空了,但是在此之前还没有true的返回,则直接返回false
        if (root == null) {
            return false;
        }
        // 2 叶子结点的临界条件
        if (root.left == null && root.right == null) {
            return sum == root.val;
        }
        int ret = sum - root.val;
        // if (root.left == null) { 这部分可以直接注释掉是因为root == null 满足就直接返回false了。
        //     return hasPathSum(root.right, ret);
        // }
        // if (root.right == null) {
        //     return hasPathSum(root.left, ret);
        // }
        return hasPathSum(root.right, ret) || hasPathSum(root.left, ret);
    }
}
// 网上解法一
public boolean hasPathSum(TreeNode root, int sum) {
      return  helper(root,0,sum);
    }
    public boolean helper(TreeNode root,int cur,int sum)
    {
      if(root==null)
          return false;
        cur=cur+root.val;
        if(root.left==null&&root.right==null)
        {
            return cur==sum;
        }else
        {
            return helper(root.left,cur,sum)|| helper(root.right,cur,sum);
        }
    }
  • 113 路径总和II:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
// 自己的解法,在迭代的时候,每次需要额外的开销,新建ArrayList,显然是不合适的,虽然通过
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        List<List<Integer>> res = new ArrayList();
        helper(res, new ArrayList(), root, sum);
        return res;
    }

    public void helper (List<List<Integer>> res, List<Integer> array, TreeNode root, int sum) {
        // 临界条件
        if (root == null) {
            array = null;
            return;
        }        
        array.add(root.val);
        if (root.left == null && root.right == null && sum == root.val) {
            res.add(array);
            return;
        }
        helper(res, new ArrayList(array), root.left, sum - root.val); // 主要是这一步
        helper(res, new ArrayList(array), root.right, sum - root.val);
    }
}
消耗
内存:42.6MB 空间复杂度过大
耗时:3ms
// 网上同学的解法
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        List<List<Integer>> ans = new ArrayList<>();

        helper(root, sum, ans, new ArrayList<>());

        return ans;
    }

    private void helper(TreeNode node, int sum, List<List<Integer>> ans, List<Integer> path) {
        if (node == null) {
            return;
        }

        // 将沿途到节点加入到path中
        sum -= node.val;
        path.add(node.val);

        // 遍历到叶节点
        if (node.left == null && node.right == null) {
            // 如果这是一条可行的路径,才复制path的结果到ans
            if (sum == 0)
                ans.add(new ArrayList<>(path));
        } else {
            helper(node.left, sum, ans, path);
            helper(node.right, sum, ans, path);
        }

        // 回溯
        path.remove(path.size() - 1);
    }
}
// 我参考网上之后的最后一版解法
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        List<List<Integer>> res = new ArrayList();
        helper(res, new ArrayList(), root, sum);
        return res;
    }
    
    public void helper (List<List<Integer>> res, List<Integer> array, TreeNode root, int sum) {
        // 临界条件
        if (root == null) {
            return;
        }        
        array.add(root.val);
        if (root.left == null && root.right == null) {
            if (sum == root.val) {
                res.add(new ArrayList(array));
                // return;  *** 这个return 当时是我多加的,后来发现不合适需要去掉 ***
            }
        } else {
            helper(res, array, root.left, sum - root.val);
            helper(res, array, root.right, sum - root.val);
        }
        // 回溯
        array.remove(array.size() - 1);
    }
}
执行时间:2ms
消耗内存:37.7M
优化成功

这里需要注意一个节点需要回溯的条件:
先判断当前节点是否为叶子节点,如果是则进入处理逻辑。如果不是叶子节点,则需要分别进入该节点的左右子叶节点中递归调用处理。当处理完这个之后,整个节点的也就处理完了,如果有符合要求的路径一定是加入了,然后就可以执行回溯操作。

  • 226 翻转二叉树
    • 递归解答
class Solution {
    public TreeNode invertTree(TreeNode root) {
        // 递归边界
        if ((root == null) || (root.left == null && root.right == null)) return root;
        // 取出标记节点的值
        TreeNode left = root.left;
        TreeNode right = root.right;
        // 递归前进
        root.left = invertTree(right);
        root.right = invertTree(left);
        return root;
    }
}

  • 迭代解答: 层次遍历加上途中交换左右子树
public TreeNode invertTree(TreeNode root) {
    if (root == null) return null;
    // LinkedList实现了集合框架的Queue接口
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root); // 加入元素
    while (!queue.isEmpty()) {
      // 获取并移出元素
      TreeNode current = queue.poll();
      //交换左右子树
      TreeNode temp = current.left;
      current.left = current.right;
      current.right = temp;
      //左子树不为空,将左子树入栈
      if (current.left != null) queue.add(current.left);
      //右子树不为空,将右子树入栈
      if (current.right != null) queue.add(current.right);
    }
    return root;
}
  • leetcode 236 二叉树的最近公共祖先:面试的时候遇见过一次
    • 父指针迭代法:本质就是依次记录节点的父指针,然后比较两个节点父指针的交点。
class Solution {

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        // Stack for tree traversal
        Deque<TreeNode> stack = new ArrayDeque<>();

        // HashMap for parent pointers
        Map<TreeNode, TreeNode> parent = new HashMap<>();

        parent.put(root, null);
        stack.push(root);

        // Iterate until we find both the nodes p and q
        while (!parent.containsKey(p) || !parent.containsKey(q)) {

            TreeNode node = stack.pop();

            // While traversing the tree, keep saving the parent pointers.
            if (node.left != null) {
                parent.put(node.left, node);
                stack.push(node.left);
            }
            if (node.right != null) {
                parent.put(node.right, node);
                stack.push(node.right);
            }
        }

        // Ancestors set() for node p.
        Set<TreeNode> ancestors = new HashSet<>();

        // Process all ancestors for node p using parent pointers.
        while (p != null) {
            ancestors.add(p);
            p = parent.get(p);
        }

        // The first ancestor of q which appears in
        // p's ancestor set() is their lowest common ancestor.
        while (!ancestors.contains(q))
            q = parent.get(q);
        return q;
    }
}
时间复杂度:O(N)
空间复杂度:O(N)
显然运行效率一般
  • 递归法: 引入mid变量
class Solution {

    private TreeNode ans;

    public Solution() {
        // Variable to store LCA node.
        this.ans = null;
    }

    private boolean recurseTree(TreeNode currentNode, TreeNode p, TreeNode q) {

        // If reached the end of a branch, return false.
        if (currentNode == null) {
            return false;
        }

        // Left Recursion. If left recursion returns true, set left = 1 else 0
        int left = this.recurseTree(currentNode.left, p, q) ? 1 : 0;

        // Right Recursion
        int right = this.recurseTree(currentNode.right, p, q) ? 1 : 0;

        // If the current node is one of p or q
        int mid = (currentNode == p || currentNode == q) ? 1 : 0;


        // If any two of the flags left, right or mid become True
        if (mid + left + right >= 2) {
            this.ans = currentNode;
        }

        // Return true if any one of the three bool values is True.
        return (mid + left + right > 0);
    }

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // Traverse the tree
        this.recurseTree(root, p, q);
        return this.ans;
    }
}
  • 比较简单的递归写法

本质中是在左右子树种寻找p,q节点,如果左右子树中均存在目标节点,则说明root节点为最近公共祖先。否则在左子树递归寻找p,q节点,要么为null,要么找到该节点。

   public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 临界条件
        if (root == null || root == q || root == p) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left != null && right != null) return root;
        return left != null ? left : right;
    }
  • leetcode 235: 单链表plus 1
/**
 * 2019/10/6.
 * 单链表加1
 * 756 plus 1
 *
 */
public class Solution23 {
    public ListNode plusOne(ListNode root) {
        if (root == null) {
            return root;
        }
        int flag = helper(root);
        if (flag == 1) {
            ListNode node = new ListNode(1);
            node.next = root;
            return node;
        }
        return root;
    }

    public int helper(ListNode root) {
        // 临界条件
        if (root == null) return 1; // 这里表示最后加1的情形
        int sum = (helper(root.next) + root.value);
        int flag = sum / 10;
        root.value = sum % 10;
        return flag;
    }

之前面试过的题目,重新AC了一遍

相关文章

网友评论

      本文标题:LC树题目分类详解

      本文链接:https://www.haomeiwen.com/subject/jpqfiqtx.html