文章目录
- 题目描述
- 解题方法
- 两遍扫描
- java代码
- 复杂度分析
题目描述
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
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
解题方法
两遍扫描
题目说了原地 修改,只允许使用额外常数空间。那就不能使用额外空间按大小存储数字,只能使用常数空间的变量。
我们需要做的是,找到一种方法,使新排列字典序大于旧排列,但是这个排列变大的幅度要是最小的。具体怎么做呢?我下面简单说一下。
- 第一步,我们先找到一对最靠近右边的较小数与较大数,较小数在较大数左边,较小数与较大数不一定相连,找到的较大数是最接近较小数的数。找到之后,我们需要交换两个数的位置。
- 第二步,我们记录较小数原来的下标位置,交换之后现在这个位置是较大数,这个位置之后的数字都是按照非严格递减排序的,我们需要将后面的数字翻转过来,按照非严格递增排序。还有一种可能是没有找到较小数与较大数,原排列本身就是非严格递减的,此时我们直接将原排列翻转过来。
以排列 [ 3 , 6 , 2 [3,6,\color {red} 2 [3,6,2, 3 , 3 3,\color {red}3 3,3, 1 ] 1] 1] 为例:
- 第一步,我们找到的较小数是2,较大数是最靠近右边的3,此时,将两个数交换后,原排列变为 [ 3 , 6 , 3 [3,6,\color {red} 3 [3,6,3, 3 , 2 , 1 ] 3, 2, 1] 3,2,1]。
- 第二步,我们将交换后的较大数后面的数字翻转过来,此时原排列变为 [ 3 , 6 , 3 [3,6,\color {red} 3 [3,6,3, 1 , 2 , 3 \color {green} 1, 2, 3 1,2,3 ] ] ]。此排列就是原始整数数组的下一个排列。
我描述一下该方法:
- 第一步,设数组为 a a a,长度为 n n n。我们从后向前找到数组中第一个顺序对 ( i , i + 1 ) (i, i+1) (i,i+1),使得 a [ i ] < a [ i + 1 ] a[i] < a[i+1] a[i]<a[i+1]。此时 [ i + 1 , n ) [i+1, n) [i+1,n)是非严格递减排序。 a [ i ] a[i] a[i]即为较小数。
- 第二步,我们从 [ i + 1 , n ) [i+1, n) [i+1,n)中找到最接近 a [ i ] a[i] a[i] 的较大数 a [ j ] a[j] a[j],并使 a [ j ] a[j] a[j] 尽可能靠右。
- 第三步,此时 [ i + 1 , n ) [i+1, n) [i+1,n)是非严格递减排序,我们直接使用双指针翻转区间 [ i + 1 , n ) [i+1, n) [i+1,n),使其变为非严格递增排序。如果没有找到较小数与较大数,我们直接将区间 [ 0 , n ) [0, n) [0,n)进行翻转。
java代码
public void nextPermutation(int[] nums) {if (nums == null || nums.length < 2) {return;}// 记录反转数组的起始位置int index = -1;for (int i = nums.length - 2; i >= 0; i--) {// 当左边的数比右边的数小时if (nums[i] < nums[i + 1]) {// 交换的数是最后两位if (i == nums.length - 2) {swap(i, i + 1, nums);return;} else {// 比nums[i]大且最接近nums[i]的数的下标int minIndex = i + 1;// nums[i + 1] ~ nums[nums.length - 1]是非严格从大到小排列for (int j = i + 2; j < nums.length; j++) {// 从 nums[i + 1] ~ nums[nums.length - 1]中寻找比nums[i]大且最接近nums[i]的数if (nums[i] < nums[j]) {minIndex = j;} else {break;}}swap(i, minIndex, nums);// 记录反转数组的起始位置index = i + 1;break;}}}int l;int r = nums.length - 1;if (index != -1) {l = index;} else {l = 0;}//从数组下标 l ~ r 进行数组交换while (l < r) {swap(l, r, nums);l++;r--;}
}// 数组下标i和j交换函数
public void swap(int i, int j, int[] nums) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;
}
复杂度分析
时间复杂度: O ( N ) O(N) O(N),设数组长度为 N N N,需要扫描两次数组。
空间复杂度: O ( 1 ) O(1) O(1),只需要存储常数级别的指针和变量。
- 个人公众号
- 个人小游戏