题目:
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
思路:这和之前的一题:升序数组转换为二叉搜索树 ,是很像的,只不过现在是单链表。
因为是高度平衡,所以左右两边节点数要尽可能接近,所以要找到中点。链表的重点,可以用快慢指针获取,所以获取链表中点,以根节点作为根节点,左链表作为左子树,右链表作为右子树,对左右子树递归处理,就得到结果了。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {ListNode} head
* @return {TreeNode}
*/
var sortedListToBST = function(head) {
if (!head) return null;
const node = new ListNode();
node.next = head;
let fast = node;
let slow = node;
while (fast.next && fast.next.next) {
fast = fast.next.next;
slow = slow.next;
}
const root = new TreeNode(slow.next.val);
const right = slow.next.next;
slow.next = null;
root.left = sortedListToBST(node.next);
root.right = sortedListToBST(right);
return root;
};
题目:
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
思路:利用层序遍历,存储路径上的节点即可
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {number} sum
* @return {number[][]}
*/
var pathSum = function(root, sum) {
if (!root) return [];
const stack = [
{
node: root,
val: [root.val],
},
];
const res = [];
while (stack.length) {
const item = stack.shift();
const sumV = item.val.reduce((res, i) => res + i);
if (!item.node.left && !item.node.right) {
if (sumV === sum) {
res.push(item.val);
}
} else {
if (item.node.left) {
const val = item.val.slice();
val.push(item.node.left.val);
stack.push({
node: item.node.left,
val,
});
}
if (item.node.right) {
const val = item.val.slice();
val.push(item.node.right.val);
stack.push({
node: item.node.right,
val,
});
}
}
}
return res;
};
题目:
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
思路:根据题意,是优先根节点,然后左子树,然后右子树,这和前序遍历的遍历过程类似。
因为要展开成链表,所以在递归的时候把右子树先保存起来。为了保持对最后一个节点的引用,需要判断什么时候拿不到最后一个节点。
观察可知,如果一直遍历左节点,是可以一直拿到左节点,即最后一个节点的。所以失去对最后一个节点引用的时机就是没有左节点的时候。所以左节点如果是null,则保留当前的节点,用于下一次递归。下一次递归必定是处理右节点。
/**
* 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 {TreeNode} root
* @return {void} Do not return anything, modify root in-place instead.
*/
var flatten = function(root) {
if (!root) return null;
let head;
const search = (root) => {
if (!root) return;
const right = root.right;
if (root.left) {
root.right = search(root.left);
root.left = null;
} else {
head = root;
}
if (right) {
head.right = search(right);
}
return root;
};
return search(root);
};
题目:
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
思路:和层序遍历类似,不过每一层的遍历顺序是从右到左的,所以在往栈内推入子节点的时候,先推入右节点,然后推入左节点就行了。
/**
* // Definition for a Node.
* function Node(val, left, right, next) {
* this.val = val === undefined ? null : val;
* this.left = left === undefined ? null : left;
* this.right = right === undefined ? null : right;
* this.next = next === undefined ? null : next;
* };
*/
/**
* @param {Node} root
* @return {Node}
*/
var connect = function(root) {
if (!root) return root;
const stack = [root];
let pre = null;
while (stack.length) {
const temp = [];
stack.reduce((res, item) => {
item.next = pre;
pre = item;
if (item.right) {
temp.push(item.right);
}
if (item.left) {
temp.push(item.left);
}
return res;
}, 0);
pre = null;
stack.splice(0, Infinity, ...temp);
}
return root;
};
题目:
给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
思路:这是对上一题的优化,只能使用常数的额外空间。
可以观察到,下一轮的节点的next,其实就是下一个节点,即栈内的第一个元素,所以不用reduceRight,用正常的遍历就行了
/**
* // Definition for a Node.
* function Node(val, left, right, next) {
* this.val = val === undefined ? null : val;
* this.left = left === undefined ? null : left;
* this.right = right === undefined ? null : right;
* this.next = next === undefined ? null : next;
* };
*/
/**
* @param {Node} root
* @return {Node}
*/
var connect = function(root) {
if(!root) return null
let stack=[root]
while(stack.length){
let len=stack.length;
while(len){
let node=stack.shift();
len--
node.next=len==0?null:stack[0]
node.left&&stack.push(node.left)
node.right&&stack.push(node.right)
}
}
return root
};
题目:
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
思路:
这题,就是遍历每一行,找到每一个元素的上一行同位置的元素与上一行同位置的下一个元素,取之间的较小值,然后一直计算到最后一行,取最后一行的最小值。
当然,这是思路,具体的做法有很多。
开始的思路是类似滚轮,初始化一个等于最后一行长度的数组,这个数组记录遍历到每一行时候的每个位置的路径最小值,直到最后一行。
因为自上而下的遍历,对行从左到右遍历的话,会影响后一个值的计算,所以对行要从右到左遍历,额外空间是O(n)
/**
* @param {number[][]} triangle
* @return {number}
*/
var minimumTotal = function(triangle) {
const h = triangle.length;
if (!h) return 0;
const l = triangle[h - 1].length;
const res = new Array(l);
for (const v of triangle) {
v.reduceRight((pre, item, index) => {
if (!index) {
res[index] = item + (res[index] || 0);
} else {
res[index] =
item +
Math.min(
res[index] === undefined ? Infinity : res[index],
res[index - 1]
);
}
return pre;
}, 0);
}
return Math.min(...res);
};
再观察分析,对行的遍历也可以从反过来,即从底向上遍历,计算过程是一样的,并且因为第一行只有一个元素,所以不需要最后一步的取最小值。同时,修改原数组,也不需要额外的空间了。
/**
* @param {number[][]} triangle
* @return {number}
*/
var minimumTotal = function(triangle) {
const h = triangle.length;
if (!h) return 0;
triangle.reduceRight((pre, lineItem, linIndex) => {
if (linIndex == h - 1) return pre;
lineItem.reduceRight((pre, item, index) => {
lineItem[index] =
item +
Math.min(
triangle[linIndex + 1][index],
triangle[linIndex + 1][index + 1]
);
}, 0);
return pre;
}, 0);
return triangle[0][0];
};