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
中的所有整数 互不相同
思路:
解决给定不含重复数字的数组的全排列问题时,可以按照回溯算法的三部曲来具体实现,主要包括递归函数参数、递归终止条件和单层搜索的逻辑,下面是详细说明:
-
递归函数参数:
nums
:原始数组,用于生成全排列。used
:标记数组,用于标记元素是否被使用过。path
:当前路径,用于存储当前排列的元素。result
:最终结果集,用于存储所有全排列情况。
-
递归终止条件:
- 当当前路径长度等于原始数组长度时,表示找到了一个完整的排列,将其加入结果集。
- 即,如果
path.size() == nums.size()
,则将path
加入result
中,并返回。
-
单层搜索的逻辑:
- 遍历原始数组
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
思路:
这道题目和上面 全排列 的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。
这里又涉及到去重了。
-
回溯的三部曲:
- 递归函数参数:backtracking函数需要传入原始数组nums、标记数组used、当前路径path和结果集result。
- 递归终止条件:当当前路径长度等于原始数组长度时,表示找到一个完整的排列,将其加入结果集。
- 单层搜索的逻辑:在每一层的循环中,根据剪枝条件判断是否跳过当前元素,然后进行选择、递归和撤销选择的过程。
-
used数组的作用:
- used数组用于标记元素是否已经被使用过,保证每个元素不会重复使用。
- 在剪枝条件中,如果当前元素已经被使用过,则直接跳过该元素,避免重复计算。
-
去重部分的处理:
- 在单层搜索的逻辑中,通过判断相邻元素重复以及其是否被使用过来进行剪枝,确保不会出现重复的排列。
- 通过排序输入数组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)和回溯算法来找到符合条件的路径。具体步骤如下:
- 构建目标地点targets的哈希表,用于存储每个起始地点对应的目的地及其剩余航班数量。
- 定义回溯函数backtracking,参数为已选择的航班数量ticketNum和当前路径result。
- 设定递归终止条件:当当前路径长度等于总航班数量加1时,说明找到了符合要求的完整路径,返回true。
- 在单层搜索的逻辑中,遍历当前起始地点对应的目的地集合,对每个目的地执行以下操作:
- 如果该目的地还有剩余航班,则选择该目的地,将其加入路径result,剩余航班数量减一。
- 递归调用回溯函数backtracking,搜索下一个航班。
- 若成功找到一条路径,直接返回true;否则恢复状态(剩余航班数量加一,将已选目的地移出路径)。
- 在findItinerary函数中,首先清空哈希表targets,然后遍历输入的航班票价,统计目的地数量。
- 将起始地点"JFK"加入初始路径result,调用回溯函数backtracking开始搜索完整路径。
- 最后返回找到的结果路径。
代码:
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
)列的值为 1 到 500 。 “评分”(rating
)列的值为 1 到 5 。评分小于 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
思路:
- 使用聚合函数avg计算每个查询名称的平均质量,计算方法是rating(评分)除以position(位置),并保留两位小数。
- 使用sum和count函数结合计算低评分查询的百分比,计算方法是统计rating小于3的数量,再除以总数量并乘以100获得百分比,保留两位小数。
- 在查询语句中筛选出查询名称不为空的记录,避免计算无效数据。
- 最后按查询名称进行分组,得出每个查询名称的平均质量和低评分查询的百分比。
代码:
-- 查询每个查询名称的平均质量和低评分查询的百分比
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以上的产品名称和销售总量。具体的解题思路如下:
- 使用左连接(left join)将Products表和Orders表连接起来,通过产品ID(product_id)进行关联,以获取产品名称和销售量的数据。
- 在where子句中,筛选出订单日期在’2020-02-01’(含)到’2020-03-01’(不含)之间的订单数据,即指定日期范围内的销售情况。
- 利用group by子句按产品名称进行分组,以便计算每个产品的销售总量。
- 使用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的产品