Leetcode——二叉树展开为链表 / 将有序数组转换为二叉搜索树 / 有序链表转换二叉搜索树
Posted Yawn,
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode——二叉树展开为链表 / 将有序数组转换为二叉搜索树 / 有序链表转换二叉搜索树相关的知识,希望对你有一定的参考价值。
1. 二叉树展开为链表
(1)递归
- 首先将根节点的左子树变成链表
- 其次将根节点的右子树变成链表
- 最后将变成链表的右子树放在变成链表的左子树的最右边
模拟一下:
1
/ \\
2 5
/ \\ \\
3 4 6
//左走到最里面,然后弹出来
2
/ \\
3 4
//用temp存储4,将3连在2的右边
2
\\
3 temp -> 4
//找到root最右边节点,也就是3,将4连上去,这就得到了2-3-4
2
\\
3
\\
4
同样可得到5-6,然后连接起来即可
代码实现:
/**
* 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 void flatten(TreeNode root) {
if(root == null){
return ;
}
//将根节点的左子树变成链表
flatten(root.left);
//将根节点的右子树变成链表
flatten(root.right);
//把树的右边换成左边的链表
TreeNode temp = root.right;
root.right = root.left;
//记得要将左边置空
root.left = null;
//找到left的最右边的节点
while(root.right != null)
root = root.right;
//把右边的链表接到刚才树的最右边的节点
root.right = temp;
}
}
(2)先序遍历(迭代)
可以发现展开的顺序其实就是二叉树的先序遍历。
- 将左子树插入到右子树的地方
- 将原来的右子树接到左子树的最右边节点
- 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
1
/ \\
2 5
/ \\ \\
3 4 6
//将 1 的左子树插入到右子树的地方
1
\\
2 5
/ \\ \\
3 4 6
//将原来的右子树接到左子树的最右边节点
1
\\
2
/ \\
3 4
\\
5
\\
6
//将 2 的左子树插入到右子树的地方
1
\\
2
\\
3 4
\\
5
\\
6
//将原来的右子树接到左子树的最右边节点
1
\\
2
\\
3
\\
4
\\
5
\\
6
......
public void flatten(TreeNode root) {
while (root != null) {
//左子树为 null,直接考虑下一个节点
if (root.left == null) {
root = root.right;
} else {
// 找左子树最右边的节点
TreeNode pre = root.left;
while (pre.right != null) {
pre = pre.right;
}
//将原来的右子树接到左子树的最右边节点
pre.right = root.right;
// 将左子树插入到右子树的地方
root.right = root.left;
root.left = null;
// 考虑下一个节点
root = root.right;
}
}
}
(3)直接改变链表指向(递归)
们知道题目给定的遍历顺序其实就是先序遍历的顺序,所以我们能不能利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。
先序遍历的顺序是 1 2 3 4 5 6。
遍历到 2,把 1 的右指针指向 2。1 -> 2 3 4 5 6。
遍历到 3,把 2 的右指针指向 3。1 -> 2 -> 3 4 5 6。
一直进行下去似乎就解决了这个问题。但现实是残酷的,原因就是我们把 1 的右指针指向 2,那么 1 的原本的右孩子就丢失了,也就是 5 就找不到了。
解决方法的话,我们可以逆过来进行。
我们依次遍历 6 5 4 3 2 1,然后每遍历一个节点就将当前节点的右指针更新为上一个节点。
遍历到 5,把 5 的右指针指向 6。6 <- 5 4 3 2 1。
遍历到 4,把 4 的右指针指向 5。6 <- 5 <- 4 3 2 1。
1
/ \\
2 5
/ \\ \\
3 4 6
这样就不会有丢失孩子的问题了,因为更新当前的右指针的时候,当前节点的右孩子已经访问过了。
而 6 5 4 3 2 1 的遍历顺序其实变形的后序遍历,遍历顺序是右子树->左子树->根节点。
public void flatten(TreeNode root) {
Stack<TreeNode> toVisit = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
while (cur != null || !toVisit.isEmpty()) {
while (cur != null) {
toVisit.push(cur); // 添加根节点
cur = cur.right; // 递归添加右节点
}
cur = toVisit.peek(); // 已经访问到最右的节点了
// 在不存在左节点或者右节点已经访问过的情况下,访问根节点
if (cur.left == null || cur.left == pre) {
toVisit.pop();
/**************修改的地方***************/
cur.right = pre;
cur.left = null;
/*************************************/
pre = cur;
cur = null;
} else {
cur = cur.left; // 左节点还没有访问过就先访问左节点
}
}
}
2. 将有序数组转换为二叉搜索树
(1)递归
- 根据升序数组,恢复一棵高度平衡的BST。
- BST的中序遍历是升序的,因此本题等同于根据中序遍历的序列恢复二叉搜索树。因此我们可以以升序序列中的任一个元素作为根节点,以该元素左边的升序序列构建左子树,以该元素右边的升序序列构建右子树,这样得到的树就是一棵二叉搜索树啦
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return dfs(nums, 0, nums.length - 1);
}
private TreeNode dfs(int[] nums, int lo, int hi) {
if (lo > hi) {
return null;
}
// 以升序数组的中间元素作为根节点 root。
int mid = lo + (hi - lo) / 2;
TreeNode root = new TreeNode(nums[mid]);
// 递归的构建 root 的左子树与右子树。
root.left = dfs(nums, lo, mid - 1);
root.right = dfs(nums, mid + 1, hi);
return root;
}
}
3. 有序链表转换二叉搜索树
(1)快慢指针 + 递归
- 就是找链表中间节点作为根节点,然后再找中点两边的子链表的中点,一直递归下去直到子链表为空
- 特例处理:如果head为空返回空,如果head.next为空返回值为head.val的树节点
- 利用快慢指针找链表中间节点(slow每次走一步,fast每次走两步,循环停止时slow指向中间节点)
- 同时记录一下slow的前一个节点pre,这是为后面的断开操作做准备
- 创建树的根节点,把slow的值赋给它,并断开链表中间节点和左边子链表的连接
- 递归链表中间节点左右两边的子链表,找子链表的中间节点,再找子链表的子链表的中间节点,如此循环往复,直到符合特例处理的条件递归返回
class Solution {
public TreeNode sortedListToBST(ListNode head) {
//1. 特例处理
if (head == null){
return null;
}else if(head.next == null){
return new TreeNode(head.val);
}
//2. 利用快慢指针找链表中间节点
ListNode slow = head, fast = head;
ListNode pre = null;
while( fast != null && fast.next != null){
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
//3. 创建树的根节点,并断开相应连接
TreeNode root = new TreeNode(slow.val);
pre.next = null;
//4. 递归链表中间节点左右两边的子链表
root.left = sortedListToBST(head);
root.right = sortedListToBST(slow.next);
return root;
}
}
(2)list
实际上还可以把链表中的值全都存储到集合list中,每次把list分为两部分,和上面原理一样
public TreeNode sortedListToBST(ListNode head) {
List<Integer> list = new ArrayList<>();
//把链表节点值全部提取到list中
while (head != null) {
list.add(head.val);
head = head.next;
}
return sortedListToBSTHelper(list, 0, list.size() - 1);
}
TreeNode sortedListToBSTHelper(List<Integer> list, int left, int right) {
if (left > right)
return null;
//把list中数据分为两部分
int mid = left + (right - left) / 2;
TreeNode root = new TreeNode(list.get(mid));
root.left = sortedListToBSTHelper(list, left, mid - 1);
root.right = sortedListToBSTHelper(list, mid + 1, right);
return root;
}
以上是关于Leetcode——二叉树展开为链表 / 将有序数组转换为二叉搜索树 / 有序链表转换二叉搜索树的主要内容,如果未能解决你的问题,请参考以下文章