练习题(2024/4/28)

1全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

思路:

解决给定不含重复数字的数组的全排列问题时,可以按照回溯算法的三部曲来具体实现,主要包括递归函数参数、递归终止条件和单层搜索的逻辑,下面是详细说明:

  1. 递归函数参数:

    • nums:原始数组,用于生成全排列。
    • used:标记数组,用于标记元素是否被使用过。
    • path:当前路径,用于存储当前排列的元素。
    • result:最终结果集,用于存储所有全排列情况。
  2. 递归终止条件:

    • 当当前路径长度等于原始数组长度时,表示找到了一个完整的排列,将其加入结果集。
    • 即,如果path.size() == nums.size(),则将path加入result中,并返回。
  3. 单层搜索的逻辑:

    • 遍历原始数组nums的每个元素,如果该元素未被使用过(即used[i] == false),则:
      • 将当前元素加入路径path中。
      • 将当前元素标记为已使用,即used[i] = true
      • 递归调用自身,继续搜索下一层,即backtracking(nums, used, path, result)
      • 在递归返回后,撤销选择:
        • 将当前元素从路径path中移除。
        • 将当前元素标记为未使用,即used[i] = false

代码:

class Solution {
public:
    vector<vector<int>> result; // 存储最终结果的二维向量
    vector<int> path; // 存储当前路径的向量

    // 回溯函数,用于搜索所有排列情况
    void backtracking(vector<int>& nums, vector<bool>& used) {
        if (path.size() == nums.size()) { // 当路径长度等于数组长度时,表示找到一个排列 纵向遍历
            result.push_back(path); // 将当前排列加入结果集 
            return;
        }

        for (int i = 0; i < nums.size(); i++) {
            if (used[i]) continue; // 如果该元素已经被使用过,则跳过  横向遍历
            used[i] = true; // 标记当前元素已被使用
            path.push_back(nums[i]); // 将当前元素加入路径
            backtracking(nums, used); // 递归搜索下一层
            path.pop_back(); // 回溯,撤销选择
            used[i] = false; // 标记当前元素未被使用
        }
    }

    // 主函数,调用回溯函数并返回结果
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear(); // 清空结果集
        path.clear(); // 清空路径
        vector<bool> used(nums.size(), false); // 初始化标记数组,0表示元素未被使用,1表示元素已被使用
        backtracking(nums, used); // 调用回溯函数
        return result; // 返回最终结果
    }
};

2全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

思路:

这道题目和上面 全排列 的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列

这里又涉及到去重了。

  1. 回溯的三部曲:

    • 递归函数参数:backtracking函数需要传入原始数组nums、标记数组used、当前路径path和结果集result。
    • 递归终止条件:当当前路径长度等于原始数组长度时,表示找到一个完整的排列,将其加入结果集。
    • 单层搜索的逻辑:在每一层的循环中,根据剪枝条件判断是否跳过当前元素,然后进行选择、递归和撤销选择的过程。
  2. used数组的作用:

    • used数组用于标记元素是否已经被使用过,保证每个元素不会重复使用。
    • 在剪枝条件中,如果当前元素已经被使用过,则直接跳过该元素,避免重复计算。
  3. 去重部分的处理:

    • 在单层搜索的逻辑中,通过判断相邻元素重复以及其是否被使用过来进行剪枝,确保不会出现重复的排列。
    • 通过排序输入数组nums,可以使重复元素连续,便于判断和剪枝操作。

代码:

class Solution {
private:
    vector<int> path; // 当前路径
    vector<vector<int>> result; // 结果集
    void backtracking(vector<int> &nums, vector<bool> &used) {
        if (path.size() == nums.size()) { // 当当前路径长度等于原始数组长度时,表示找到一个完整的排列
            result.push_back(path); // 将当前路径加入结果集
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == true) {
                continue; // 剪枝操作,去除重复的情况
            }
            if (used[i] == true) { // 如果当前元素已经被使用过,则跳过
                continue;
            }
            used[i] = true; // 标记当前元素为已使用
            path.push_back(nums[i]); // 将当前元素加入路径
            backtracking(nums, used); // 递归搜索下一层
            used[i] = false; // 撤销对当前元素的选择,将其标记为未使用
            path.pop_back(); // 移除当前元素,回溯到上一层
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear(); // 清空结果集
        path.clear(); // 清空路径
        sort(nums.begin(), nums.end()); // 排序输入数组,便于判断重复情况
        vector<bool> used(nums.size(), false); // 初始化标记数组
        backtracking(nums, used); // 调用回溯函数,开始搜索全排列
        return result; // 返回结果集
    }
};

3重新安排行程

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。

示例 1:

输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]

示例 2:

输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。

