目录
Leetcode94:输出二叉树的中序遍历序列(中序 递归 迭代 medium)
Leetcode98:判断一棵二叉树是否是BST(BST 中序 medium)
《剑指offer》面试题33:BST的后序遍历序列(BST 后序)
《剑指offer》面试题36:BST转双链表(BST 中序 双链表)
Leetcode108:有序数组转BST(BST 分治 easy)
Leetcode109:单链表转BST(BST 中序 单链表 medium)
《剑指offer》面试题54:BST第k小的节点(BST 中序)
Leetcode116:填充同一层的兄弟节点(BFS 前序 medium)
《剑指offer》面试题32(题目一):按层不分行输出二叉树(BFS)
《剑指offer》面试题32(题目二):按层分行输出二叉树(BFS)
《剑指offer》面试题32(题目三):之字形打印二叉树(BFS 栈)
《剑指offer》面试题34:二叉树中和为某一值的路径(回溯)
Leetcode124:二叉树的最大路径和(动态规划 后序 hard)
Leetcode863:二叉树中与target距离为K的结点(DFS(前序) BFS medium)
《剑指offer》面试题55(题目一):二叉树的深度(递归)
《剑指offer》面试题55(题目二):平衡二叉树AVL(后序)
遍历
《剑指offer》面试题7:重建二叉树(前序
中序
)
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if (pre == null || in == null) {
return null;
}
if (pre.length == 0 || in.length == 0) {
return null;
}
if (pre.length != in.length) {
return null;
}
TreeNode root = new TreeNode(pre[0]);
for (int i = 0; i < pre.length; i++) {
if (pre[0] == in[i]) {
root.left = reConstructBinaryTree(
Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
root.right = reConstructBinaryTree(
Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length));
}
}
return root;
}
}
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
TreeNode root=reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
return root;
}
//前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
private TreeNode reConstructBinaryTree(int [] pre,int startPre,int endPre,int [] in,int startIn,int endIn) {
if(startPre>endPre||startIn>endIn)
return null;
TreeNode root=new TreeNode(pre[startPre]);
for(int i=startIn;i<=endIn;i++)
if(in[i]==pre[startPre]){
root.left=reConstructBinaryTree(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);
root.right=reConstructBinaryTree(pre,i-startIn+startPre+1,endPre,in,i+1,endIn);
break;
}
return root;
}
}
《剑指offer》面试题8:中序遍历的下一个节点(中序
)
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
题解:
- 如果当前节点有右子节点:下一个节点是右子树中的最左子节点
- 如果当前节点没有右子节点
- 当前节点是左子树:下一个节点就是父节点
- 当前节点是右子树:下一个节点就是该节点所在子树是某个节点左子树的那个节点
class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if(pNode == null) return null;
TreeLinkNode pNext = null;
//1.如果当前节点有右子树
if(pNode.right!=null){
pNext = pNode.right;
while(pNext.left!=null){
pNext = pNext.left;
}
return pNext;
}//2.如果没有右子树
else if(pNode.next !=null){
//2.1当前节点是左子树
if(pNode.next.left == pNode){
pNext = pNode.next;
}else{
//2.2当前节点是右子树
TreeLinkNode cur = pNode;
TreeLinkNode par = pNode.next;
while(par!=null&&cur == par.right){
cur = par;
par = par.next;
}
pNext = par;
}
return pNext;
}
return null;
}
}
《剑指offer》面试题26:树的子结构(前序
)
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
题解:
1.第一步在树A中找到和B的根节点的值一样的结点R; 这实际上就是树的遍历。可以用递归实现
递归调用HasSubTree遍历二叉树A。如果发现某一结点的值和树B的头结点的值相同,则转向第2步判断两个结点为根的数是否存在父子关系
2.第二步再判断树A中以R为根结点的子树是不是包含和树B一样的结构。
这个过程其实就是要要判断两棵树对应的节点数据是否相同。这个是一个递归的过程。
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//如果a为空,或者b为空
if(root1 == null || root2 ==null) return false;
boolean boo = false;
//如果A的值等于B的值,就开始比较(第二步方法)
if(root1.val == root2.val){
boo = haveChild(root1,root2);
}
//如果boo不为true,就是没有找到
if(boo != true){
//就向下查找A中与B根节点值相等的子树
return HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
}else{
//如果 boo == true,说明已经找到
return true;
}
}
public boolean haveChild(TreeNode root1,TreeNode root2){
if(root2 == null) return true;//子树为空,必为子树
if(root1 == null) return false;//子树不为空,父树为空
//如果两个节点值不相等
if(root1.val != root2.val){
return false;
}else{
//继续递归比较下面的节点值
return haveChild(root1.left,root2.left)&&haveChild(root1.right,root2.right);
}
}
}
《剑指offer》面试题37:序列化二叉树(前序
)
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
题解:
Binary Search Tree 的序列化本质上是对其值进行编码,更重要的是对其结构进行编码。可以遍历树来完成上述任务。众所周知,我们有两个一般策略:
方法一:深度优先搜索
深度优先搜索(DFS)在这个策略中,我们采用深度作为优先顺序,这样我们就可以从一个根开始,一直延伸到某个叶,然后回到根,到达另一个分支。
根据根节点、左节点和右节点之间的相对顺序,可以进一步将DFS策略区分为 preorder、inorder 和 postorder 。
然而,在这个任务中,DFS 策略更适合我们的需要,因为相邻节点之间的链接自然地按顺序编码,这对后面的反序列化任务非常有帮助。
方案二:广度优先搜索
广度优先搜索(BFS)我们按照高度的顺序从上到下逐级扫描树。更高级别的节点将先于较低级别的节点访问。
//DFC 序列化字符串:1,2,3,None,None,4,None,None,5,None,None
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
return rserialize(root, "");
}
public String rserialize(TreeNode root, String str) {
// Recursive serialization.
if (root == null) {
str += "null,";
} else {
str += str.valueOf(root.val) + ",";
str = rserialize(root.left, str);
str = rserialize(root.right, str);
}
return str;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] data_array = data.split(",");
List<String> data_list = new LinkedList<String>(Arrays.asList(data_array));
return rdeserialize(data_list);
}
public TreeNode rdeserialize(List<String> l) {
// Recursive deserialization.
if (l.get(0).equals("null")) {
l.remove(0);
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(l.get(0)));
l.remove(0);
root.left = rdeserialize(l);
root.right = rdeserialize(l);
return root;
}
};
/**
* 按层次遍历,序列化格式:1 2 3 null null 4 5 null null null null
* 借助队列实现树的BFS
*/
public String serialize(TreeNode root) {
StringBuilder builder = new StringBuilder();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode pop = queue.poll();
builder.append(pop != null ? pop.val : null).append(" ");
if (pop != null) {
queue.add(pop.left);
queue.add(pop.right);
}
}
return builder.toString();
}
// Decodes your encoded data to tree.
/**
* "1 2 3 null null 4 5 null null null null"遍历数组,使用BFS反序列化生成Tree
*/
public TreeNode deserialize(String data) {
String[] split = data.split(" ");
if (split.length == 0) return null;
String top = split[0];
if (top.equals("null")) {
return null;
}
TreeNode root = new TreeNode(toInt(top));
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int index = 1;
while (!queue.isEmpty() && index < split.length) {
TreeNode poll = queue.poll();
String left = split[index++];
String right = split[index++];
if (!left.equals("null")) {
TreeNode leftNode = new TreeNode(toInt(left));
poll.left = leftNode;
queue.add(leftNode);
}
if (!right.equals("null")) {
TreeNode rightNode = new TreeNode(toInt(right));
poll.right = rightNode;
queue.add(rightNode);
}
}
return root;
}
private int toInt(String str) {
return Integer.parseInt(str);
}
Leetcode94:输出二叉树的中序遍历序列(中序
递归
迭代
medium)
题解:
法1:递归(前中后三种遍历都一样的套路)
- 时间复杂度:O(n)。递归函数 T(n)=2⋅T(n/2)+1。
- 空间复杂度:最坏情况下需要空间O(n),平均情况为O(logn)。
法2:使用栈进行迭代
class Solution {
public List < Integer > inorderTraversal(TreeNode root) {
List < Integer > res = new ArrayList < > ();
helper(root, res);
return res;
}
public void helper(TreeNode root, List < Integer > res) {
if (root == null) return;
helper(root.left, res);
res.add(root.val);
helper(root.right, res);
}
}
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
//这里依次压入左边界
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
//这里每次弹出一个节点后,就压入该节点的右节点
cur = stack.pop();
list.add(cur.val);
cur = cur.right;
}
}
return list;
}
}
Leetcode98:判断一棵二叉树是否是BST(BST
中序
medium
)
给定一个二叉树,判断其是否是一个有效的二叉搜索树
错误的解法:对于节点node,如果1.node-val > node->left->val; 2.node->val < node->right->val;那么继续判断左右子树是否满足该性质,如果满足,那么该树就是一棵BST。这种方法只满足局部BST性质(可以举例)实际上不仅右子结点要大于该节点,整个右子树的元素都应该大于该节点。
正确的解法:
法1:递归:我们需要在遍历树的同时保留结点的上界与下界,在比较时不仅比较子结点的值,也要与上下界比较。首先将结点的值与上界和下界(如果有)比较。然后,对左子树和右子树递归进行该过程。
法2:迭代:通过使用栈,上面的递归法可以转化为迭代法。这里使用深度优先搜索,比广度优先搜索要快一些。
法3:中序遍历,如果是BST,那么中序遍历序列一定是递增的序列,只需要用一个变量维护已经遍历过的序列的最后一个值prev,通过比较当前节点的值和prev,如果依然满足递增的性质,那么继续判断,否则返回false
//递归方法
class Solution {
public boolean isValidBST(TreeNode root) {
return helper(root, null, null);
}
public boolean helper(TreeNode node, Integer lower, Integer upper) {
if (node == null) return true;
int val = node.val;
if (lower != null && val <= lower) return false;
if (upper != null && val >= upper) return false;
if (! helper(node.right, val, upper)) return false;
if (! helper(node.left, lower, val)) return false;
return true;
}
}
//迭代法
class Solution {
LinkedList<TreeNode> stack = new LinkedList();
LinkedList<Integer> uppers = new LinkedList(),lowers = new LinkedList();
public void update(TreeNode root, Integer lower, Integer upper) {
stack.add(root);
lowers.add(lower);
uppers.add(upper);
}
public boolean isValidBST(TreeNode root) {
Integer lower = null, upper = null, val=null;
update(root, lower, upper);
while (!stack.isEmpty()) {
root = stack.poll();
lower = lowers.poll();
upper = uppers.poll();
if (root == null) continue;
val = root.val;
if (lower != null && val <= lower) return false;
if (upper != null && val >= upper) return false;
update(root.right, val, upper);
update(root.left, lower, val);
}
return true;
}
}
//中序遍历
class Solution {
public boolean isValidBST(TreeNode root) {
Stack<TreeNode> stack = new Stack();
double inorder = - Double.MAX_VALUE;
while (!stack.isEmpty() || root != null) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
// If next element in inorder traversal
// is smaller than the previous one
// that's not BST.
if (root.val <= inorder) return false;
inorder = root.val;
root = root.right;
}
return true;
}
}
《剑指offer》面试题33:一个数组是否是BST的后序遍历序列(BST
后序
)
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同
题解:左->右->根 ---> 小->大->中
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence.length == 0) return false;
int end = sequence.length - 1;
return verify(sequence,0,end);
}
public boolean verify(int[] sequence,int start,int end){
if(start>end){
return true;
}
//后续遍历,最后一个元素就是根节点
//根节点之前比根节点大的数都是右子树上的节点
int i = end-1;
while(i>=start && sequence[end]<sequence[i]){
i--;
}
//当向左遍历遇见比根节点小的数,就是左子树;
//如果发现有比根节点大的节点,就说明不是二叉搜索树后序遍历
int j=i;
for(;j>=start;j--){
if(sequence[j] >sequence[end]){
return false;
}
}
//然后递归判断左子树与右子树
return verify(sequence,0,i)&&verify(sequence,i+1,end-1);
}
}
《剑指offer》面试题36:BST转双链表(BST
中序
双链表
)
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向
题解:方法一:非递归版
1.核心是中序遍历的非递归算法。
2.修改当前遍历节点与前一遍历节点的指针指向。
方法二:递归版
1.将左子树构造成双链表,并返回链表头节点。
2.定位至左子树双链表最后一个节点。
3.如果左子树链表不为空的话,将当前root追加到左子树链表。
4.将右子树构造成双链表,并返回链表头节点。
5.如果右子树链表不为空的话,将该链表追加到root节点之后。
6.根据左子树链表是否为空确定返回的节点。
方法三:改进递归版
思路与方法二中的递归版一致,仅对第2点中的定位作了修改,新增一个全局变量记录左子树的最后一个节点。
//方法一:中序遍历,结合中序遍历栈实现方式
public TreeNode ConvertBSTToBiList(TreeNode root) {
if(root==null)
return null;
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode p = root;
TreeNode pre = null;// 用于保存中序遍历序列的上一节点
boolean isFirst = true;
while(p!=null||!stack.isEmpty()){
while(p!=null){
stack.push(p);
p = p.left;
}
p = stack.pop();//栈中弹出的节点当前节点 p 就是下次循环的前一个节点pre
if(isFirst){//这只执行一次
root = p;// 将中序遍历序列中的第一个节点记为链表头结点root
pre = root;
isFirst = false;
}else{
pre.right = p;//前一个节点的右指针指向当前节点
p.left = pre;//当前节点的左指针指向前一个节点
pre = p;//让前一个节点指向当前节点开始下次循环
}
//处理右节点
p = p.right;
}
return root;
}
方法二:递归版
public TreeNode Convert(TreeNode root) {
if(root==null)
return null;
if(root.left==null&&root.right==null)
return root;
// 1.将左子树构造成双链表,并返回链表头节点
TreeNode left = Convert(root.left);
TreeNode p = left;
// 2.定位至左子树双链表最后一个节点
while(p!=null&&p.right!=null){
p = p.right;
}
// 3.如果左子树链表不为空的话,将当前root追加到左子树链表
if(left!=null){
p.right = root;
root.left = p;
}
// 4.将右子树构造成双链表,并返回链表头节点
TreeNode right = Convert(root.right);
// 5.如果右子树链表不为空的话,将该链表追加到root节点之后
if(right!=null){
right.left = root;
root.right = right;
}
return left!=null?left:root;
}
方法三:改进递归版
解题思路:
思路与方法二中的递归版一致,仅对第2点中的定位作了修改,新增一个全局变量记录左子树的最后一个节点。
// 记录子树链表的最后一个节点,终结点只可能为只含左子树的非叶节点与叶节点
protected TreeNode leftLast = null;
public TreeNode Convert(TreeNode root) {
if(root==null)
return null;
if(root.left==null&&root.right==null){
leftLast = root;// 最后的一个节点可能为最右侧的叶节点
return root;
}
// 1.将左子树构造成双链表,并返回链表头节点
TreeNode left = Convert(root.left);
// 3.如果左子树链表不为空的话,将当前root追加到左子树链表
if(left!=null){
leftLast.right = root;
root.left = leftLast;
}
leftLast = root;// 当根节点只含左子树时,则该根节点为最后一个节点
// 4.将右子树构造成双链表,并返回链表头节点
TreeNode right = Convert(root.right);
// 5.如果右子树链表不为空的话,将该链表追加到root节点之后
if(right!=null){
right.left = root;
root.right = right;
}
return left!=null?left:root;
}
Leetcode108:有序数组转BST(BST
分治
easy
)
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
题解:使用递归
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
if(nums.length == 0){
return null;
}
return sortedArrayToBST(nums,0,nums.length - 1);
}
private TreeNode sortedArrayToBST(int[] nums,int l,int h){
if(l > h){
return null;
}
int m = l + (h - l)/2;
TreeNode root = new TreeNode(nums[m]);
root.left = sortedArrayToBST(nums,l,m - 1);
root.right = sortedArrayToBST(nums,m + 1,h);
return root;
}
}
Leetcode109:单链表转BST(BST
中序
单链表
medium
)
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
法1:与上题数组相同方法
法2:使用快慢指针和递归
public TreeNode sortedListToBST(ListNode head) {
if(head == null){
return null;
}
int size = 0;
ListNode runner = head;
node = head;
while(runner != null){
runner = runner.next;
size ++;
}
return inorderHelper(0, size - 1);
}
public TreeNode inorderHelper(int start, int end){
if(start > end){
return null;
}
int mid = start + (end - start) / 2;
TreeNode left = inorderHelper(start, mid - 1);
TreeNode treenode = new TreeNode(node.val);
treenode.left = left;
node = node.next;
TreeNode right = inorderHelper(mid + 1, end);
treenode.right = right;
return treenode;
}
public TreeNode sortedListToBST(ListNode head) {
if (head == null) return null;
if (head.next == null) return new TreeNode(head.val);
ListNode slow = head, pre = null, fast = head;
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
pre.next = null; //cut left sub list here
TreeNode n = new TreeNode(slow.val);
n.left = sortedListToBST(head);
n.right = sortedListToBST(slow.next);
return n;
}
《剑指offer》面试题54:BST第k小的节点(BST
中序
)
给定一个二叉搜索树,编写一个函数 kthSmallest
来查找其中第 k 个最小的元素。
题解:
法1:涉及到二叉搜索树的中序遍历就是有序的,所以可以使用栈结构实现,实现中序遍历需要顺序压入左边界,然后弹出一个节点并压入该节点的右节点
法2:递归
TreeNode KthNode(TreeNode pRoot, int k)
{
//首先中序遍历这颗二叉树
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode p = pRoot;
int i = 0;
while(p != null || !stack.isEmpty()){
if(p != null){
stack.push(p);
//依次放入左节点
p = p.left;
}else{
//最先弹出的是左节点,也就是当前最小的节点
TreeNode node = stack.pop();
i++;
if(i == k){
return node;
}
//指针指向弹出节点的右节点,如果为空,则比他大的是栈中他的父节点
//如果不为空,说明比他大的是他的右子树中最左的那个节点
p = node.right;
}
}
return null;
}
//递归方法
TreeNode KthNode(TreeNode pRoot, int k){
if(pRoot != null){
TreeNode node = KthNode(pRoot.left, k);
if(node != null)
return node;
index ++;
//只有在中间的时候才是遍历这个节点
if(index == k)
return pRoot;
node = KthNode(pRoot.right, k);
if(node != null)
return node;
}
return null;
}
Leetcode116:填充同一层的兄弟节点(BFS
前序
medium
)
给一个二叉树,填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。初始状态下,所有 next 指针都被设置为 NULL
。
class Solution {
public Node connect(Node root) {
Node res = root;
while(root != null && root.left != null) {
Node cur = root;
//这里每一次都从该行的第一个节点开始
while(cur != null) {
cur.left.next = cur.right;
cur.right.next = cur.next == null ? null : cur.next.left;
cur = cur.next;
}
//每次记录行首节点,开始下次循环
root = root.left;
}
return res;
}
}
《剑指offer》面试题32(题目一):按层不分行输出二叉树(BFS
)
从上往下打印出二叉树的每个节点,同层节点从左至右打印
题解:广度优先搜索,使用队列
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> list = new ArrayList<Integer>();
Queue<Integer> queue = new LinkedList<Integer>();
if(root == null ){
return null;
}
queue.put(root);
TreeNode node = new TreeNode(0);
while(queue.peek()!=null){
node = queue.poll();
list.add(node.val);
if(node.left!=null){
queue.put(node.left);
}
if(node.right!=null){
queue.put(node.right);
}
}
return list;
}
}