1单词接龙
字典 wordList
中从单词 beginWord
和 endWord
的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk
:
- 每一对相邻的单词只差一个字母。
- 对于
1 <= i <= k
时,每个si
都在wordList
中。注意,beginWord
不需要在wordList
中。 sk == endWord
给你两个单词 beginWord
和 endWord
和一个字典 wordList
,返回 从 beginWord
到 endWord
的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0
。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出:5 解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] 输出:0 解释:endWord "cog" 不在字典中,所以无法进行转换。
提示:
1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord
、endWord
和wordList[i]
由小写英文字母组成beginWord != endWord
wordList
中的所有字符串 互不相同
思路:
-
BFS算法:使用队列来实现广度优先搜索,保证了每次先搜索距离起点最近的节点,从而找到最短路径。
-
单词集合和访问记录:通过将单词列表转换为无序集合并记录访问过的单词,可以有效地避免重复搜索和形成环路。
-
新单词判断:在生成新单词时,通过判断其是否在单词集合中且未被访问过,可以确保不会形成无效的转换路径。
-
路径长度记录:使用
visitMap
记录每个单词的路径长度,以便在找到结束单词时能够准确返回最短路径长度。
代码:
class Solution {
public:
// 使用双向BFS,寻找最短转换序列长度
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
// 将单词列表转换为无序集合,以提高查询速度
unordered_set<string> wordSet(wordList.begin(), wordList.end());
// 如果结束单词不在单词集合中,则无法转换,返回0
if (wordSet.find(endWord) == wordSet.end()) return 0;
// 记录单词是否被访问过及到达该单词的路径长度
unordered_map<string, int> visitMap; // <单词, 到达该单词的路径长度>
// 初始化队列
queue<string> que;
que.push(beginWord);
// 初始化visitMap
visitMap.insert(pair<string, int>(beginWord, 1));
while (!que.empty()) {
string word = que.front();
que.pop();
int path = visitMap[word]; // 当前单词的路径长度
// 遍历单词的每个字符
for (int i = 0; i < word.size(); i++) {
string newWord = word; // 用一个新单词替换当前单词,因为每次只替换一个字符
// 26个字母依次替换当前字符
for (int j = 0; j < 26; j++) {
newWord[i] = j + 'a';
// 如果新单词等于结束单词,则返回路径长度+1
if (newWord == endWord) return path + 1;
// 如果新单词在单词集合中,并且未被访问过
if (wordSet.find(newWord) != wordSet.end() && visitMap.find(newWord) == visitMap.end()) {
// 添加访问信息
visitMap.insert(pair<string, int>(newWord, path + 1));
que.push(newWord);
}
}
}
}
// 未找到转换序列,返回0
return 0;
}
};
2飞地的数量
给你一个大小为 m x n
的二进制矩阵 grid
,其中 0
表示一个海洋单元格、1
表示一个陆地单元格。
一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid
的边界。
返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。
示例 1:
输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]] 输出:3 解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。
示例 2:
输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]] 输出:0 解释:所有 1 都在边界上或可以到达边界。
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 500
grid[i][j]
的值为0
或1
思路:
从网格的边界开始,对边界上为陆地的格子进行深度优先搜索,将与边界相连的陆地格子标记为已访问,并统计数量。接着再次遍历整个网格,对未访问过的陆地格子进行深度优先搜索,同样标记为已访问,并统计数量。最终返回符合题目要求的陆地空格数量
代码:
class Solution {
private:
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; // 保存四个方向
int count; // 统计符合题目要求的陆地空格数量
// 深度优先搜索
void dfs(vector<vector<int>>& grid, int x, int y) {
// 标记当前格子已经访问过
grid[x][y] = 0;
// 统计陆地数量
count++;
// 遍历四个方向
for (int i = 0; i < 4; i++) {
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
// 如果超出边界,跳过
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;
// 如果是海洋格子,跳过
if (grid[nextx][nexty] == 0) continue;
// 递归遍历相邻的陆地格子
dfs(grid, nextx, nexty);
}
return;
}
public:
// 计算符合题目要求的陆地空格数量
int numEnclaves(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size();
// 从左右两侧边界开始遍历
for (int i = 0; i < n; i++) {
if (grid[i][0] == 1) dfs(grid, i, 0);
if (grid[i][m - 1] == 1) dfs(grid, i, m - 1);
}
// 从上下两侧边界开始遍历
for (int j = 0; j < m; j++) {
if (grid[0][j] == 1) dfs(grid, 0, j);
if (grid[n - 1][j] == 1) dfs(grid, n - 1, j);
}
count = 0;
// 遍历整个网格,进行DFS
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 如果是陆地格子且未访问过,进行DFS
if (grid[i][j] == 1) dfs(grid, i, j);
}
}
return count;
}
};
3确认率
表: Signups
+----------------+----------+ | Column Name | Type | +----------------+----------+ | user_id | int | | time_stamp | datetime | +----------------+----------+ User_id是该表的主键。 每一行都包含ID为user_id的用户的注册时间信息。
表: Confirmations
+----------------+----------+ | Column Name | Type | +----------------+----------+ | user_id | int | | time_stamp | datetime | | action | ENUM | +----------------+----------+ (user_id, time_stamp)是该表的主键。 user_id是一个引用到注册表的外键。 action是类型为('confirmed', 'timeout')的ENUM 该表的每一行都表示ID为user_id的用户在time_stamp请求了一条确认消息,该确认消息要么被确认('confirmed'),要么被过期('timeout')。
用户的 确认率 是 'confirmed'
消息的数量除以请求的确认消息的总数。没有请求任何确认消息的用户的确认率为 0
。确认率四舍五入到 小数点后两位 。
编写一个SQL查询来查找每个用户的 确认率 。
以 任意顺序 返回结果表。
查询结果格式如下所示。
示例1:
输入: Signups 表: +---------+---------------------+ | user_id | time_stamp | +---------+---------------------+ | 3 | 2020-03-21 10:16:13 | | 7 | 2020-01-04 13:57:59 | | 2 | 2020-07-29 23:09:44 | | 6 | 2020-12-09 10:39:37 | +---------+---------------------+ Confirmations 表: +---------+---------------------+-----------+ | user_id | time_stamp | action | +---------+---------------------+-----------+ | 3 | 2021-01-06 03:30:46 | timeout | | 3 | 2021-07-14 14:00:00 | timeout | | 7 | 2021-06-12 11:57:29 | confirmed | | 7 | 2021-06-13 12:58:28 | confirmed | | 7 | 2021-06-14 13:59:27 | confirmed | | 2 | 2021-01-22 00:00:00 | confirmed | | 2 | 2021-02-28 23:59:59 | timeout | +---------+---------------------+-----------+ 输出: +---------+-------------------+ | user_id | confirmation_rate | +---------+-------------------+ | 6 | 0.00 | | 3 | 0.00 | | 7 | 1.00 | | 2 | 0.50 | +---------+-------------------+ 解释: 用户 6 没有请求任何确认消息。确认率为 0。 用户 3 进行了 2 次请求,都超时了。确认率为 0。 用户 7 提出了 3 个请求,所有请求都得到了确认。确认率为 1。 用户 2 做了 2 个请求,其中一个被确认,另一个超时。确认率为 1 / 2 = 0.5。
思路:
两个表中获取数据,然后将它们连接在一起,并根据用户ID将结果分组。在连接过程中,它会计算每个用户的确认率,即确认操作数量与注册操作数量的比率,并四舍五入到小数点后两位。
代码:
-- 选择用户ID和确认率,并将结果四舍五入到小数点后两位
select s.user_id, round(count(c.action = 'confirmed' or null) / count(s.user_id), 2) as confirmation_rate
-- 从Signups表中选择用户ID列
from Signups s
-- 左连接Confirmations表,使用用户ID进行关联
left join Confirmations c on s.user_id = c.user_id
-- 按用户ID分组
group by s.user_id;
4丢失信息的雇员
表: Employees
+-------------+---------+ | Column Name | Type | +-------------+---------+ | employee_id | int | | name | varchar | +-------------+---------+ employee_id 是该表中具有唯一值的列。 每一行表示雇员的 id 和他的姓名。
表: Salaries
+-------------+---------+ | Column Name | Type | +-------------+---------+ | employee_id | int | | salary | int | +-------------+---------+ employee_id 是该表中具有唯一值的列。 每一行表示雇员的 id 和他的薪水。
编写解决方案,找到所有 丢失信息 的雇员 id。当满足下面一个条件时,就被认为是雇员的信息丢失:
- 雇员的 姓名 丢失了,或者
- 雇员的 薪水信息 丢失了
返回这些雇员的 id employee_id
, 从小到大排序 。
查询结果格式如下面的例子所示。
示例 1:
输入: Employees table: +-------------+----------+ | employee_id | name | +-------------+----------+ | 2 | Crew | | 4 | Haven | | 5 | Kristian | +-------------+----------+ Salaries table: +-------------+--------+ | employee_id | salary | +-------------+--------+ | 5 | 76071 | | 1 | 22517 | | 4 | 63539 | +-------------+--------+ 输出: +-------------+ | employee_id | +-------------+ | 1 | | 2 | +-------------+ 解释: 雇员 1,2,4,5 都在这个公司工作。 1 号雇员的姓名丢失了。 2 号雇员的薪水信息丢失了。
思路:
从两个表中选择员工ID,并将它们合并成一个结果集。然后,根据员工ID进行分组,只选择出现一次的员工ID,最后按照员工ID进行排序。这样做可以找出只出现在一个表中而不在另一个表中的员工ID。
UNION ALL
是用于将两个或多个 select 语句的结果集合并在一起,不去除重复行。与 UNION
不同的是,UNION
会去除重复行,而 UNION ALL
保留所有行
代码:
select
employee_id -- 选择员工ID
from
(
select employee_id from employees -- 从员工表中选择员工ID
union all
select employee_id from salaries -- 从工资表中选择员工ID
) as t
group by
employee_id -- 按员工ID分组
having
count(employee_id) = 1 -- 只选择出现一次的员工ID
order by
employee_id; -- 按员工ID排序