题目
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
思路
从后往前遍历数组,将数组分为两个部分 [ 0, i ] 以及 [ t, hi ],在后一部分中找到比 nums[ i ] 大的第一个元素,与之交换后,再将 [ t , hi ] 中的元素以升序输出。注意,在索引 i 从后往前遍历的过程中, [ t, hi ] 可始终保持降序的有序性,原因:只有比 [ t, hi ] 的所有元素都大才会被插入至 [ t, hi ] 的最前端。实现如下:
class Solution {
public:
void nextPermutation(vector<int>& nums) {
if(nums.empty())
return;
int hi=nums.size()-1;
if(nums.size()<=2){
swap(nums[0], nums[hi]);
return;
}
int t=hi+1, i=hi;
//至少有三个元素
for(; i>=0; i--, t--){
if(t==nums.size())
continue;
//查找[t, hi]之间第一个大于nums[i]的元素
int j=search(t, hi, nums[i], nums);
if(j==-1){//没有找到相应的元素
if(i==0)//字典最大序的情况
reverse(0, hi, nums);
continue;
}else{
swap(nums[i], nums[j]);
reverse(t, hi, nums);
break;
}
}
}
int search(int lo, int hi, int target, vector<int>& nums){
if(target>nums[lo])
return -1;
//线性查找
for(int i=hi; i>=lo; i--){
if(nums[i]>target)
return i;
}
return -1;
}
void reverse(int lo, int hi, vector<int>& nums){
while(lo<hi){
swap(nums[lo], nums[hi]);
lo++, hi--;
}
}
};
由于是有序数组,所以可以在查找效率上进行提升(二分查找注意,1、搜索区间确定 [ lo, hi ]。2、索引移动后对其它区间的影响。3、返回索引是否越界可以看其增长方向):
int search(int lo, int hi, int target, vector<int>& nums){
int t=lo;
while(lo<=hi){//令索引小于 lo 的元素全部大于nums[lo],索引大于hi的元素全部小于等于nums[hi]
int mi=lo+(hi-lo)/2;
if(nums[mi]<target)
hi=mi-1;
else if(target<nums[mi])
lo=mi+1;
else if(target==nums[mi])
hi=mi-1;
}
//终止时, lo元素必定不大于 target或者越界(为nums.size()),而lo-1必定大于target或者越界(lo从未增长过)
if(lo==t)
return -1;
return lo-1;
}