题意
实现next_permutation()
。
思路
首先,我们知道,要使下一个数尽量小,那我们就需要下一个变大的数尽量在后面。
假如我们选择位置k的数和某一个位置进行交换,我们需要考虑:为什么不选择k+1及其之后的位置?因为k+1到n的位置上的数已经达到了最大。即k+1到n的数组是降序的。比如我们k+1到n的数字为:1, 2, 3, 4。那么我们可以选择2换到前面使之增大。但是若其为:4, 3, 2, 1。很容易知道其能组成的数字已经达到了最大!所以我们要选择的位置为最后一个升序点之前的那一个点。
即假如我们的值为7, 1, 3, 6, 5, 4, 2。那么我们的最后一个升序点为6,所以它之前的那个点为3,因为是升序的,我们很容易知道3和6交换能够使值变大。但是能否使值变大但是变大的最小呢?
我们可以画一个图来表示这个阶段:
很容易观察知道:我们将4和3交换位置(即在3之后,最小的并且大于3的点),然后对后面进行排序,就可以得到我们下一个排列,于是得到了我们时间复杂度为
O(nlogn)
的算法。
算法1
时间复杂度 O(nlogn)
- 找到最后一个升序点的前一个点A。
- 找到A之后最后一个最小的并且大于A的点B。
- 交换A和B的值,并对A之后的点从小到大排序。
算法2
时间复杂度 O(n) 。
我们考虑能否利用已有的性质不进行排序。
首先,我们还是和之前的算法一样:
- 找到最后一个升序点的前一个点A。
- 找到A之后最后一个最小的并且大于A的点B。
于是,我们现在的状态就是这样:
C是A之前的部分,D是AB之间的哪些部分,E是B之后的部分。
我们需要将B放到A的位置上,那么,我们的状态为:
我们需要做的就是:使G部分最小(G是由A和D和E组成的)
因为A是最后一个升序点之前的那个点,于是我们就知道:D, B, E三部分均为降序,并且有 D>B>E 和 B>A (会存在=的情况,和>一样的)。
于是我们有如下的性质: D>A>E 。
即我们的G部分的组成为:
并且,由我们之前的结论知道:我们的D和E均为降序的,所以我们需要将E和Dreverse
来使之为升序的(我们可以先reverse再交换AB使只reverse一次)。
所以,我们可以得到我们完整的算法2:
- 找到最后一个升序点的前一个点A。
- 找到A之后大于A并且最小的那个点B(尽量靠后)
- 将A之后的位置reverse。
- 交换A和B。
时间复杂度 O(n) 。
代码
algorithm 1
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size(), i = n - 2;
//find the last increase point
for (; i >= 0; i--) {
if (nums[i] < nums[i + 1]) break;
}
int ind = i + 1;
for (int j = i + 1; j < n; j++)
if (nums[j] > nums[i] && nums[j] < nums[ind])
ind = j;
if (i >= 0) swap(nums[i], nums[ind]);
if (i + 1 < n) sort(nums.begin() + i + 1, nums.end());
}
};
algorithm 2
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size(), i = n - 2;
for (; i >= 0; i--) {
if (nums[i] < nums[i + 1]) break;
}
int ind = i + 1;
if (i >= 0) {
for (int j = i + 1; j < n; j++)
if (nums[j] > nums[i] && nums[j] <= nums[ind])
ind = j;
}
if (i + 1 < n) reverse(nums.begin() + i + 1, nums.end());
if (i >= 0) swap(nums[i], nums[i + n - ind]);
}
};