[LeetCode#31]Next Permutation

Problem:

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place, do not allocate extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

Analysis:

This problem test many basic understandings over the inherent principle of sorting!!!
You must value it!!!

During my implemetation, I have made following coding errors:
Mistake 1: use break in two-level "for-loop". 
The break could only jump out one level loop.
-------------------------------------------------------------------
        int i = -1;
        int j = -1;
        boolean is_found = false;
        for (i = nums.length - 1; i >= 1; i--) {
            for (j = i - 1; j >= 0; j--) {
                if (nums[i] > nums[j]) {
                    is_found = true;
                    break;
                }
            }
        }



Analysis:
? What would cause a number increase? 
The increase on any digit.
1123223 -> 1123224, 2123223

However, for this problem, we have limite numbers to select from. We were only allowed to exchange the position of two numbers.
[1, 2, 3, 4, 5]  - > [1, 2, 3, 5, 4]

[1, 2, 3, (9, 8, 2)]
Since we try to find the next permutation(closest permutation), we try to achieve the larger number at the circle part. Since any increase change in high level number (in this problem, low index nums) must larger than the number we have achieved at the low level number!!!

Now, let use seperate part of the array: (9, 8, 2)
Our goal is to find the the lowest exchange. However for the circle part, it has already reach the maximum number, we could not update on it any more.

Let us enlarge the circle.
[1, 2, (3, 9, 8, 2)]
We apparently could exchange "3" with "9" or "8" to enlarge the number. Since we want the closet number, we apparently should choose '8'.
[1, 2, 8, 9, 3, 2]


Algorithm:
1. Scan from the low level number(with high index in nums), if the num[i] smaller than a number after it, we could enlarge the number by replace them. 
for (int i = nums.length - 2; i >= 0; i--) {
    for (int j = nums.length - 1; j > i; j--) {
        if (nums[i] < nums[j]) {
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
            Arrays.sort(nums, i+1, nums.length);
            return;
        }   
    }
}

2. After the replacement, the numbers after nums[i] may not in ascending order, we need to sort the afterward part.
Arrays.sort(nums, i+1, nums.length);

Ugly solution:
public class Solution {
    public void nextPermutation(int[] nums) {
        if (nums == null)
            throw new IllegalArgumentException("nums is null");
        if (nums.length <= 1)
            return;
        for (int i = nums.length - 2; i >= 0; i--) {
            for (int j = nums.length - 1; j > i; j--) {
                if (nums[i] < nums[j]) {
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                    Arrays.sort(nums, i+1, nums.length);
                    return;
                }   
            }
        }
        reverse(nums);
    }
    
    
    private void reverse(int[] nums) {
        int len = nums.length;
        for (int i = 0; i < len / 2; i++) {
            int temp = nums[i];
            nums[i] = nums[len-1-i];
            nums[len-1-i] = temp;
        }
    }
}




My first solution is really really ugly, it use two level for loop and sort to get the answer. The reason for above ugly codes is the insufficient understanding the invariant between the problem. 

1. two-level for-loop to find the two numbers. 
Why? Have you used the invariant:
[1, 2, 3, (9, 8, 2)]
The scaned part must in descending order (9, 8, 2), otherwise we would stop at low level number. 
Why you still need to compare num[i] with each nums[j] after it? To find the first nums[j] larger than nums[i].
Why not divide it into two steps:

step 1: find num[i] that break the invariant.
int pos = -1; 
for (int i = nums.length - 2; i >= 0; i--) {
    if (nums[i] < nums[i+1]) {
        pos = i;
        break;
    }
}   

step 2: scan from the last number(high index number), to find out nums[j] could be used for the replacement.
if (pos != -1) {
    for (int j = nums.length - 1; j > pos; j--) {
        if (nums[pos] < nums[j]) {
            ...
        }
    }
}


2. why use sort for numbers after nums[i]?
Absolutely no need, since the number after nums[i] must in descending order.
[1, 2, (3, 9, 8, 2)]
=>
[1, 2, (3, 9, 8, 2)]
(9, 8, 2) 
The part after nums[i] must in descending order, and nums[i] < nums[j], thus it must smaller than nums[j+1], 
And we know nums[i] must larger than nums[j-1]. (We scan from the last number). The reaplacement actually not break the invariant(descending order). We just need to reverse it, that's enough!!!
if (nums[pos] < nums[j]) {
    int temp = nums[pos];
    nums[pos] = nums[j];
    nums[j] = temp;
    reverse(nums, pos+1);
    return;
}

Solution:

public class Solution {
     public void nextPermutation(int[] nums) {
        if (nums == null)
            throw new IllegalArgumentException("nums is null");
        if (nums.length <= 1)
            return;
        int pos = -1; 
        for (int i = nums.length - 2; i >= 0; i--) {
            if (nums[i] < nums[i+1]) {
                pos = i;
                break;
            }
        }   
        if (pos != -1) {
            for (int j = nums.length - 1; j > pos; j--) {
                if (nums[pos] < nums[j]) {
                    int temp = nums[pos];
                    nums[pos] = nums[j];
                    nums[j] = temp;
                    reverse(nums, pos+1);
                    return;
                }
            }
        }
        reverse(nums, 0);
    }
    
    
    private void reverse(int[] nums, int start) {
        int front = start;
        int end = nums.length - 1; 
        while (front < end) {
            int temp = nums[front];
            nums[front] = nums[end];
            nums[end] = temp;
            front++;
            end--;
        }
    }
}

 

posted @ 2015-09-07 04:38  airforce  阅读(174)  评论(0编辑  收藏  举报