掘金团队号上线,助你 Offer 临门! 点击 查看详情
二叉树的最近公共祖先(题号235、236)
题目
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T
的两个节点 p
、q
,最近公共祖先表示为一个节点 x
,满足 x
是 p
、q
的祖先且 x
的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
- 树中节点数目在范围
[2, 105]
内。 -109 <= Node.val <= 109
- 所有
Node.val
互不相同 。 p != q
p
和q
均存在于给定的二叉树中。
链接
解释
这是LeetCode的235和236两题,由于内容差不多,就放在一起了。
首先说说普通二叉树的解法,这种方法就有点不好理解了,但代码看上去十分简洁易懂,首先,判断当前节点是不是null
如果是,直接返回,这一点毋庸置疑。
之后判断root
是否等于q
或者p
,如果等于两者其中之一,就返回root
节点。之后递归调用该函数,传入左节点和右节点,拿到左边和右边的值,如果左边的值是null
,那证明q
和p
都在右边,如果右边的值是null
,那么返回左边,如果两边都是null
,那么直接返回root
节点,因为显然root
节点就是q
和p
的最近公共祖先了。
如果还是看不懂的话,就自己画图想想,或者去LeetCode看看答案,里面有图解,应该会有所帮助。
解决完普通二叉树后,二叉搜索树就简单很多了,因为可以利用它左右节点的大小关系来进行更简单的判断,条件也更清楚,而且这里可以使用递归和遍历两种方法,可操作性也更多了。
自己的答案
无
更好的方法(普通二叉树-递归)
var lowestCommonAncestor = function(root, p, q) {
if (root === null) return null
if (root.val === q.val || root.val === p.val) return root
var left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if (left === null) {
return right
} else {
if (right === null) {
return left
} else {
return root
}
}
};
这里为了更直观,在判断上采用了root === null
的写法,其实!root
也是可以的。
更好的方法(二叉搜索树-递归)
var lowestCommonAncestor = function(root, p, q) {
if (!root) return root
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left, p, q)
}
if (root.val < p.val && root.val < q.val) {
return lowestCommonAncestor(root.right, p, q)
}
return root
};
此为递归解法,如果根节点的值同时小于p
和q
的值,那么证明p
和q
在根节点的右边,之后递归调用右节点的值;同理可得,如果同时大于p
和q
,那么证明p
和在根节点的左侧,取左子树进行下一步递归,最后不能满足同时大于或者小于`p`和
值,那么证明当前的root
就是二者的最近公共祖先节点,返回即可。
更好的方法2(二叉搜索树-遍历)
var lowestCommonAncestor = function(root, p, q) {
if (!root) return root
while (root) {
if (root.val > p.val && root.val > q.val) {
root = root.left
} else if (root.val < p.val && root.val < q.val) {
root = root.right
} else {
return root
}
}
};
此写法为遍历写法,整体逻辑和递归类似,可以理解为一种不同的写法,在此不多做赘述,看看即可。
Pow(x, n)(题号50)
题目
实现 pow(x, n)
,即计算 x
的 n
次幂函数(即,x
的n
次方)。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
提示:
-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= xn <= 104
链接
解释
这块就有点涉及到数学知识了,乍一看是很简单的一题,一直n-1
即可,但实时告诉我没这么简单,在提交时发现n
可能会巨大,然后就内存不足了,直接GG。
那么这个时候就要考虑到时间复杂度了,n-1
的时间复杂度时O(n),此时显然需要降低时间复杂度,数学渣渣的我自然时没办法想出来的,讲真,看了答案后才知道,如果不看答案我应该永远都不会想到这种方法。
主要的逻辑很简单,利用幂的特性,如果幂指数为n
,那么可以利用n / 2
来减少一次递归或者遍历,此时之需要将本来的指改为其平方即可,由x
变为x * x
,这就完事了,那么具体到代码中去可能还需要考虑n
的奇偶性,如果时奇数,那么需要用x
来乘上上一步的结果,然后将n
改为n-1
,继续进行下一步循环。
到了这里这题就很简单了,直接看答案即可。
自己的答案
var myPow = function(x, n) {
if (n === 0) return 1
var res = 1
flag = n > 0
n = Math.abs(n)
while (n > 0) {
if (n === 1) break
if (n % 2 === 1) {
res *= x
n--
}
x = x * x
n = n / 2
}
return flag ? res * x : 1 / (res * x)
};
有点小丑陋,主要是在判断n
的正负上,其实使用递归调用一次就好了,这里有点小费劲。
更好的方法(遍历)
var myPow = function(x, n) {
if (n === 0) return 1
if (n < 0) return myPow(1 / x, -n)
var res = 1
while (n > 1) {
if (n % 2 === 1) {
res = res * x
n--
}
x = x * x
n = n / 2
}
return res * x
};
这里就用递归调用的方法解决的n
的正负问题
更好的方法(递归)
var myPow = function(x, n) {
if (n === 0) return 1
if (n < 0) return myPow(1 / x, -n)
return n % 2 === 1 ? x * myPow(x, n - 1) : myPow( x * x, n / 2 )
};
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