1 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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
均存在于给定的二叉树中。
思路:
首先,我们判断根节点是否为空,或者是否等于其中一个节点p或q,如果是的话直接返回根节点。接着,我们分别在左子树和右子树中递归地寻找两个节点的最近公共祖先。如果左子树中找不到公共祖先,则返回右子树中的结果;如果右子树中找不到公共祖先,则返回左子树中的结果。最后,如果左右子树均有公共祖先,则返回根节点作为最近公共祖先。
代码:
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {// 如果根节点为空或者根节点等于p或q,则返回根节点if(root == nullptr || root == p || root == q) return root;// 递归地在左子树中查找p和q的公共祖先TreeNode *left = lowestCommonAncestor(root->left, p, q);// 递归地在右子树中查找p和q的公共祖先TreeNode *right = lowestCommonAncestor(root->right, p, q);// 如果左子树中无公共祖先,则返回右子树中的结果if(left == nullptr) return right;// 如果右子树中无公共祖先,则返回左子树中的结果if(right == nullptr) return left;// 如果左右子树均有公共祖先,则返回根节点return root;}
};
2 二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点 root
和要插入树中的值 value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5 输出:[4,2,7,1,3,5] 解释:另一个满足题目要求可以通过的树是:
示例 2:
输入:root = [40,20,60,10,30,50,70], val = 25 输出:[40,20,60,10,30,50,70,null,null,25]
示例 3:
输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5 输出:[4,2,7,1,3,5]
提示:
- 树中的节点数将在
[0, 104]
的范围内。 -108 <= Node.val <= 108
- 所有值
Node.val
是 独一无二 的。 -108 <= val <= 108
- 保证
val
在原始BST中不存在。
思路:
这道题是要将一个值为val的节点插入到二叉搜索树中。首先判断根节点是否为空,如果为空,则创建一个值为val的新节点作为根节点。然后,根据插入值val与当前节点值的大小关系,递归地将值插入到左子树或右子树中。
通常可以按照以下三个步骤进行:
-
确定递归函数的参数和返回值: 首先确定递归函数的参数,一般包括当前节点以及需要传递的其他信息,返回值通常是递归函数需要计算或返回的结果。
-
确定递归的终止条件: 确定递归函数何时应该停止递归,即递归的终止条件。在这个问题中,终止条件是当当前节点为空时,说明已经遍历到叶子节点的子节点,应该返回插入的新节点。
-
确定单层递归的逻辑: 确定每一层递归应该做什么操作。在这个问题中,我们根据插入值与当前节点值的大小关系,决定是将值插入到左子树还是右子树中,然后继续递归调用。
代码:
class Solution {
public:// 将值为val的节点插入二叉搜索树中TreeNode* insertIntoBST(TreeNode* root, int val) {// 如果根节点为空,则创建一个值为val的新节点作为根节点if(root==nullptr){TreeNode* node=new TreeNode(val);return node;}// 如果插入值小于当前节点的值,则递归地插入到左子树中if (root->val>val) root->left=insertIntoBST(root->left,val);// 如果插入值大于当前节点的值,则递归地插入到右子树中if(root->val<val) root->right=insertIntoBST(root->right,val);// 返回根节点return root;}
};
3二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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 为不同节点且均存在于给定的二叉搜索树中。
思路:
本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。
在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?
因为是有序树,所以 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。
那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先。
-
递归函数设计:
- 定义了一个私有的递归函数
traversal
,参数包括当前节点cur
、需要寻找的两个节点p
和q
。 - 返回值是当前子树中的最近公共祖先节点。
- 定义了一个私有的递归函数
-
终止条件:
- 如果当前节点为空,直接返回NULL,表示当前子树中不存在最近公共祖先。
-
单层递归逻辑:
- 如果当前节点的值同时大于
p
和q
的值,则说明p
和q
都在当前节点的左子树中,因此递归遍历左子树。 - 如果当前节点的值同时小于
p
和q
的值,则说明p
和q
都在当前节点的右子树中,因此递归遍历右子树。 - 如果以上两个条件都不满足,则当前节点即为最近公共祖先节点,直接返回当前节点。
- 如果当前节点的值同时大于
-
最终结果:
- 在
lowestCommonAncestor
函数中调用traversal
函数,并返回其结果,即为最近公共祖先节点。
- 在
代码:
class Solution {
private:// 递归遍历函数,寻找最近公共祖先节点TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q){if(cur == NULL) return NULL;// 如果当前节点的值大于p和q节点的值,则递归遍历左子树if(cur ->val > p->val && cur->val > q->val){TreeNode* left= traversal(cur->left, p, q);if(cur != NULL){return left;}}// 如果当前节点的值小于p和q节点的值,则递归遍历右子树if(cur ->val < p->val && cur->val < q->val){TreeNode* right= traversal(cur->right, p, q);if(cur != NULL){return right;}}return cur; // 返回当前节点作为公共祖先}
public:// 寻找p和q的最近公共祖先节点TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {return traversal(root, p, q);}
};
4患某种疾病的患者
患者信息表: Patients
+--------------+---------+ | Column Name | Type | +--------------+---------+ | patient_id | int | | patient_name | varchar | | conditions | varchar | +--------------+---------+ 在 SQL 中,patient_id (患者 ID)是该表的主键。 'conditions' (疾病)包含 0 个或以上的疾病代码,以空格分隔。 这个表包含医院中患者的信息。
查询患有 I 类糖尿病的患者 ID (patient_id)、患者姓名(patient_name)以及其患有的所有疾病代码(conditions)。I 类糖尿病的代码总是包含前缀 DIAB1
。
按 任意顺序 返回结果表。
查询结果格式如下示例所示。
示例 1:
输入:
Patients表:
+------------+--------------+--------------+
| patient_id | patient_name | conditions |
+------------+--------------+--------------+
| 1 | Daniel | YFEV COUGH |
| 2 | Alice | |
| 3 | Bob | DIAB100 MYOP |
| 4 | George | ACNE DIAB100 |
| 5 | Alain | DIAB201 |
+------------+--------------+--------------+
输出:
+------------+--------------+--------------+
| patient_id | patient_name | conditions |
+------------+--------------+--------------+
| 3 | Bob | DIAB100 MYOP |
| 4 | George | ACNE DIAB100 |
+------------+--------------+--------------+
解释:Bob 和 George 都患有代码以 DIAB1 开头的疾病。
思路:
常见的SQL正则表达式匹配包括使用 REGEXP
或 RLIKE
关键字,它们用于在 WHERE
子句中对文本列进行正则表达式匹配。具体来说:
REGEXP
或RLIKE
:用于指定正则表达式模式进行匹配。- 正则表达式模式:定义了要匹配的字符串模式,可以包含特殊字符和通配符,如
.
、*
、+
等。 - 匹配操作符:常见的匹配操作符包括
^
(表示字符串开头)、$
(表示字符串结尾)、[]
(字符类,表示匹配其中的任何一个字符)、{}
(限定符,表示匹配特定数量的前一个元素)等。
-
查询目标:
- 使用 select关键字选择需要查询的列,包括患者ID (
patient_id
)、患者姓名 (patient_name
) 和病况信息 (conditions
)。
- 使用 select关键字选择需要查询的列,包括患者ID (
-
条件过滤:
- 使用 where子句对查询结果进行条件过滤,只选择符合条件的记录。
conditions
列使用正则表达式进行匹配,\\bDIAB1.*
表示以"DIAB1"开头的任意字符序列。这样可以确保只选择病况信息以"DIAB1"开头的患者记录。
-
查询结果:
- 执行查询后,返回符合条件的患者ID、患者姓名和病况信息的记录集合。
代码:
select patient_id, patient_name,conditions
from Patients
where conditions regexp '\\bDIAB1.*';
5 进店却未进行过交易的顾客
SQL Schema
Pandas Schema
表:Visits
+-------------+---------+ | Column Name | Type | +-------------+---------+ | visit_id | int | | customer_id | int | +-------------+---------+ visit_id 是该表中具有唯一值的列。 该表包含有关光临过购物中心的顾客的信息。
表:Transactions
+----------------+---------+ | Column Name | Type | +----------------+---------+ | transaction_id | int | | visit_id | int | | amount | int | +----------------+---------+ transaction_id 是该表中具有唯一值的列。 此表包含 visit_id 期间进行的交易的信息。
有一些顾客可能光顾了购物中心但没有进行交易。请你编写一个解决方案,来查找这些顾客的 ID ,以及他们只光顾不交易的次数。
返回以 任何顺序 排序的结果表。
返回结果格式如下例所示。
示例 1:
输入: Visits
+----------+-------------+ | visit_id | customer_id | +----------+-------------+ | 1 | 23 | | 2 | 9 | | 4 | 30 | | 5 | 54 | | 6 | 96 | | 7 | 54 | | 8 | 54 | +----------+-------------+Transactions
+----------------+----------+--------+ | transaction_id | visit_id | amount | +----------------+----------+--------+ | 2 | 5 | 310 | | 3 | 5 | 300 | | 9 | 5 | 200 | | 12 | 1 | 910 | | 13 | 2 | 970 | +----------------+----------+--------+ 输出: +-------------+----------------+ | customer_id | count_no_trans | +-------------+----------------+ | 54 | 2 | | 30 | 1 | | 96 | 1 | +-------------+----------------+ 解释: ID = 23 的顾客曾经逛过一次购物中心,并在 ID = 12 的访问期间进行了一笔交易。 ID = 9 的顾客曾经逛过一次购物中心,并在 ID = 13 的访问期间进行了一笔交易。 ID = 30 的顾客曾经去过购物中心,并且没有进行任何交易。 ID = 54 的顾客三度造访了购物中心。在 2 次访问中,他们没有进行任何交易,在 1 次访问中,他们进行了 3 次交易。 ID = 96 的顾客曾经去过购物中心,并且没有进行任何交易。 如我们所见,ID 为 30 和 96 的顾客一次没有进行任何交易就去了购物中心。顾客 54 也两次访问了购物中心并且没有进行任何交易。
思路:
-
连接表格: 首先,使用 left
join
将Visits
表和Transactions
表连接起来。这种连接会包括左边表中的所有行,即使右边表中没有匹配的行也会被包括进来。连接条件是Visits.visit_id = Transactions.visit_id
,这意味着我们正在将这两个表关联到它们共同的访问ID上。 -
筛选条件: 在连接后的结果上,使用
WHERE
子句来筛选出那些没有对应交易记录的行。这通过条件transaction_id is null
完成,它会过滤掉在Transactions
表中没有对应交易ID的记录,即表示没有交易记录的情况。 -
分组统计: 接着,使用 group
by
子句按照顾客ID (customer_id
) 进行分组。这意味着所有具有相同顾客ID的行将被归为同一组。 -
计数: 在每个分组上,使用 count
()
函数来计算每个顾客ID出现的次数。这个计数会给出每个顾客ID对应的无交易记录的访问数量。 -
结果显示: 最后,将结果显示为两列:顾客ID (
customer_id
) 和对应的无交易记录的访问数量 (count_no_trans
)。
代码:
select customer_id, count(customer_id) as count_no_trans
from Visits
left join Transactions
on Visits.visit_id = Transactions.visit_id
where transaction_id is null
group by customer_id;