提示:

  • 1 <= tickets.length <= 300
  • tickets[i].length == 2
  • fromi.length == 3
  • toi.length == 3
  • fromi 和 toi 由大写英文字母组成
  • fromi != toi

思路:

使用深度优先搜索(DFS)和回溯算法来找到符合条件的路径。具体步骤如下:

  1. 构建目标地点targets的哈希表,用于存储每个起始地点对应的目的地及其剩余航班数量。
  2. 定义回溯函数backtracking,参数为已选择的航班数量ticketNum和当前路径result。
  3. 设定递归终止条件:当当前路径长度等于总航班数量加1时,说明找到了符合要求的完整路径,返回true。
  4. 在单层搜索的逻辑中,遍历当前起始地点对应的目的地集合,对每个目的地执行以下操作:
    • 如果该目的地还有剩余航班,则选择该目的地,将其加入路径result,剩余航班数量减一。
    • 递归调用回溯函数backtracking,搜索下一个航班。
    • 若成功找到一条路径,直接返回true;否则恢复状态(剩余航班数量加一,将已选目的地移出路径)。
  5. 在findItinerary函数中,首先清空哈希表targets,然后遍历输入的航班票价,统计目的地数量。
  6. 将起始地点"JFK"加入初始路径result,调用回溯函数backtracking开始搜索完整路径。
  7. 最后返回找到的结果路径。

代码:

class Solution {
private:
    unordered_map<string, map<string, int>> targets; // 存储航班目的地及其剩余数量
    bool backtracking(int ticketNum, vector<string>& result) {
        if (result.size() == ticketNum + 1) { // 终止条件:找到一条符合要求的路径
            return true;
        }
        for (pair<const string, int>& target : targets[result[result.size() - 1]]) {
            if (target.second > 0) { // 如果该航班仍有剩余票,则选择该航班目的地
                result.push_back(target.first); // 将目的地加入路径
                target.second--; // 剩余票数减一
                if (backtracking(ticketNum, result)) return true; // 递归搜索下一层
                target.second++; // 回溯操作:航班目的地数量加一
                result.pop_back(); // 回溯操作:移除最后一个目的地
            }
        }
        return false;
    }
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        targets.clear(); // 清空目标地图
        vector<string> result; // 存放结果路径
        for (const vector<string>& vec : tickets) {
            targets[vec[0]][vec[1]]++; // 统计航班目的地及其剩余数量
        }
        result.push_back("JFK"); // 起始地点为"JFK"
        backtracking(tickets.size(), result); // 调用回溯函数开始搜索路径
        return result; // 返回结果路径
    }
};

4 查询结果的质量和占比

Queries 表: 

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| query_name  | varchar |
| result      | varchar |
| position    | int     |
| rating      | int     |
+-------------+---------+
此表可能有重复的行。
此表包含了一些从数据库中收集的查询信息。
“位置”(position)列的值为 1500 。
“评分”(rating)列的值为 15 。评分小于 3 的查询被定义为质量很差的查询。

将查询结果的质量 quality 定义为:

各查询结果的评分与其位置之间比率的平均值。

将劣质查询百分比 poor_query_percentage 为:

评分小于 3 的查询结果占全部查询结果的百分比。

编写解决方案,找出每次的 query_name 、 quality 和 poor_query_percentage

quality 和 poor_query_percentage 都应 四舍五入到小数点后两位 。

以 任意顺序 返回结果表。

结果格式如下所示:

示例 1:

输入:
Queries table:
+------------+-------------------+----------+--------+
| query_name | result            | position | rating |
+------------+-------------------+----------+--------+
| Dog        | Golden Retriever  | 1        | 5      |
| Dog        | German Shepherd   | 2        | 5      |
| Dog        | Mule              | 200      | 1      |
| Cat        | Shirazi           | 5        | 2      |
| Cat        | Siamese           | 3        | 3      |
| Cat        | Sphynx            | 7        | 4      |
+------------+-------------------+----------+--------+
输出:
+------------+---------+-----------------------+
| query_name | quality | poor_query_percentage |
+------------+---------+-----------------------+
| Dog        | 2.50    | 33.33                 |
| Cat        | 0.66    | 33.33                 |
+------------+---------+-----------------------+
解释:
Dog 查询结果的质量为 ((5 / 1) + (5 / 2) + (1 / 200)) / 3 = 2.50
Dog 查询结果的劣质查询百分比为 (1 / 3) * 100 = 33.33

Cat 查询结果的质量为 ((2 / 5) + (3 / 3) + (4 / 7)) / 3 = 0.66
Cat 查询结果的劣质查询百分比为 (1 / 3) * 100 = 33.33

