练习题(2024/5/23)

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
  • beginWordendWord 和 wordList[i] 由小写英文字母组成
  • beginWord != endWord
  • wordList 中的所有字符串 互不相同

思路:

  1. BFS算法:使用队列来实现广度优先搜索,保证了每次先搜索距离起点最近的节点,从而找到最短路径。

  2. 单词集合和访问记录:通过将单词列表转换为无序集合并记录访问过的单词,可以有效地避免重复搜索和形成环路。

  3. 新单词判断:在生成新单词时,通过判断其是否在单词集合中且未被访问过,可以确保不会形成无效的转换路径。

  4. 路径长度记录:使用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排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值