Leetcode 996. Number of Squareful Arrays

996. Number of Squareful Arrays


Original Problem

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

Return the number of permutations of A that are squareful. Two permutations A1 and A2 differ if and only if there is some index i such that A1[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

Chinese Translation

给定一个非负整数数组 A,如果该数组每对相邻元素之和是一个完全平方数,则称这一数组为正方形数组。

返回 A 的正方形排列的数目。两个排列 A1 和 A2 不同的充要条件是存在某个索引 i,使得 A1[i] != A2[i]。

示例 1:

输入:[1,17,8]
输出:2
解释:
[1,8,17] 和 [17,8,1] 都是有效的排列。
示例 2:

输入:[2,2,2]
输出:1

提示:

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

Official Solutions

Backtrack

Brief Idea

构造一张图,包含所有的边 ii 到 jj ,如果满足 A[i] + A[j]A[i]+A[j] 是一个完全平方数。我们的目标就是求这张图的所有哈密顿路径,即经过图中所有点仅一次的路径。

Algorithm

我们使用 count 记录对于每一种值还有多少个节点等待被访问,与一个变量 todo 记录还剩多少个节点等待被访问。

对于每一个节点,我们可以访问它的所有邻居节点(从数值的角度来看,从而大大减少复杂度)。

对于每一个节点,我们可以访问它的所有邻居节点(从数值的角度来看,从而大大减少复杂度)。

更多细节请看行内注释。

JAVA Solution
class Solution {
    Map<Integer, Integer> count;
    Map<Integer, List<Integer>> graph;
    public int numSquarefulPerms(int[] A) {
        int N = A.length;
        count = new HashMap();
        graph = new HashMap();

        // count.get(v) : 数组 A 中值为 v 的节点数量
        for (int x: A)
            count.put(x, count.getOrDefault(x, 0) + 1);

        // graph.get(v) : 在 A 中的值 w 满足 v + w 是完全平方数
        //                (ie., "vw" is an edge)
        for (int x: count.keySet())
            graph.put(x, new ArrayList());

        for (int x: count.keySet())
            for (int y: count.keySet()) {
                int r = (int) (Math.sqrt(x + y) + 0.5);
                if (r * r == x + y)
                    graph.get(x).add(y);
            }

        // 增加从 x 开始的可行路径数量
        int ans = 0;
        for (int x: count.keySet())
            ans += dfs(x, N - 1);
        return ans;
    }

    public int dfs(int x, int todo) {
        count.put(x, count.get(x) - 1);
        int ans = 1;  
        if (todo != 0) {
            ans = 0;
            for (int y: graph.get(x)) if (count.get(y) != 0) {
                ans += dfs(y, todo - 1);
            }
        }
        count.put(x, count.get(x) + 1);
        return ans;
    }
}
Python Solution
class Solution(object):
    def numSquarefulPerms(self, A):
        N = len(A)
        count = collections.Counter(A)

        graph = {x: [] for x in count}
        for x in count:
            for y in count:
                if int((x+y)**.5 + 0.5) ** 2 == x+y:
                    graph[x].append(y)

        def dfs(x, todo):
            count[x] -= 1
            if todo == 0:
                ans = 1
            else:
                ans = 0
                for y in graph[x]:
                    if count[y]:
                        ans += dfs(y, todo - 1)
            count[x] += 1
            return ans

        return sum(dfs(x, len(A) - 1) for x in count)
Analysis

时间复杂度
O(N N)O(N N),其中 N​N​ 是 A 的长度。更加严格的复杂度界限在本文中不做赘述。 但是明显构造得到的图中不包含三角形,且图的另外一些性质保证了算法的复杂度。

空间复杂度
O(N)O(N)。

Dynamic

Brief Idea

与 方法一 中相似,构造一样的图。因为节点的数量非常少,所以可以使用掩码标记所有已经过点的方式来进行动态规划。

Algorithm

我们用同样的方式构造与 方法一 中一样的图。

现在,我们令 dfs(node, visited) 等于从 node 节点出发访问剩余的节点的可行方法数。这里,visited 是一个掩码:(visited >> i) & 1 为真,当且仅当第 i 个节点已经被访问过了。

这样计算之后,对于 A 中拥有相同值的节点我们会重复计算。考虑这个因素,对于 A 中的值 x,如果 A 中包含 k 个值为 x 的节点,我们令最终答案除以 k!。

Python Solution
from functools import lru_cache

class Solution:
    def numSquarefulPerms(self, A):
        N = len(A)

        def edge(x, y):
            r = math.sqrt(x+y)
            return int(r + 0.5) ** 2 == x+y

        graph = [[] for _ in range(len(A))]
        for i, x in enumerate(A):
            for j in range(i):
                if edge(x, A[j]):
                    graph[i].append(j)
                    graph[j].append(i)

        # find num of hamiltonian paths in graph

        @lru_cache(None)
        def dfs(node, visited):
            if visited == (1 << N) - 1:
                return 1

            ans = 0
            for nei in graph[node]:
                if (visited >> nei) & 1 == 0:
                    ans += dfs(nei, visited | (1 << nei))
            return ans

        ans = sum(dfs(i, 1<<i) for i in range(N))
        count = collections.Counter(A)
        for v in count.values():
            ans //= math.factorial(v)
        return ans
JAVA Solution
class Solution {
    int N;
    Map<Integer, List<Integer>> graph;
    Integer[][] memo;

    public int numSquarefulPerms(int[] A) {
        N = A.length;
        graph = new HashMap();
        memo = new Integer[N][1 << N];

        for (int i = 0; i < N; ++i)
            graph.put(i, new ArrayList());

        for (int i = 0; i < N; ++i)
            for (int j = i+1; j < N; ++j) {
                int r = (int) (Math.sqrt(A[i] + A[j]) + 0.5);
                if (r * r == A[i] + A[j]) {
                    graph.get(i).add(j);
                    graph.get(j).add(i);
                }
            }


        int[] factorial = new int[20];
        factorial[0] = 1;
        for (int i = 1; i < 20; ++i)
            factorial[i] = i * factorial[i-1];

        int ans = 0;
        for (int i = 0; i < N; ++i)
            ans += dfs(i, 1 << i);

        Map<Integer, Integer> count = new HashMap();
        for (int x: A)
            count.put(x, count.getOrDefault(x, 0) + 1);
        for (int v: count.values())
            ans /= factorial[v];

        return ans;
    }

    public int dfs(int node, int visited) {
        if (visited == (1 << N) - 1)
            return 1;
        if (memo[node][visited] != null)
            return memo[node][visited];

        int ans = 0;
        for (int nei: graph.get(node))
            if (((visited >> nei) & 1) == 0)
                ans += dfs(nei, visited | (1 << nei));
        memo[node][visited] = ans;
        return ans;
    }
}
Analysis

时间复杂度:
O(N 2N)O(N2 N),其中 N 是 A 的长度。

空间复杂度
O(N2N)O(N2N )。

C++ Solution

/**
1.记录下来每个点的后续点
2.记录下来每个点的重复次数 
3.随便选一个点开始遍历,每过一次这个点就把重复次数-1,并遍历后续点。
如果到最后所有点的重复次数都被成功置零,就算一种排列方式。
*/
class Solution {
public:
	map<int, int> count;//不重复元素数量
	map<int, vector<int> > gragh;
	int numSquarefulPerms(vector<int>& A) {
		int N = A.size();
		for (int i = 0; i < N; i++) {
			count[A[i]]++;
		}
		map<int, int>::iterator i, j;
		for (i = count.begin(); i != count.end(); i++) {//构造邻接图
			for (j = count.begin(); j != count.end(); j++) {
				int r = (int)(sqrt(i->first + j->first) + 0.5);
				if (r*r == (i->first + j->first))
					gragh[i->first].push_back(j->first);//满足平方根的两个节点间有连线
			}
		}
		// 增加从 x 开始的可行路径数量
		int ans = 0;
		for (i = count.begin(); i != count.end(); i++) {
			ans += dfs(i->first, N - 1);
		}
		return ans;
	}
	int dfs(int x, int todo) {//从顶点x开始深度遍历
		count[x]--;
		int ans = 1;
		if (todo != 0) {//还有剩余节点未处理
			ans = 0;
			for (unsigned i = 0; i < gragh[x].size(); i++) {//gragh[x]记录x的邻接点
				if (count[gragh[x][i]] != 0) {
					ans += dfs(gragh[x][i], todo - 1);
				}
			}
		}
		count[x]++;
		return ans;//todo=0表示所有节点都被遍历完
		}

Quote

original problem: Leetcode 996
official solution: Leetcode Official solution
c++ solution: 都夜

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值