Number of Squareful Arrays — Hard Problem & Solution
An array is squareful when the sum of every pair of adjacent elements is a perfect square (0, 1, 4, 9, 16, ...).
- Difficulty: Hard
- Topics: Arrays, Math, Backtracking, Bitmask
- Asked at: Amazon, Google
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
An array is squareful when the sum of every pair of adjacent elements is a perfect square (0, 1, 4, 9, 16, ...).
Return the number of distinct permutations of nums that are squareful. Two permutations are different when they differ at some index — swapping two equal values does not create a new permutation.
Example 1
Input: nums = [1,17,8]
Output: 2
Explanation: `[1,8,17]` and `[17,8,1]`: 1 + 8 = 9 and 8 + 17 = 25.
Example 2
Input: nums = [2,2,2]
Output: 1
Explanation: 2 + 2 = 4, and all orderings are the same array.
Example 3
Input: nums = [3,6,10]
Output: 2
Explanation: `[3,6,10]` and `[10,6,3]`: 9 and 16.
Constraints
1 <= nums.length <= 120 <= nums[i] <= 10^9
How to solve Number of Squareful Arrays
Backtracking over permutations with two prunings: adjacency (the next value must form a square with the previous one) and duplicate suppression (equal values are used in a fixed order).
Approach
- Sort
numsand precomputeok[i][j]=nums[i] + nums[j]is a perfect square. dfs(last, placed): ifplaced == n, count 1.- Otherwise for each unused index
i: skip it ifi > 0,nums[i] == nums[i - 1]andi - 1is unused; skip it iflast >= 0and!ok[last][i]. Mark, recurse withlast = i, unmark. - Return
dfs(-1, 0).
Why it works
The duplicate rule allows exactly one ordering of the copies of each value, so every distinct value sequence is built once; the adjacency check rejects a prefix as soon as it stops being squareful, which keeps the tree small. An equivalent check: a bitmask DP counting index-paths, divided by the factorials of the multiplicities, gives the same number.
Complexity
- Time —
O(n!) worst case, much less with pruning; O(n^2) precomputation - Space —
O(n^2)
Pitfalls
a + bcan exceed 2^31 − 1 — compute it in 64 bits.- Floating-point
sqrtcan be off by one for large values; verify with an integer square. - 0 is a perfect square, so
[0,0]is squareful.
Reference solution
Python
from typing import List
from math import isqrt
def numSquarefulPerms(nums: List[int]) -> int:
a = sorted(nums)
n = len(a)
def square(x):
r = isqrt(x)
return r * r == x
ok = [[square(a[i] + a[j]) for j in range(n)] for i in range(n)]
used = [False] * n
def dfs(last, placed):
if placed == n:
return 1
total = 0
for i in range(n):
if used[i]:
continue
if i > 0 and a[i] == a[i - 1] and not used[i - 1]:
continue
if last >= 0 and not ok[last][i]:
continue
used[i] = True
total += dfs(i, placed + 1)
used[i] = False
return total
return dfs(-1, 0)JavaScript
var numSquarefulPerms = function(nums) {
var a = nums.slice().sort(function(x, y) { return x - y; });
var n = a.length;
var square = function(x) {
var r = Math.floor(Math.sqrt(x));
while (r * r > x) r--;
while ((r + 1) * (r + 1) <= x) r++;
return r * r === x;
};
var ok = [];
for (var i = 0; i < n; i++) {
ok.push([]);
for (var j = 0; j < n; j++) ok[i].push(square(a[i] + a[j]));
}
var used = new Array(n).fill(false);
var dfs = function(last, placed) {
if (placed === n) return 1;
var total = 0;
for (var i = 0; i < n; i++) {
if (used[i]) continue;
if (i > 0 && a[i] === a[i - 1] && !used[i - 1]) continue;
if (last >= 0 && !ok[last][i]) continue;
used[i] = true;
total += dfs(i, placed + 1);
used[i] = false;
}
return total;
};
return dfs(-1, 0);
};Also on the editorial tab: C, C#, C++, Go, Java, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 988 arrays problems · the whole catalogue
Learn the technique: Arrays · Backtracking