Taro Logo

Next Permutation

Medium
Meta logo
Meta
Topics:
ArraysTwo Pointers

A permutation of an array of integers is an arrangement of its members into a sequence or linear order.

  • For example, for arr = [1,2,3], the following are all the permutations of arr: [1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1].

The next permutation of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the next permutation of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order).

  • For example, the next permutation of arr = [1,2,3] is [1,3,2].
  • Similarly, the next permutation of arr = [2,3,1] is [3,1,2].
  • While the next permutation of arr = [3,2,1] is [1,2,3] because [3,2,1] does not have a lexicographical larger rearrangement.

Given an array of integers nums, find the next permutation of nums. The replacement must be in place and use only constant extra memory. Give the optimal solution. Explain Big(O) run-time. Explain Big(O) space usage. Break down edge cases.

Example: nums = [1,2,3] should produce [1,3,2] nums = [3,2,1] should produce [1,2,3] nums = [1,1,5] should produce [1,5,1]

Solution


Naive Approach (Brute Force)

A simple but inefficient approach would be to generate all possible permutations of the input array, sort them lexicographically, and then find the permutation that comes after the given input array.

Algorithm:

  1. Generate all permutations of the array.
  2. Sort the permutations lexicographically.
  3. Find the index of the input array in the sorted list.
  4. Return the next permutation in the list. If the input array is the last permutation, return the first permutation (sorted in ascending order).

Code (Python):

import itertools

def next_permutation_brute_force(nums):
    permutations = sorted(list(itertools.permutations(nums)))
    
    # Find the index of the current permutation
    for i in range(len(permutations)):
        if list(nums) == list(permutations[i]):
            current_index = i
            break
    else:
        return sorted(nums) # Handle the case where nums is not in permutations (shouldn't happen normally)

    # Return the next permutation, or the first if it's the last
    if current_index == len(permutations) - 1:
        return list(permutations[0])
    else:
        return list(permutations[current_index + 1])

Time Complexity: O(n! * n log(n))

  • Generating all permutations takes O(n!).
  • Sorting the permutations takes O(n! * log(n!)), which simplifies to O(n! * n log(n)).
  • Finding the index and returning the next permutation takes O(n!).

Space Complexity: O(n! * n)

  • Storing all permutations requires O(n! * n) space.

Optimal Approach

A more efficient, in-place algorithm can find the next permutation without generating all possible permutations.

Algorithm:

  1. Find the first decreasing element: Starting from the right, find the first element nums[i] that is smaller than its next element nums[i+1]. If no such element is found, the array is in descending order, and we need to reverse the entire array to get the smallest permutation.
  2. Find the smallest element to swap: Again, starting from the right, find the smallest element nums[j] that is greater than nums[i].
  3. Swap: Swap nums[i] and nums[j].
  4. Reverse the suffix: Reverse the subarray starting from nums[i+1] to the end of the array. This ensures that the suffix is in ascending order, resulting in the next lexicographical permutation.

Edge Cases:

  • Empty array: If the array is empty or contains only one element, there is no next permutation, and the array remains unchanged.
  • Array in descending order: If the array is in descending order, reverse it to get the smallest permutation (ascending order).

Code (Python):

def next_permutation(nums):
    n = len(nums)
    
    # 1. Find the first decreasing element
    i = n - 2
    while i >= 0 and nums[i] >= nums[i + 1]:
        i -= 1
    
    if i >= 0:
        # 2. Find the smallest element to swap
        j = n - 1
        while j >= 0 and nums[j] <= nums[i]:
            j -= 1
        
        # 3. Swap
        nums[i], nums[j] = nums[j], nums[i]
    
    # 4. Reverse the suffix
    left = i + 1
    right = n - 1
    while left < right> nums[right]:
            left, right = right, left

    return nums

Time Complexity: O(n)

  • Finding the decreasing element, finding the element to swap, and reversing the suffix all take O(n) time.

Space Complexity: O(1)

  • The algorithm operates in place and uses only constant extra memory.