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 <= 12
  • 0 <= 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

  1. Sort nums and precompute ok[i][j] = nums[i] + nums[j] is a perfect square.
  2. dfs(last, placed): if placed == n, count 1.
  3. Otherwise for each unused index i: skip it if i > 0, nums[i] == nums[i - 1] and i - 1 is unused; skip it if last >= 0 and !ok[last][i]. Mark, recurse with last = i, unmark.
  4. 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 + b can exceed 2^31 − 1 — compute it in 64 bits.
  • Floating-point sqrt can 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