1 下一个排列
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3]
,以下这些都可以视作arr
的排列:[1,2,3]
、[1,3,2]
、[3,1,2]
、[2,3,1]
。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3]
的下一个排列是[1,3,2]
。 - 类似地,
arr = [2,3,1]
的下一个排列是[3,1,2]
。 - 而
arr = [3,2,1]
的下一个排列是[1,2,3]
,因为[3,2,1]
不存在一个字典序更大的排列。
给你一个整数数组 nums
,找出 nums
的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3] 输出:[1,3,2]
示例 2:
输入:nums = [3,2,1] 输出:[1,2,3]
示例 3:
输入:nums = [1,1,5] 输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
思路:
-
找到交换点: 从右往左找到第一个非递增的元素,记为索引
i
。如果找不到这样的元素,说明当前排列已经是最大排列,直接将整个数组逆序,得到最小排列。 -
交换位置: 在索引
i
右边的元素中,找到比nums[i]
大的最小元素,记为索引j
。交换nums[i]
和nums[j]
。 -
调整顺序: 将索引
i
右边的元素按升序排列,以确保下一个排列是大于当前排列的最小排列。
过程
- 从倒数第二个元素开始向前遍历数组,找到第一个比后一个元素小的元素的索引 i。
- 如果找到了这样的元素,再从数组的最后一个元素开始向前遍历,找到第一个比 nums[i] 大的元素的索引 j。
- 将 nums[i] 和 nums[j] 交换位置。
- 将索引 i+1 到末尾的元素进行排序,使其成为下一个排列的最小序列。
- 如果没有找到符合条件的元素,则将整个数组反转,得到最小的排列
代码:
class Solution {
public:// 下一个排列void nextPermutation(vector<int>& nums) {// 如果数组长度小于等于1,则无需处理if (nums.size() <= 1) {return;}// 从倒数第二个元素开始向前遍历for (int i = nums.size() - 2; i >= 0; --i) {// 找到第一个比后一个元素小的元素if (nums[i] < nums[i + 1]) {// 从后向前找到第一个比 nums[i] 大的元素,进行交换for (int j = nums.size() - 1; j > i; --j) {if (nums[i] < nums[j]) {swap(nums[i], nums[j]);// 将 i+1 到末尾的元素进行排序,使其成为下一个排列的最小序列sort(nums.begin() + i + 1, nums.end());return;}}}}// 如果没有找到符合条件的元素,则将整个数组反转,得到最小的排列sort(nums.begin(), nums.end());}
};
2 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5 输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2 输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7 输出: 4
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
为 无重复元素 的 升序 排列数组-104 <= target <= 104
思路:
-
初始化指针: 将左指针
left
初始为数组的首位置,右指针right
初始为数组的尾位置。 -
二分查找: 在左指针小于等于右指针的条件下,执行二分查找算法:
- 计算中间位置
middle
。 - 如果中间元素小于目标值
target
,则将左指针移到中间位置的右边一位。 - 如果中间元素大于目标值
target
,则将右指针移到中间位置的左边一位。 - 如果中间元素等于目标值
target
,直接返回中间位置索引。
- 计算中间位置
代码:
class Solution {
public:// 搜索插入位置int searchInsert(vector<int>& nums, int target) {// 左指针初始为数组首位置,右指针初始为数组尾位置int left = 0;int right = nums.size() - 1;// 在左指针小于等于右指针的条件下进行循环while(left <= right){// 计算中间位置int middle = left + ((right - left) / 2);// 如果中间元素小于目标值,则将左指针移到中间位置的右边一位if (nums[middle] < target){left = middle + 1;}// 如果中间元素大于目标值,则将右指针移到中间位置的左边一位else if (nums[middle] > target){right = middle - 1;}// 如果中间元素等于目标值,则直接返回中间位置索引else {return middle;}}// 如果循环结束仍未找到目标值,则返回左指针位置,即插入位置return left;}
};
3体育馆的人流量
表:Stadium
+---------------+---------+ | Column Name | Type | +---------------+---------+ | id | int | | visit_date | date | | people | int | +---------------+---------+ visit_date 是该表中具有唯一值的列。 每日人流量信息被记录在这三列信息中:序号 (id)、日期 (visit_date)、 人流量 (people) 每天只有一行记录,日期随着 id 的增加而增加
编写解决方案找出每行的人数大于或等于 100
且 id
连续的三行或更多行记录。
返回按 visit_date
升序排列 的结果表。
查询结果格式如下所示。
示例 1:
输入:
Stadium
表:
+------+------------+-----------+
| id | visit_date | people |
+------+------------+-----------+
| 1 | 2017-01-01 | 10 |
| 2 | 2017-01-02 | 109 |
| 3 | 2017-01-03 | 150 |
| 4 | 2017-01-04 | 99 |
| 5 | 2017-01-05 | 145 |
| 6 | 2017-01-06 | 1455 |
| 7 | 2017-01-07 | 199 |
| 8 | 2017-01-09 | 188 |
+------+------------+-----------+
输出:
+------+------------+-----------+
| id | visit_date | people |
+------+------------+-----------+
| 5 | 2017-01-05 | 145 |
| 6 | 2017-01-06 | 1455 |
| 7 | 2017-01-07 | 199 |
| 8 | 2017-01-09 | 188 |
+------+------------+-----------+
解释:
id 为 5、6、7、8 的四行 id 连续,并且每行都有 >= 100 的人数记录。
请注意,即使第 7 行和第 8 行的 visit_date 不是连续的,输出也应当包含第 8 行,因为我们只需要考虑 id 连续的记录。
不输出 id 为 2 和 3 的行,因为至少需要三条 id 连续的记录。
思路:
- 使用自连接将 Stadium 表连接三次,分别用别名 a、b、c。
- 筛选满足条件的记录:每行的人数大于等于 100,并且前两行的 id 是连续的。
- 使用 OR 运算符来考虑三种情况:
- 检查 b 和 c 的 id 是否连续,且 a 和 b 的 id 也连续。
- 或者检查 a 和 c 的 id 是否连续,且 b 和 a 的 id 也连续。
- 或者检查 c 和 b 的 id 是否连续,且 a 和 c 的 id 也连续。
- 最后按照 visit_date 升序排序结果。
代码:
-- 选择不重复的记录
select distinct a.*
-- 从 Stadium 表进行自连接,分别用 a、b、c 作为别名
from stadium as a, stadium as b, stadium as c
-- 筛选满足条件的记录:每行的人数大于等于 100,并且前两行的 id 是连续的
where a.people >= 100 and b.people >= 100 and c.people >= 100
and (-- 检查 b 和 c 的 id 是否连续,且 a 和 b 的 id 也连续(b.id - a.id = 1 and c.id - b.id = 1)-- 或者检查 a 和 c 的 id 是否连续,且 b 和 a 的 id 也连续or (a.id - b.id = 1 and c.id - a.id = 1)-- 或者检查 c 和 b 的 id 是否连续,且 a 和 c 的 id 也连续or (c.id - b.id = 1 and a.id - c.id = 1)
)
-- 按照 visit_date 升序排序结果
order by a.visit_date;
4好友申请 II :谁有最多的好友
RequestAccepted
表:
+----------------+---------+ | Column Name | Type | +----------------+---------+ | requester_id | int | | accepter_id | int | | accept_date | date | +----------------+---------+ (requester_id, accepter_id) 是这张表的主键(具有唯一值的列的组合)。 这张表包含发送好友请求的人的 ID ,接收好友请求的人的 ID ,以及好友请求通过的日期。
编写解决方案,找出拥有最多的好友的人和他拥有的好友数目。
生成的测试用例保证拥有最多好友数目的只有 1 个人。
查询结果格式如下例所示。
示例 1:
输入: RequestAccepted 表: +--------------+-------------+-------------+ | requester_id | accepter_id | accept_date | +--------------+-------------+-------------+ | 1 | 2 | 2016/06/03 | | 1 | 3 | 2016/06/08 | | 2 | 3 | 2016/06/08 | | 3 | 4 | 2016/06/09 | +--------------+-------------+-------------+ 输出: +----+-----+ | id | num | +----+-----+ | 3 | 3 | +----+-----+ 解释: 编号为 3 的人是编号为 1 ,2 和 4 的人的好友,所以他总共有 3 个好友,比其他人都多。
思路:
成为朋友是一个双向的过程,所以如果一个人接受了另一个人的请求,他们两个都会多拥有一个朋友。
所以我们可以将 requester_id 和 accepter_id 联合起来,然后统计每个人出现的次数
-
数据准备:首先,从
RequestAccepted
表中提取所有的requester_id
和accepter_id
,将它们视为同一类标识(id
)并合并到一个结果集中。这是通过一个联合查询来完成的,其中请求者和接受者被统一命名为id
。 -
计数:对于合并后的
id
列,使用GROUP BY
和COUNT(*)
来计算每个id
出现的次数,即其好友数量。 -
排序和限制:对计数结果进行降序排序,以便最大数量的好友排在最前面。然后使用
LIMIT 1
只选择排在第一位的记录,即具有最多好友数量的用户。
代码:
-- 选择 id 和每个 id 出现的次数作为别名为 num 的列
select id, count(*) as num
-- 从一个由 RequestAccepted 表的 requester_id 和 accepter_id 列组成的联合结果中选择 id
from (select requester_id as id from RequestAccepted-- 将 requester_id 列别名为 idunion allselect accepter_id as id from RequestAccepted
) as t1
-- 按照每个 id 出现的次数(num)降序排序
group by id
order by count(*) desc
-- 只返回排序结果的第一行
limit 1;
5 销售员
表: SalesPerson
+-----------------+---------+ | Column Name | Type | +-----------------+---------+ | sales_id | int | | name | varchar | | salary | int | | commission_rate | int | | hire_date | date | +-----------------+---------+ sales_id 是该表的主键列(具有唯一值的列)。 该表的每一行都显示了销售人员的姓名和 ID ,以及他们的工资、佣金率和雇佣日期。
表: Company
+-------------+---------+ | Column Name | Type | +-------------+---------+ | com_id | int | | name | varchar | | city | varchar | +-------------+---------+ com_id 是该表的主键列(具有唯一值的列)。 该表的每一行都表示公司的名称和 ID ,以及公司所在的城市。
表: Orders
+-------------+------+ | Column Name | Type | +-------------+------+ | order_id | int | | order_date | date | | com_id | int | | sales_id | int | | amount | int | +-------------+------+ order_id 是该表的主键列(具有唯一值的列)。 com_id 是 Company 表中 com_id 的外键(reference 列)。 sales_id 是来自销售员表 sales_id 的外键(reference 列)。 该表的每一行包含一个订单的信息。这包括公司的 ID 、销售人员的 ID 、订单日期和支付的金额。
编写解决方案,找出没有任何与名为 “RED” 的公司相关的订单的所有销售人员的姓名。
以 任意顺序 返回结果表。
返回结果格式如下所示。
示例 1:
输入: SalesPerson 表: +----------+------+--------+-----------------+------------+ | sales_id | name | salary | commission_rate | hire_date | +----------+------+--------+-----------------+------------+ | 1 | John | 100000 | 6 | 4/1/2006 | | 2 | Amy | 12000 | 5 | 5/1/2010 | | 3 | Mark | 65000 | 12 | 12/25/2008 | | 4 | Pam | 25000 | 25 | 1/1/2005 | | 5 | Alex | 5000 | 10 | 2/3/2007 | +----------+------+--------+-----------------+------------+ Company 表: +--------+--------+----------+ | com_id | name | city | +--------+--------+----------+ | 1 | RED | Boston | | 2 | ORANGE | New York | | 3 | YELLOW | Boston | | 4 | GREEN | Austin | +--------+--------+----------+ Orders 表: +----------+------------+--------+----------+--------+ | order_id | order_date | com_id | sales_id | amount | +----------+------------+--------+----------+--------+ | 1 | 1/1/2014 | 3 | 4 | 10000 | | 2 | 2/1/2014 | 4 | 5 | 5000 | | 3 | 3/1/2014 | 1 | 1 | 50000 | | 4 | 4/1/2014 | 1 | 4 | 25000 | +----------+------------+--------+----------+--------+ 输出: +------+ | name | +------+ | Amy | | Mark | | Alex | +------+ 解释: 根据表orders
中的订单 '3' 和 '4' ,容易看出只有 'John' 和 'Pam' 两个销售员曾经向公司 'RED' 销售过。 所以我们需要输出表salesperson
中所有其他人的名字。
思路:
-
首先,我们需要确定哪些销售员曾经向名为 “RED” 的公司销售过产品。我们可以通过联结 Orders 表和 Company 表来实现。
-
然后,我们将排除这些销售员,从而找出剩余的销售员。这些剩余的销售员就是没有向名为 “RED” 的公司销售过产品的销售员。
过程:
-
主查询目标:主查询的目标是从 SalesPerson 表中选择销售员的名字。
-
子查询作用:子查询的作用是筛选出在 Orders 表中没有销售给名为 ‘RED’ 的公司的销售订单的 sales_id。
-
子查询逻辑:子查询使用了左连接将 Orders 表与 Company 表关联,找出与名为 ‘RED’ 的公司相关的销售订单,然后通过 WHERE 子句将这些订单筛选出来。
-
主查询过滤:主查询使用了 NOT IN 运算符,将子查询的结果作为条件,从而排除了在子查询中出现的 sales_id,从 SalesPerson 表中选择出符合条件的销售员名字。
-
最终目的:该查询的最终目的是找出那些没有向名为 ‘RED’ 的公司销售过产品的销售员,并返回他们的名字。
代码:
-- 选择 SalesPerson 表中的名字列
select SalesPerson.name
-- 从 SalesPerson 表中选择名字
from SalesPerson
-- 只选择那些在 Orders 表中销售订单的 sales_id 没有对应的记录
where sales_id not in (-- 从 Orders 表和 Company 表中选择销售订单的 sales_idselect sales_id from Orders -- 使用左连接将 Orders 表与 Company 表关联,基于 Orders 表中的 com_id 和 Company 表中的 com_idleft join Company on Orders.com_id = Company.com_id-- 筛选出公司名为 'RED' 的记录where Company.name = 'RED'
);