下一个排列
解:策略:查找最长倒序的后缀,然后将这个后缀与前面的数字交换,翻转过来,就是下一个序列。
class Solution {
public void nextPermutation(int[] nums) {
// find i such that nums[i]<nums[i+1] from nums.length-1 to 0
// if i does not exist,just reverse the array
int i = nums.length-2;
while(i>=0 && nums[i]>=nums[i+1])--i;
if(i>=0){
// find a position for i to swap
// i.e. find the rightmost smallest e in [i+1,n-1] such that e>i
int k=rightmost(nums,nums[i],i+1,nums.length-1);
swap(nums,i,k);
}
reverse(nums,i+1,nums.length-1);
}
int rightmost(int[] nums,int e,int i,int j){
while(i<j){
int m =j+(i-j)/2;
if(nums[m]>e)i=m;
else j=m-1;
}
return i;
}
void reverse(int[] nums,int i,int j){
for(int r=i,p=j;r<p;++r,--p){
int t = nums[r];
nums[r] = nums[p];
nums[p] = t;
}
}
void swap(int[] nums,int i,int j){
int t=nums[i];
nums[i]=nums[j];
nums[j]=t;
}
}
算法解释:因为一个排列中可能存在重复,即1 1 3 3 2[4] 2[5], 如果只是将2[4]和2[5]进行交换,并不能产生下一个排列。只要找到下一个逆序对,即A[i]>A[i+1]的点,交换它们,即可得到下一个排列。为什么?因为从末尾往前查找,所以它们一定是最小的那个。
然后,还需要额外的一步:将尾部进行翻转,因为i+1之后全都是逆序的,翻转过来就是最小的。
总而言之,算法分为两个步骤: 1.找到逆序对,交互 2.翻转尾部
可枚举的排列选择
题目:1220. Count Vowels Permutation
解:使用滚动数组+常量状态优化。
class Solution {
private final int mod = (int)Math.pow(10, 9) + 7;
public int countVowelPermutation(int n) {
if(n == 1) {
return 5;
}
// dp[i][j] simplified to 5 constants using a rolling strategy.
long a = 1, e = 1, i = 1, o = 1, u = 1;
for(int m = 2; m <= n; m++) {
long newA = i + u + e;
long newE = a + i;
long newI = o + e;
long newU = o + i;
long newO = i;
a = newA % mod;
e = newE % mod;
i = newI % mod;
u = newU % mod;
o = newO % mod;
}
return (int)((a + e + i + o + u) % mod);
}
}