1. 程式人生 > >[LeetCode] Beautiful Arrangement 優美排列

[LeetCode] Beautiful Arrangement 優美排列

Suppose you have N integers from 1 to N. We define a beautiful arrangement as an array that is constructed by these N numbers successfully if one of the following is true for the ith position (1 ≤ i ≤ N) in this array:

  1. The number at the ith position is divisible by i.
  2. i is divisible by the number at the ith position.

Now given N, how many beautiful arrangements can you construct?

Example 1:

Input: 2
Output: 2
Explanation: 

The first beautiful arrangement is [1, 2]:
Number at the 1st position (i=1) is 1, and 1 is divisible by i (i=1).
Number at the 2nd position (i=2) is 2, and 2 is divisible by i (i=2).
The second beautiful arrangement is [2, 1]:
Number at the 1st position (i=1) is 2, and 2 is divisible by i (i=1).
Number at the 2nd position (i=2) is 1, and i (i=2) is divisible by 1.

Note:

  1. N is a positive integer and will not exceed 15.

這道題給了我們1到N,總共N個正數,然後定義了一種優美排列方式,對於該排列中的所有數,如果數字可以整除下標,或者下標可以整除數字,那麼我們就是優美排列,讓我們求出所有優美排列的個數。那麼對於求種類個數,或者是求所有情況,這種問題通常要用遞迴來做,遞迴簡直是暴力的不能再暴力的方法了。而遞迴方法等難點在於寫遞迴函式,如何確定終止條件,還有for迴圈中變數的起始位置如何確定。那麼這裡我們需要一個visited陣列來記錄數字是否已經訪問過,因為優美排列中不能有重複數字。我們用變數pos來標記已經生成的數字的個數,如果大於N了,說明已經找到了一組排列,結果res自增1。在for迴圈中,i應該從1開始,因為我們遍歷1到N中的所有數字,如果該數字未被使用過,且滿足和座標之間的整除關係,那麼我們標記該數字已被訪問過,再呼叫下一個位置的遞迴函式,之後不要忘記了恢復初始狀態,參見程式碼如下:

解法一:

class Solution {
public:
    int countArrangement(int N) {
        int res = 0;
        vector<int> visited(N + 1, 0);
        helper(N, visited, 1, res);
        return res;
    }
    void helper(int N, vector<int>& visited, int pos, int& res) {
        if (pos > N) {
            ++res; 
            return;
        }
        for (int i = 1; i <= N; ++i) {
            if (visited[i] == 0 && (i % pos == 0 || pos % i == 0)) {
                visited[i] = 1;
                helper(N, visited, pos + 1, res);
                visited[i] = 0;
            }
        }
    }
};

上面的解法在N=4時產生的優美序列如下:

1 2 3 4
1 4 3 2
2 1 3 4
2 4 3 1
3 2 1 4
3 4 1 2
4 1 3 2
4 2 3 1

通過看上面的分析,是不是覺得這道題的本質其實是求全排列,然後在所有全排列中篩選出符合題意的排列。那麼求全排列的另一種經典解法就是交換陣列中任意兩個數字的位置,來形成新的排列,參見程式碼如下:

解法二:

class Solution {
public:
    int countArrangement(int N) {
        vector<int> nums(N);
        for (int i = 0; i < N; ++i) nums[i] = i + 1;
        return helper(N, nums);
    }
    int helper(int n, vector<int>& nums) {
        if (n <= 0) return 1;
        int res = 0;
        for (int i = 0; i < n; ++i) {
            if (n % nums[i] == 0 || nums[i] % n == 0) {
                swap(nums[i], nums[n - 1]);
                res += helper(n - 1, nums);
                swap(nums[i], nums[n - 1]);
            }
        }
        return res;
    }
};

上面的解法在N=4時產生的優美序列如下:

2 4 3 1
4 2 3 1
3 4 1 2
4 1 3 2
1 4 3 2
3 2 1 4
2 1 3 4
1 2 3 4

參考資料: