## 题目地址(31. 下一个排列)

https://leetcode-cn.com/problems/next-permutation/

## 题目描述

```
实现获取下一个排列的函数，算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列，则将数字重新排列成最小的排列（即升序排列）。

必须原地修改，只允许使用额外常数空间。

以下是一些例子，输入位于左侧列，其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

```

## 前置知识

- 回溯法

## 公司

- 阿里
- 腾讯
- 百度
- 字节

## 思路

符合直觉的方法是按顺序求出所有的排列，如果当前排列等于 nums，那么我直接取下一个但是这种做法不符合 constant space 要求（题目要求直接修改原数组）,时间复杂度也太高，为 O(n!),肯定不是合适的解。

这种题目比较抽象，写几个例子通常会帮助理解问题的规律。我找了几个例子，其中蓝色背景表示的是当前数字找下一个更大排列的时候`需要改变的元素`.

![31.next-permutation](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlu4t2qbfj30cx0703yw.jpg)

我们不难发现，蓝色的数字都是从后往前第一个不递增的元素，并且我们的下一个更大的排列
只需要改变蓝色的以及之后部分即可，前面的不需要变。

那么怎么改变蓝色的以及后面部分呢？为了使增量最小，
由于前面我们观察发现，其实剩下的元素从左到右是递减的，而我们想要变成递增的，我们只需要不断交换首尾元素即可。

另外我们也可以以回溯的角度来思考这个问题，让我们先回溯一次：

![31.next-permutation-2](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlu4tmf9vj30d204r74f.jpg)

这个时候可以选择的元素只有 2，我们无法组成更大的排列，我们继续回溯，直到如图：

![31.next-permutation-3](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlu4ukjgej30go07imxq.jpg)

我们发现我们可以交换 4 或者 2 实现变大的效果，但是要保证变大的幅度最小（下一个更大），
我们需要选择最小的，由于之前我们发现后面是从左到右递减的，显然就是交换最右面大于 1 的。

之后就是不断交换使之幅度最小：

![31.next-permutation-4](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlu4vhrisj30h00cmwfn.jpg)

## 关键点解析

- 写几个例子通常会帮助理解问题的规律
- 在有序数组中首尾指针不断交换位置即可实现 reverse
- 找到从右边起`第一个大于nums[i]的`，并将其和 nums[i]进行交换

## 代码

语言支持: Javascript, Python3, CPP

JavaScript Code:

```js
/*
 * @lc app=leetcode id=31 lang=javascript
 *
 * [31] Next Permutation
 */

function reverseRange(A, i, j) {
  while (i < j) {
    const temp = A[i];
    A[i] = A[j];
    A[j] = temp;
    i++;
    j--;
  }
}
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var nextPermutation = function (nums) {
  // 时间复杂度O(n) 空间复杂度O(1)
  if (nums == null || nums.length <= 1) return;

  let i = nums.length - 2;
  // 从后往前找到第一个降序的,相当于找到了我们的回溯点
  while (i > -1 && nums[i + 1] <= nums[i]) i--;

  // 如果找了就swap
  if (i > -1) {
    let j = nums.length - 1;
    // 找到从右边起第一个大于nums[i]的，并将其和nums[i]进行交换
    // 因为如果交换的数字比nums[i]还要小肯定不符合题意
    while (nums[j] <= nums[i]) j--;
    const temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
  }

  // 最后我们只需要将剩下的元素从左到右，依次填入当前最小的元素就可以保证是大于当前排列的最小值了
  // [i + 1, A.length -1]的元素进行反转

  reverseRange(nums, i + 1, nums.length - 1);
};
```

Python3 Code:

```python
class Solution:
    def nextPermutation(self, nums):
        """
        Do not return anything, modify nums in-place instead.
        :param list nums
        """
        # 第一步，从后往前，找到下降点
        down_index = None
        for i in range(len(nums)-2, -1, -1):
            if nums[i] < nums[i+1]:
                down_index = i
                break
        # 如果没有下降点，重新排列
        if down_index is None:
            nums.reverse()
        # 如果有下降点
        else:
            # 第二步，从后往前，找到比下降点大的数，对换位置
            for i in range(len(nums)-1, i, -1):
                if nums[down_index] < nums[i]:
                    nums[down_index], nums[i] = nums[i], nums[down_index]
                    break
            # 第三部，重新排列下降点之后的数
            i, j = down_index+1, len(nums)-1
            while i < j:
                nums[i], nums[j] = nums[j], nums[i]
                i += 1
                j -= 1
```

CPP Code:

```cpp
class Solution {
public:
  void nextPermutation(vector<int>& nums) {
    int i = nums.size() - 2, j = nums.size() - 1;
    while (i >= 0 && nums[i] >= nums[i + 1]) --i;
    if (i >= 0) {
      while (j > i && nums[j] <= nums[i]) --j;
      swap(nums[i], nums[j]);
    }
    reverse(nums.begin() + i + 1, nums.end());
  }
};
```

## 相关题目

- [46.next-permutation](./46.next-permutation.md)
- [47.permutations-ii](./47.permutations-ii.md)
- [60.permutation-sequence](./60.permutation-sequence.md)(TODO)
