[LeetCode] 996. Number of Squareful Arrays 平方陣列的個數
Given an arrayA
of 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 permutationsA1
andA2
differ if and only if there is some indexi
such 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 <= A.length <= 12
0 <= A[i] <= 1e9
這道題給了一個非負整陣列成的陣列A,定義了一種平方陣列,即任意兩個相鄰的數字之和是個完全平方數,現在讓找出有多少個A的全排列陣列是個平方陣列。其實這道題有兩個難點,一個是如何求全排列,另一個是如何在生成全排列的過程中驗證平方陣列。LeetCode 有好幾道關於全排列的題,比較基本的就是這兩道 Permutations 和 Permutations II,很明顯這道題中的數字是有可能重複的,所以跟後者更接近。其實這道題的解法就是基於
解法一:
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
類似題目:
參考資料:
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