Before attempting this problem, you should be comfortable with:
Array Manipulation - In-place swapping and reversing elements
Two Pointers Technique - Using left and right pointers to reverse subarrays
Lexicographic Ordering - Understanding how sequences are compared and ordered
1. Brute Force
Intuition
The most straightforward approach generates all permutations of the array, sorts them in lexicographic order, finds the current permutation in that sorted list, and returns the next one. If we are at the last permutation, we wrap around to the first.
This approach is extremely inefficient for large arrays since the number of permutations is n!, but it demonstrates the concept of what "next permutation" means.
Algorithm
Generate all unique permutations of the input array.
Sort the permutations lexicographically.
Find the current array in the sorted list.
Return the next permutation in the list (wrapping to the first if at the end).
classSolution:defnextPermutation(self, nums: List[int])->None:"""
Do not return anything, modify nums in-place instead.
"""
permutations = self.permute(nums[:])
permutations.sort()for i, p inenumerate(permutations):if p == nums:
nextP = permutations[(i +1)%len(permutations)]for j inrange(len(nums)):
nums[j]= nextP[j]breakdefpermute(self, nums: List[int])-> List[List[int]]:
res =[]defdfs(i):if i ==len(nums):
res.append(nums.copy())returnfor j inrange(i,len(nums)):if j > i and nums[i]== nums[j]:continue
nums[i], nums[j]= nums[j], nums[i]
dfs(i +1)for j inrange(len(nums)-1, i,-1):
nums[j], nums[i]= nums[i], nums[j]
nums.sort()
dfs(0)return res
publicclassSolution{publicvoidnextPermutation(int[] nums){List<List<Integer>> perms =permute(nums.clone());Collections.sort(perms,(a, b)->{for(int i =0; i < a.size(); i++){int diff = a.get(i)- b.get(i);if(diff !=0)return diff;}return0;});for(int i =0; i < perms.size(); i++){List<Integer> p = perms.get(i);boolean match =true;for(int j =0; j < nums.length; j++){if(p.get(j)!= nums[j]){ match =false;break;}}if(match){List<Integer> next = perms.get((i +1)% perms.size());for(int j =0; j < nums.length; j++){
nums[j]= next.get(j);}return;}}}privateList<List<Integer>>permute(int[] nums){List<List<Integer>> res =newArrayList<>();Arrays.sort(nums);boolean[] used =newboolean[nums.length];List<Integer> path =newArrayList<>();dfs(nums, used, path, res);return res;}privatevoiddfs(int[] nums,boolean[] used,List<Integer> path,List<List<Integer>> res){if(path.size()== nums.length){
res.add(newArrayList<>(path));return;}for(int i =0; i < nums.length; i++){if(used[i])continue;if(i >0&& nums[i]== nums[i -1]&&!used[i -1])continue;
used[i]=true;
path.add(nums[i]);dfs(nums, used, path, res);
path.remove(path.size()-1);
used[i]=false;}}}
classSolution{public:voidnextPermutation(vector<int>& nums){auto perms =permute(nums);sort(perms.begin(), perms.end());for(int i =0; i < perms.size(); i++){if(perms[i]== nums){auto& next = perms[(i +1)% perms.size()];
nums = next;return;}}}private:
vector<vector<int>>permute(vector<int> nums){sort(nums.begin(), nums.end());
vector<vector<int>> res;
vector<int> path;
vector<bool>used(nums.size(),false);
function<void()> dfs =[&](){if(path.size()== nums.size()){
res.push_back(path);return;}for(int i =0; i < nums.size(); i++){if(used[i])continue;if(i >0&& nums[i]== nums[i -1]&&!used[i -1])continue;
used[i]=true;
path.push_back(nums[i]);dfs();
path.pop_back();
used[i]=false;}};dfs();return res;}};
classSolution{/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/nextPermutation(nums){constpermute=(arr)=>{const res =[];
arr.sort((a, b)=> a - b);const used =Array(arr.length).fill(false);const path =[];constdfs=()=>{if(path.length === arr.length){
res.push([...path]);return;}for(let i =0; i < arr.length; i++){if(used[i])continue;if(i >0&& arr[i]=== arr[i -1]&&!used[i -1])continue;
used[i]=true;
path.push(arr[i]);dfs();
path.pop();
used[i]=false;}};dfs();return res;};const perms =permute([...nums]);
perms.sort((a, b)=>{for(let i =0; i < a.length; i++){if(a[i]!== b[i])return a[i]- b[i];}return0;});for(let i =0; i < perms.length; i++){const p = perms[i];if(p.every((v, j)=> v === nums[j])){const next = perms[(i +1)% perms.length];for(let j =0; j < nums.length; j++){
nums[j]= next[j];}break;}}}}
publicclassSolution{publicvoidNextPermutation(int[] nums){var perms =Permute((int[])nums.Clone());
perms.Sort((a, b)=>{for(int i =0; i < a.Count; i++){int diff = a[i]- b[i];if(diff !=0)return diff;}return0;});for(int i =0; i < perms.Count; i++){var p = perms[i];bool match =true;for(int j =0; j < nums.Length; j++){if(p[j]!= nums[j]){ match =false;break;}}if(match){var next = perms[(i +1)% perms.Count];for(int j =0; j < nums.Length; j++){
nums[j]= next[j];}return;}}}privateList<List<int>>Permute(int[] nums){
Array.Sort(nums);var res =newList<List<int>>();var used =newbool[nums.Length];var path =newList<int>();voidDfs(){if(path.Count == nums.Length){
res.Add(newList<int>(path));return;}for(int i =0; i < nums.Length; i++){if(used[i])continue;if(i >0&& nums[i]== nums[i -1]&&!used[i -1])continue;
used[i]=true;
path.Add(nums[i]);Dfs();
path.RemoveAt(path.Count -1);
used[i]=false;}}Dfs();return res;}}
funcnextPermutation(nums []int){
perms :=permute(append([]int{}, nums...))
sort.Slice(perms,func(a, b int)bool{for i :=0; i <len(perms[a]); i++{if perms[a][i]!= perms[b][i]{return perms[a][i]< perms[b][i]}}returnfalse})for i :=0; i <len(perms); i++{
match :=truefor j :=0; j <len(nums); j++{if perms[i][j]!= nums[j]{
match =falsebreak}}if match {
next := perms[(i+1)%len(perms)]copy(nums, next)return}}}funcpermute(nums []int)[][]int{
sort.Ints(nums)var res [][]int
used :=make([]bool,len(nums))var path []intvar dfs func()
dfs =func(){iflen(path)==len(nums){
res =append(res,append([]int{}, path...))return}for i :=0; i <len(nums); i++{if used[i]{continue}if i >0&& nums[i]== nums[i-1]&&!used[i-1]{continue}
used[i]=true
path =append(path, nums[i])dfs()
path = path[:len(path)-1]
used[i]=false}}dfs()return res
}
class Solution {funnextPermutation(nums: IntArray){val perms =permute(nums.clone())
perms.sortWith{ a, b ->for(i in a.indices){if(a[i]!= b[i])return@sortWith a[i]- b[i]}0}for(i in perms.indices){var match =truefor(j in nums.indices){if(perms[i][j]!= nums[j]){
match =falsebreak}}if(match){val next = perms[(i +1)% perms.size]for(j in nums.indices){
nums[j]= next[j]}return}}}privatefunpermute(nums: IntArray): MutableList<IntArray>{
nums.sort()val res = mutableListOf<IntArray>()val used =BooleanArray(nums.size)val path = mutableListOf<Int>()fundfs(){if(path.size == nums.size){
res.add(path.toIntArray())return}for(i in nums.indices){if(used[i])continueif(i >0&& nums[i]== nums[i -1]&&!used[i -1])continue
used[i]=true
path.add(nums[i])dfs()
path.removeAt(path.size -1)
used[i]=false}}dfs()return res
}}
classSolution{funcnextPermutation(_ nums:inout[Int]){var perms =permute(nums)
perms.sort { a, b infor i in0..<a.count {if a[i]!= b[i]{return a[i]< b[i]}}returnfalse}for i in0..<perms.count {var match =truefor j in0..<nums.count {if perms[i][j]!= nums[j]{
match =falsebreak}}if match {let next = perms[(i +1)% perms.count]for j in0..<nums.count {
nums[j]= next[j]}return}}}privatefuncpermute(_ nums:[Int])->[[Int]]{var nums = nums.sorted()var res =[[Int]]()var used =Array(repeating:false, count: nums.count)var path =[Int]()funcdfs(){if path.count == nums.count {
res.append(path)return}for i in0..<nums.count {if used[i]{continue}if i >0&& nums[i]== nums[i -1]&&!used[i -1]{continue}
used[i]=true
path.append(nums[i])dfs()
path.removeLast()
used[i]=false}}dfs()return res
}}
Time & Space Complexity
Time complexity: O(n!∗n)
Space complexity: O(n!∗n)
2. Greedy
Intuition
To find the next lexicographically greater permutation, we need to make the smallest possible change that increases the array's value. The key insight is to find the rightmost position where we can make an increase.
Scan from right to left to find the first element that is smaller than its right neighbor. This element (call it pivot) can be swapped with something larger to get a bigger permutation. We then find the smallest element to the right of pivot that is still larger than pivot and swap them.
After the swap, everything to the right of the pivot position is in descending order. To get the smallest possible permutation from this point, we reverse that suffix to ascending order.
If no such pivot exists (the array is fully descending), we are at the largest permutation, so we reverse the entire array to get the smallest.
Algorithm
Scan from the second-to-last element leftward to find the first index i where nums[i] < nums[i+1].
If such an index exists:
Scan from the right to find the first index j where nums[j] > nums[i].
classSolution:defnextPermutation(self, nums:list[int])->None:"""
Do not return anything, modify nums in-place instead.
"""
n =len(nums)
i = n -2while i >=0and nums[i]>= nums[i +1]:
i -=1if i >=0:
j = n -1while nums[j]<= nums[i]:
j -=1
nums[i], nums[j]= nums[j], nums[i]
l, r = i +1, n -1while l < r:
nums[l], nums[r]= nums[r], nums[l]
l +=1
r -=1
publicclassSolution{publicvoidnextPermutation(int[] nums){int n = nums.length;int i = n -2;while(i >=0&& nums[i]>= nums[i +1]){
i--;}if(i >=0){int j = n -1;while(nums[j]<= nums[i]){
j--;}swap(nums, i, j);}int l = i +1, r = n -1;while(l < r){swap(nums, l++, r--);}}privatevoidswap(int[] nums,int i,int j){int tmp = nums[i];
nums[i]= nums[j];
nums[j]= tmp;}}
classSolution{public:voidnextPermutation(vector<int>& nums){int n = nums.size();int i = n -2;while(i >=0&& nums[i]>= nums[i +1]){
i--;}if(i >=0){int j = n -1;while(nums[j]<= nums[i]){
j--;}swap(nums[i], nums[j]);}int l = i +1, r = n -1;while(l < r){swap(nums[l++], nums[r--]);}}};
classSolution{/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/nextPermutation(nums){const n = nums.length;let i = n -2;while(i >=0&& nums[i]>= nums[i +1]){
i--;}if(i >=0){let j = n -1;while(nums[j]<= nums[i]){
j--;}[nums[i], nums[j]]=[nums[j], nums[i]];}let l = i +1,
r = n -1;while(l < r){[nums[l], nums[r]]=[nums[r], nums[l]];
l++;
r--;}}}
publicclassSolution{publicvoidNextPermutation(int[] nums){int n = nums.Length;int i = n -2;while(i >=0&& nums[i]>= nums[i +1]){
i--;}if(i >=0){int j = n -1;while(nums[j]<= nums[i]){
j--;}Swap(nums, i, j);}int l = i +1, r = n -1;while(l < r){Swap(nums, l++, r--);}}privatevoidSwap(int[] nums,int i,int j){int tmp = nums[i];
nums[i]= nums[j];
nums[j]= tmp;}}
funcnextPermutation(nums []int){
n :=len(nums)
i := n -2for i >=0&& nums[i]>= nums[i+1]{
i--}if i >=0{
j := n -1for nums[j]<= nums[i]{
j--}
nums[i], nums[j]= nums[j], nums[i]}
l, r := i+1, n-1for l < r {
nums[l], nums[r]= nums[r], nums[l]
l++
r--}}
class Solution {funnextPermutation(nums: IntArray){val n = nums.size
var i = n -2while(i >=0&& nums[i]>= nums[i +1]){
i--}if(i >=0){var j = n -1while(nums[j]<= nums[i]){
j--}
nums[i]= nums[j].also{ nums[j]= nums[i]}}var l = i +1var r = n -1while(l < r){
nums[l]= nums[r].also{ nums[r]= nums[l]}
l++
r--}}}
classSolution{funcnextPermutation(_ nums:inout[Int]){let n = nums.count
var i = n -2while i >=0&& nums[i]>= nums[i +1]{
i -=1}if i >=0{var j = n -1while nums[j]<= nums[i]{
j -=1}
nums.swapAt(i, j)}var l = i +1var r = n -1while l < r {
nums.swapAt(l, r)
l +=1
r -=1}}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1)
Common Pitfalls
Finding Pivot with Wrong Comparison
The pivot is the rightmost element that is smaller than its right neighbor (nums[i] < nums[i+1]). Using <= instead of < causes the algorithm to fail on arrays with duplicate elements, as it may skip valid pivot positions.
Swapping with Wrong Element
After finding the pivot, you must swap it with the smallest element to its right that is still larger than the pivot. Swapping with the first larger element from the left (instead of from the right) may not produce the lexicographically smallest increase.
Forgetting to Reverse the Suffix
After swapping, the suffix to the right of the pivot position is in descending order. Failing to reverse it to ascending order produces a larger permutation than necessary. The reversal is essential to get the next (smallest) greater permutation.