1. 程式人生 > 實用技巧 >[LeetCode] 996. Number of Squareful Arrays 平方陣列的個數

[LeetCode] 996. Number of Squareful Arrays 平方陣列的個數


Given an arrayAof non-negative integers, the array issquarefulif for every pair of adjacent elements, their sum is a perfect square.

Return the number of permutations of A that are squareful. Two permutationsA1andA2differ if and only if there is some indexisuch thatA1[i] != A2[i].

Example 1:

Input: [1,17,8]
Output: 2
Explanation:
[1,8,17] and [17,8,1] are the valid permutations.

Example 2:

Input: [2,2,2]
Output: 1

Note:

  1. 1 <= A.length <= 12
  2. 0 <= A[i] <= 1e9

這道題給了一個非負整陣列成的陣列A,定義了一種平方陣列,即任意兩個相鄰的數字之和是個完全平方數,現在讓找出有多少個A的全排列陣列是個平方陣列。其實這道題有兩個難點,一個是如何求全排列,另一個是如何在生成全排列的過程中驗證平方陣列。LeetCode 有好幾道關於全排列的題,比較基本的就是這兩道 PermutationsPermutations II,很明顯這道題中的數字是有可能重複的,所以跟後者更接近。其實這道題的解法就是基於

Permutations II 來做的,只不過在過程中加入了判斷平方陣列的步驟。關於求有重複數字的全排列的講解可以參見上面的帖子,這裡就簡略的講講,首先要給原陣列排序,使得重複的數字相鄰,便於跳過。在遞迴陣列中,記得要跳過重複數字,然後要判斷是否是平方陣列,若 out 不為空,則把前一個數字和當前數字相加,求 Double 型的平方根,這裡用了一個小 trick,對該平方根求 floor,若跟原平方根相同,則說明數字和是個完全平方數,因為若不是完全平方數的話,平方根會有小數部分,向下取整的話必定和之前不同了。後面就是經典的遞迴部分了,注意之後需要還原狀態,參見程式碼如下:


解法一:

class Solution {
public:
    int numSquarefulPerms(vector<int>& A) {
        int res = 0;
        vector<int> visited(A.size()), out;
        sort(A.begin(), A.end());
        helper(A, 0, visited, out, res);
        return res;
    }
    void helper(vector<int>& A, int level, vector<int>& visited, vector<int>& out, int& res) {
        if (level >= A.size()) {
            ++res;
            return;
        }
        for (int i = 0; i < A.size(); ++i) {
            if (visited[i] == 1) continue;
            if (i > 0 && A[i] == A[i - 1] && visited[i - 1] == 0) continue;
            if (!out.empty()) {
                double root = sqrt(A[i] + out.back());
                if (root - floor(root) != 0) continue;
            }
            visited[i] = 1;
            out.push_back(A[i]);
            helper(A, level + 1, visited, out, res);
            visited[i] = 0;
            out.pop_back();
        }
    }
};

求全排列還有一種不停的交換兩個數字位置的解法,同樣,具體講解可以參見這個帖子 Permutations II。這裡用了一個 start 變數,注意和上面的 level 變數區別開來,上面的解法由於是在拼全排列,i每次要從0開始遍歷,而這裡交換的話,i每次從 start 開始遍歷。在去除重複情況後,交換 A[i] 和 A[start],然後還是要判斷平方陣列,不過這裡用了跟上面不同的驗證方法,這裡對數字求平方根後去整型值,就會自動捨去小數部分,然後再平方,若跟原數相同,則說明是完全平方數,參見程式碼如下:


解法二:

class Solution {
public:
    int numSquarefulPerms(vector<int>& A) {
        int res = 0;
        sort(A.begin(), A.end());
        helper(A, 0, res);
        return res;
    }
    void helper(vector<int> A, int start, int& res) {
        if (start >= A.size()) ++res;
        for (int i = start; i < A.size(); ++i) {
            if (i > start && A[i] == A[start]) continue;
            swap(A[i], A[start]);
            if (start == 0 || (start > 0 && isSquare(A[start] + A[start - 1]))) {
                helper(A, start + 1, res);
            }
        }
    }
    bool isSquare(int num) {
        int r = sqrt(num);
        return r * r == num;
    }
};

下面這種解法是論壇上的高分解法,感覺也很巧妙。使用了兩個 HashMap,一個用來建立每個數字和其出現次數之間的對映,另一個是建立數字和陣列中能跟其能組成完全平方數的所有數字的集合之間對映。首先遍歷原陣列A,找出每個數字出現的次數,然後遍歷這個 numCnt,兩個 for 迴圈,每次取兩個數字,驗證其和是否是完全平方數,是的話就加入第二個 HashMap 中。之後就要開始構建全排列了,也是遍歷 numCnt,對每個數字呼叫遞迴函式,這裡用個 left 表示還需要的數字的個數。在遞迴函式中,若 left 等於0了,說明已經湊夠全排列數字的個數,結果 res 自增1,並返回。否則當前數字的對映值自減1,然後遍歷其在 numMap 中可以組成完全平方數的所有數字,若該數字的對映值大於0,對其呼叫遞迴,for 迴圈結束後記得恢復狀態,參見程式碼如下:


解法三:

class Solution {
public:
    int numSquarefulPerms(vector<int>& A) {
        unordered_map<int, int> numCnt;
        unordered_map<int, unordered_set<int>> numMap;
        int res = 0, n = A.size();
        for (int num : A) ++numCnt[num];
        for (auto &a : numCnt) {
            for (auto &b : numCnt) {
                int x = a.first, y = b.first, r = sqrt(x + y);
                if (r * r == x + y) numMap[x].insert(y);
            }
        }
        for (auto &a : numCnt) {
            helper(a.first, n - 1, numCnt, numMap, res);
        }
        return res;
    }
    void helper(int x, int left, unordered_map<int, int>& numCnt, unordered_map<int, unordered_set<int>>& numMap, int& res) {
        if (left == 0) {
            ++res;
            return;
        }
        --numCnt[x];
        for (int y : numMap[x]) {
            if (numCnt[y] > 0 ) {
                helper(y, left - 1, numCnt, numMap, res);
            }
        }
        ++numCnt[x];
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/996


類似題目:

Permutations

Permutations II


參考資料:

https://leetcode.com/problems/number-of-squareful-arrays/

https://leetcode.com/problems/number-of-squareful-arrays/discuss/238593/Java-DFS-%2B-Unique-Perms

https://leetcode.com/problems/number-of-squareful-arrays/discuss/238562/C%2B%2BPython-Backtracking

https://leetcode.com/problems/number-of-squareful-arrays/discuss/238612/4msC%2B%2B-Simple-Backtracking-like-Permutations-II


LeetCode All in One 題目講解彙總(持續更新中...)