这篇博客记录刷题第15天的学习心得与笔记。
231. 2的幂
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
示例 1:
输入: 1
输出: true
解释: 20 = 1示例 2:
输入: 16
输出: true
解释: 24 = 16示例 3:
输入: 218
输出: false
- 这题很简单,常规思路是对2的幂系数由小到大遍历,看能否找到与给定n相等的2的幂,代码如下:
class Solution {
public:
bool isPowerOfTwo(int n) {
int i = 0;
if (n < 1) return false;
while (pow(2, i) <= n) {
if (n == pow(2,i))
return true;
else
i++;
}
return false;
}
};
-
更巧妙的算法是通过位运算实现[1],解法1:我们发现: 2 n 2^n 2n 写成二进制为 10 ⋯ 0 ( n 个 0 ) 10 \cdots 0(n个0) 10⋯0(n个0),而 2 n − 1 2^{n-1} 2n−1 写成二进制为 01 ⋯ 1 ( n 个 1 ) 01 \cdots 1(n个1) 01⋯1(n个1),因此与运算 & 为0。通过
return (n & n-1 == 0);
可以判断给定 n是否是2的幂。 -
位运算解法2:2的幂次方在二进制下,只有1位是1,其余全是0。例如:8—00001000。负数的在计算机中二进制表示为补码(原码->正常二进制表示,原码按位取反(0-1,1-0),最后再+1。然后两者进行与操作,得到的肯定是原码中最后一个二进制的1。例如8&(-8)->00001000 & 11111000 得 00001000,即8。 所以通过
return (n > 0) && (n & -n) == n;
也可以判断。 -
位运算解法3:一个整数如果是2的幂次方,必定可以被最大的2的整数次幂,也就是2的31次幂整除。
return (n>0) && (1<<31) % n == 0;
注意这里用的是移位运算,把二进制数进行左右移位。左移1位,扩大2倍;右移1位,缩小2倍。
235. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉搜索树中。
首先回顾一下二叉搜索树[2, 5]如下:
二叉搜索树(Binary Search Tree),(又:二叉查找树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树。
二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。
因此我们可以利用二叉搜索树的特性,如果节点 p 和 q 的值都小于根节点,则 p 和 q 必定都位于根节点的左子树,以左子树为根节点继续递归搜索;如果节点 p 和 q 的值都大于根节点,则 p 和 q 必定都位于根节点的右子树,以右子树为根节点继续递归搜索;否则 p 和 q 必定是一大一小,位于根节点两侧,最近公共祖先就是根节点。代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
if (p->val < root->val && q->val < root->val)
return lowestCommonAncestor(root->left, p, q);
if (p->val > root->val && q->val > root->val)
return lowestCommonAncestor(root->right, p, q);
return root;
}
};
236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 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。因为根据定义最近公共祖先节点可以为节点本身。说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉树中。
- 递归法[3],思路如下:
(1) 如果当前结点 r o o t root root 等于nullptr
,则直接返回nullptr
(2) 如果 r o o t root root等于 p 或者 q,那这棵树一定返回 p 或者 q
(3) 然后递归左右子树,因为是递归,使用函数后可认为左右子树已经算出结果,用 l e f t left left 和 r i g h t right right 表示
(4) 此时若 l e f t left left 为空,那最终结果只要看 r i g h t right right;若 r i g h t right right 为空,那最终结果只要看 l e f t left left
(5) 如果 l e f t left left 和 r i g h t right right 都非空,因为只给了 p 和 q 两个结点,都非空,说明一边一个,因此 r o o t root root 是他们的最近公共祖先
(6) 如果 l e f t left left 和 r i g h t right right都为空,则返回空
时间复杂度是 O ( n ) O(n) O(n),每个结点最多遍历一次,空间复杂度是 O ( n ) O(n) O(n):需要系统栈空间。代码如下:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr) {
return root;
}
if (root == p || root == q) {
return root;
}
TreeNode *left = lowestCommonAncestor(root->left, p, q);
TreeNode *right = lowestCommonAncestor(root->right, p, q);
if(left != nullptr && right != nullptr) {
return root;
}
else if (left != nullptr) {
return left;
}
else if (right != nullptr) {
return right;
}
return nullptr;
}
};
- 非递归法,存储父节点[4]
我们可以用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从 p 结点开始不断往上跳,并记录已经访问过的节点,再从 q 节点开始不断往上跳,如果碰到已经访问过的节点,那么这个节点就是我们要找的最近公共祖先。算法如下:
- 从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
- 从 p 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
- 同样,我们再从 q 节点开始不断往它的祖先移动,如果有祖先已经被访问过,即意味着这是 p 和 q 的深度最深的公共祖先,即 LCA 节点。
贴出官方C++代码如下:
class Solution {
public:
unordered_map<int, TreeNode*> fa; //哈希表存储<子节点val, 父节点>
unordered_map<int, bool> vis; //祖先节点是否访问
void dfs(TreeNode* root) {
//深度遍历二叉树,记录每个节点的父节点
if (root->left != nullptr) {
fa[root->left->val] = root;
dfs(root->left);
}
if (root->right != nullptr) {
fa[root->right->val] = root;
dfs(root->right);
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
fa[root->val] = nullptr;
dfs(root);
while (p != nullptr) {
vis[p->val] = true; //已访问
p = fa[p->val]; //将p父节点赋给p
}
while (q != nullptr) {
if (vis[q->val]) //已访问过
return q;
q = fa[q->val]; //将q父节点赋给q
}
return nullptr;
}
};
参考
[1] https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/comments/
[2] 二叉搜索树-百度百科
[3] https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/c-jing-dian-di-gui-si-lu-fei-chang-hao-li-jie-shi-
[4] https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/er-cha-shu-de-zui-jin-gong-gong-zu-xian-by-leetc-2/
[5] 二叉查找树(BST)及二叉树的遍历