先說一個消息,爲了方便互相交流學習,青銅三人行建了個微信羣,感興趣的夥伴可以掃碼加下面的小助手抱你入羣哦!
每週一題,代碼無敵~這周「青銅三人行」帶了一個二叉樹的問題:
在二叉樹中增加一行
青銅三人行——每週一題@在二叉樹中增加一行
力扣題目
給定一個二叉樹,根節點爲第1層,深度爲 1。在其第 d 層追加一行值爲 v 的節點。
添加規則:給定一個深度值 d (正整數),針對深度爲 d-1 層的每一非空節點 N,爲 N 創建兩個值爲 v 的左子樹和右子樹。
將 N 原先的左子樹,連接爲新節點 v 的左子樹;將 N 原先的右子樹,連接爲新節點 v 的右子樹。
如果 d 的值爲 1,深度 d - 1 不存在,則創建一個新的根節點 v,原先的整棵樹將作爲 v 的左子樹。
示例 1:
輸入:
二叉樹如下所示:
4
/ \
2 6
/ \ /
3 1 5
v = 1
d = 2
輸出:
4
/ \
1 1
/ \
2 6
/ \ /
3 1 5
示例 2:
輸入:
二叉樹如下所示:
4
/
2
/ \
3 1
v = 1
d = 3
輸出:
4
/
2
/ \
1 1
/ \
3 1
注意
- 輸入的深度值 d 的範圍是:[1,二叉樹最大深度 + 1]
- 輸入的二叉樹至少有一個節點。
前置知識
題目的關鍵在於找到對應的位置插入節點。而要找到這個“位置”的關鍵就在於找到插入層的所有父節點。因此對於二叉樹的遍歷不太瞭解的小夥伴,建議先去了解一下二叉樹的各種遍歷方式:
遞歸解法
對於二叉樹的遍歷,遞歸是一種常用的解法。在這道題中 helen 就使用了遞歸的方法來找到對應的層級,然後進行節點插入。
設定滿足條件爲進行 d-1 次遞歸,當滿足時,這個節點就爲需要插入節點的父節點。值的注意的是,要記得在每次遞歸時同時對左子樹和右子樹遞歸:
var addOneRow = function (root, v, d) {
if (d === 1) {
const head = {
val: v,
left: root,
right: null,
}
return head;
}
function traverse (root, count) {
if (!root) return;
if (count === d - 1) {
root.left = {
val: v,
left: root.left,
right: null,
};
root.right = {
val: v,
right: root.right,
left: null,
}
} else {
traverse(root.left, count+1);
traverse(root.right, count+1);
}
}
traverse(root,1);
return root;
}
遍歷解法
函數遞歸是一種簡化、理解遍歷的方法。但是遞歸的缺點在於有可能在遞歸層級過深的時候造成爆棧的。在所有的程序中,只要有遞歸解法,就一定會存在遍歷解法。因此書香帶來了遍歷的思路。
在其中,先遍歷二叉樹找到 d 層節點,存放入數組中,再次遍歷數組中的節點,插入新的節點:
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {number} v
* @param {number} d
* @return {TreeNode}
*/
var addOneRow = function (root, v, d) {
function findDNodes(root, d) {
let resultNodes = [root];
for (let i = 1; i < d; i++) {
resultNodes = resultNodes
.filter(node => node)
.reduce((sofar, curr) => {
return sofar.concat([curr.left, curr.right]);
}, []);
}
return resultNodes;
}
if (d === 1) {
const insertNode = new TreeNode(v);
insertNode.left = root;
return insertNode;
}
const lastNodes = findDNodes(root, d - 1);
lastNodes
.filter(node => node)
.forEach(node => {
if (node) {
const insertNodeL = new TreeNode(v);
const insertNodeR = new TreeNode(v);
insertNodeL.left = node.left;
insertNodeR.right = node.right;
node.left = insertNodeL;
node.right = insertNodeR;
}
})
return root;
};
extra
最後,依然是曾大師的 go 語言 show time, 他同樣是遞歸的思路哦~
func addOneRow(root *TreeNode, v int, d int) *TreeNode {
head := root
// 跟節點換掉
if(d==1){
trRoot := new(TreeNode)
trRoot.Val = v
trRoot.Left= root
trRoot.Right =nil
return trRoot;
}
inorderDepth(root,1,d,v)
return head
}
func inorderDepth(root *TreeNode,depth int,respected int,v int) {
// d-1 層爲空節點
if(root == nil){
return
}
if(respected == depth+1){
left := root.Left
right := root.Right
trLeft := new(TreeNode)
trLeft.Val = v
trLeft.Left=left
trLeft.Right = nil
root.Left = trLeft
trRight := new(TreeNode)
trRight.Val = v
trRight.Right=right
trRight.Left = nil
root.Right = trRight
return
}
// 當深度等於或者超過respected-1時,就不需要遍歷了
if(respected < depth+1){
return
}
if(root != nil){
inorderDepth(root.Left,depth+1,respected,v)
inorderDepth(root.Right,depth+1,respected,v)
}
}
同樣還是效果驚人:
結束
二叉樹是相對鏈表更復雜一點的數據結構。它的典型用法是對節點定義一個標記函數,將一些值與每個節點相關係。這樣標記的二叉樹就可以實現二叉搜索樹和二叉堆,並應用於高效率的搜索和排序。瞭解這些數據結構會有助於我們實現更高效的程序哦~下週見!
三人行