继续打卡hot100
31. 下一个排列 - 力扣(LeetCode)
class Solution {
public:void nextPermutation(vector<int>& nums) {int n = nums.size();int i = n - 2;while(i >= 0 && nums[i] >= nums[i+1]){i--; // 找到第一个不是递减的数字 这里可能i变为-1}if(i >= 0){int j = n - 1;while(nums[i] >= nums[j]){j--;// 找到可以交换的数字}swap(nums[i], nums[j]);}reverse(nums.begin() + i + 1,nums.end());}
};
好的,我们来从头开始,一步步推导出这道题的解法。这道题是字典序算法的一个经典应用,理解它的逻辑非常有意思。
目标分析
我们要找的是一个数字序列的 “下一个更大的排列”。
-
“排列”: 意味着我们只能使用原来数字,不能改变数字本身。
-
“下一个更大”: 意味着在所有可能的排列中,我们要找一个比当前排列大一点点的,而且是大得最少的那一个。
举个例子,对于 [1, 2, 3]
:
-
所有排列按从小到大是:
[1, 2, 3]
,[1, 3, 2]
,[2, 1, 3]
,[2, 3, 1]
,[3, 1, 2]
,[3, 2, 1]
。 -
[1, 2, 3]
的下一个是[1, 3, 2]
。 -
[2, 3, 1]
的下一个是[3, 1, 2]
。 -
[3, 2, 1]
是最大的,没有下一个更大的排列了。
推导过程 (如何思考?)
为了让一个数变得更大,但又尽可能小地改变它,我们应该尽可能地去修改低位(也就是数组靠右的部分)。
我们以 [1, 3, 5, 4, 2]
为例来思考:
-
从右往左看,序列是
...5, 4, 2
。这部分[5, 4, 2]
是一个降序排列。对于一个降序的序列,无论怎么重新排列,都不可能得到一个比它本身更大的数。所以,只动[5, 4, 2]
这部分是没希望的。 -
我们必须把一个“较小的数”和一个“较大的数”进行交换,才能让整个序列变大。 而且,为了让改变尽可能小,我们应该用一个**靠右的“较小数”去和它右边的“较大数”**交换。
-
继续从右往左看,我们找到了数字
3
。在3
这个位置,它后面的序列是[5, 4, 2]
。3
比它右边的5
要小。这是一个关键的突破口!这意味着,我们可以把3
换成一个比它大的数,从而让整个数字序列变大。这个3
就是我们要操作的目标“小数”。-
第一步,就此诞生:从右向左,找到第一个
nums[i] < nums[i+1]
的位置。这个nums[i]
就是我们要找的“小数”。 在[1, 3, 5, 4, 2]
中,这个数是3
,下标i=1
。
-
-
现在我们确定了要替换
3
。用谁来替换它呢?我们应该从它右边的序列[5, 4, 2]
中选一个数。-
为了让排列变大得最少,我们应该选一个刚刚好比
3
大的数来替换。 -
在
[5, 4, 2]
中,比3
大的数有5
和4
。其中4
是那个“刚刚好”的数。 -
第二步,就此诞生:再次从右向左,在下标
i
之后的序列中,找到第一个比nums[i]
大的数nums[j]
。 在[1, 3, 5, 4, 2]
中,这个数是4
,下标j=3
。
-
-
找到这两个数之后,我们交换它们。
-
[1, 3, 5, 4, 2]
交换3
和4
之后,变成了[1, 4, 5, 3, 2]
。 -
第三步:交换
nums[i]
和nums[j]
。
-
-
现在序列是
[1, 4, 5, 3, 2]
。它确实比原来大了。但它是下一个吗?-
我们已经把更高位(左边)的数字
3
变成了4
,完成了“变大”这个主要目标。 -
为了让这个新数成为最小的那个“更大的数”,我们应该让它后面(低位)的数字尽可能地小。
-
也就是说,我们要把
[5, 3, 2]
这个子序列,变成它所有排列中最小的那一个。 -
一个序列的最小排列是什么?就是升序排列!所以
[5, 3, 2]
的最小排列是[2, 3, 5]
。 -
一个重要的观察:在执行完第二步和第三步后,
i
后面的序列(即[5, 3, 2]
)必然是降序的。为什么?因为我们是从一个降序序列[5, 4, 2]
中,用一个较小的数3
换走了4
,所以剩下的序列依然保持降序。 -
第四步,就此诞生:对于一个降序序列,如何最快地让它变成升序?直接将它反转 (reverse) 即可!
-
我们将
[5, 3, 2]
反转,得到[2, 3, 5]
。 -
最终结果就是
[1, 4, 2, 3, 5]
。
-
特殊情况
如果第一步就找不到 nums[i] < nums[i+1] 的情况呢?
例如 [5, 4, 3, 2, 1]。我们从右向左扫描完也找不到这样的 i。这说明整个序列已经是完全降序的了,它是所有排列中最大的一个。根据题目要求,此时应该返回最小的排列,也就是将整个序列反转成 [1, 2, 3, 4, 5]。
算法步骤总结
-
从后向前 查找第一个满足
nums[i] < nums[i+1]
的元素,记下其下标i
。这个nums[i]
是我们要替换的“较小数”。 -
如果找不到这样的
i
(即整个序列是降序的),说明这已经是最大的排列。直接将整个数组反转,得到最小排列,程序结束。 -
如果找到了
i
,再从后向前 查找第一个满足nums[j] > nums[i]
的元素,记下其下标j
。这个nums[j]
是用来替换的“较大数”。 -
交换
nums[i]
和nums[j]
。 -
将下标
i+1
到数组末尾的这部分子数组反转。
33. 搜索旋转排序数组 - 力扣(LeetCode)
class Solution {
public:int search(vector<int>& nums, int target) {int l = 0, r = nums.size() - 1;while(l <= r){int mid = l + (r - l) / 2;if(nums[mid] == target) return mid;if(nums[l] <= nums[mid]){ // 有序的在左边if(target >= nums[l] && target < nums[mid]){ // 判断是否在有序的一边r = mid - 1;}else{l = mid + 1;}}else{if(target > nums[mid] && target <= nums[r]){l = mid + 1;}else{r = mid - 1;}}}return -1;}};