题目:
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例
给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10
抛砖引玉
本题为链表转换成平衡二叉树,之前做过将有序数组转换平衡二叉树:
20200703:将有序数组转换为二叉搜索树 (难度:简单)
可以发现数组转换成平衡二叉树的主要逻辑为找到数组中点。
再回到本题,能想到最简单的方法就是讲链表转换成数组,然后就可以借用之前数组的逻辑完成了
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {ListNode} head
* @return {TreeNode}
*/
var sortedListToBST = function (head) {
let nums = []
while (head) {
nums.push(head.val)
head = head.next
}
return build(nums, 0, nums.length - 1)
function build(nums, left, right) {
if (left > right) return null
let mid = parseInt((left + right) / 2, 10)
let node = new TreeNode(nums[mid])
node.left = build(nums, left, mid - 1)
node.right = build(nums, mid + 1, right)
return node
}
}
分治
相较于数组,链表没有索引,通过一个元素的 next 指向下一个元素
但是同样,我们也可以变量链表找到链表的中心位置
那么问题就转换成了求链表的中心位置:
官方题解:快慢指针法:
- fast 每次移动两步
- slow 每次移动一步
- | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
fast | - | s1 | - | s2 | - | s3 |
slow | s1 | s2 | s3 | - | - | - |
s i s_i si表示指针移动是次数,可以发现 fast 移动到末尾时,slow 刚好在中点
/**
* @param {ListNode} head
* @return {TreeNode}
*/
var sortedListToBST = function (head) {
return build(head, null)
function build(left, right) {
if (left === right) return null
let mid = getMedian(left, right)
let node = new TreeNode(mid.val)
node.left = build(left, mid)
node.right = build(mid.next, right)
return node
}
function getMedian(left, right) {
let fast = left,
slow = left
while (fast != right && fast.next != right) {
fast = fast.next
fast = fast.next
slow = slow.next
}
return slow
}
}
分治 + 中序遍历优化
上面两种方法:
- 将链表转换成数组,借用数组所有查询根节点
- 查找链表中心位,利用快慢指针查询链表中心位
可以假设链表有索引和长度属性(手动统计出来)
那么可以和数组一样,每次选择中点做根节点
使用方法 1 二分遍历链表,遇到节点就生成新的二叉树节点
(每个点开始都是独立的,指向他和他指向的节点都还没生产)
生产的二叉树节点的 left 子树与 right 子树通过递归依次生成
- 递归参数(left,right),生成子树的区间
- 递归结束,区间交错,说明区间的子元素遍历完了
/**
* @param {ListNode} head
* @return {TreeNode}
*/
var sortedListToBST = function (head) {
let globalHead = head,
len = getLength(head)
return build(0, len - 1)
function getLength(head) {
let i = 0
while (head != null) {
++i
head = head.next
}
return i
}
function build(left, right) {
if (left > right) return null
let mid = parseInt((left + right + 1) / 2, 10)
let node = new TreeNode()
node.left = build(left, mid - 1)
node.val = globalHead.val
globalHead = globalHead.next
node.right = build(mid + 1, right)
return node
}
}
博客: 小书童博客
每天的每日一题,写的题解会同步更新到公众号一天一大 lee 栏目
欢迎关注留言
公号: 坑人的小书童