思路:

  1. 使用聚合函数avg计算每个查询名称的平均质量,计算方法是rating(评分)除以position(位置),并保留两位小数。
  2. 使用sum和count函数结合计算低评分查询的百分比,计算方法是统计rating小于3的数量,再除以总数量并乘以100获得百分比,保留两位小数。
  3. 在查询语句中筛选出查询名称不为空的记录,避免计算无效数据。
  4. 最后按查询名称进行分组,得出每个查询名称的平均质量和低评分查询的百分比。

代码:

-- 查询每个查询名称的平均质量和低评分查询的百分比
select query_name, -- 查询名称
       round(avg(rating/position),2) as quality, -- 计算平均质量,保留两位小数
       round(sum(rating < 3) / count(*) * 100,2) as poor_query_percentage -- 计算低评分查询的百分比,保留两位小数
from Queries -- 从Queries表中查询数据
where query_name is not null -- 筛选出不为空的查询名称
group by query_name; -- 按查询名称分组

5列出指定时间段内所有的下单产品

表: Products

+------------------+---------+
| Column Name      | Type    |
+------------------+---------+
| product_id       | int     |
| product_name     | varchar |
| product_category | varchar |
+------------------+---------+
product_id 是该表主键(具有唯一值的列)。
该表包含该公司产品的数据。

表: Orders

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| order_date    | date    |
| unit          | int     |
+---------------+---------+
该表可能包含重复行。
product_id 是表单 Products 的外键(reference 列)。
unit 是在日期 order_date 内下单产品的数目。

写一个解决方案,要求获取在 2020 年 2 月份下单的数量不少于 100 的产品的名字和数目。

返回结果表单的 顺序无要求 

查询结果的格式如下。

示例 1:

输入:
Products 表:
+-------------+-----------------------+------------------+
| product_id  | product_name          | product_category |
+-------------+-----------------------+------------------+
| 1           | Leetcode Solutions    | Book             |
| 2           | Jewels of Stringology | Book             |
| 3           | HP                    | Laptop           |
| 4           | Lenovo                | Laptop           |
| 5           | Leetcode Kit          | T-shirt          |
+-------------+-----------------------+------------------+
Orders 表:
+--------------+--------------+----------+
| product_id   | order_date   | unit     |
+--------------+--------------+----------+
| 1            | 2020-02-05   | 60       |
| 1            | 2020-02-10   | 70       |
| 2            | 2020-01-18   | 30       |
| 2            | 2020-02-11   | 80       |
| 3            | 2020-02-17   | 2        |
| 3            | 2020-02-24   | 3        |
| 4            | 2020-03-01   | 20       |
| 4            | 2020-03-04   | 30       |
| 4            | 2020-03-04   | 60       |
| 5            | 2020-02-25   | 50       |
| 5            | 2020-02-27   | 50       |
| 5            | 2020-03-01   | 50       |
+--------------+--------------+----------+
输出:
+--------------------+---------+
| product_name       | unit    |
+--------------------+---------+
| Leetcode Solutions | 130     |
| Leetcode Kit       | 100     |
+--------------------+---------+
解释:
2020 年 2 月份下单 product_id = 1 的产品的数目总和为 (60 + 70) = 130 。
2020 年 2 月份下单 product_id = 2 的产品的数目总和为 80 。
2020 年 2 月份下单 product_id = 3 的产品的数目总和为 (2 + 3) = 5 。
2020 年 2 月份 product_id = 4 的产品并没有下单。
2020 年 2 月份下单 product_id = 5 的产品的数目总和为 (50 + 50) = 100 。

思路:

对于给定的问题,需要查询指定日期范围内销售量达到100以上的产品名称和销售总量。具体的解题思路如下:

  1. 使用左连接(left join)将Products表和Orders表连接起来,通过产品ID(product_id)进行关联,以获取产品名称和销售量的数据。
  2. 在where子句中,筛选出订单日期在’2020-02-01’(含)到’2020-03-01’(不含)之间的订单数据,即指定日期范围内的销售情况。
  3. 利用group by子句按产品名称进行分组,以便计算每个产品的销售总量。
  4. 使用having子句过滤出销售量大于等于100的产品,仅显示达标产品的数据。

代码:

-- 查询指定日期范围内销售量达到100以上的产品名称与销售总量
select product_name, sum(unit) as unit -- 查询产品名称和销售总量
from Products -- 从Products表中查询
left join Orders on Products.product_id = Orders.product_id -- 使用左连接关联Products和Orders表
where order_date >= '2020-02-01' and order_date < '2020-03-01' -- 筛选出符合日期范围条件的订单
group by product_name -- 按产品名称分组
having unit >= 100; -- 过滤出销售量大于等于100的产品

  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值