微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

996. 正方形数组的数目 状态压缩/回溯

996. 正方形数组的数目

给定一个非负整数数组 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. 1 <= A.length <= 12
  2. 0 <= A[i] <= 1e9

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/number-of-squareful-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

做题结果

成功,还学会了一种有重复元素计算种类的方式 总数/(a!*b!*....*c!),此处a,b,c指代各种数字的个数

方法1:状态压缩

1. 定义每次最后一个选取的位置+已选位置 dp[n][1<<n]

2. 选1个必然可以,dp[i][1<<i]=1

3. 对于某个状态,假设它最后一个选取的x,那在这个基础上可继续选取尚未选取且可与它相连的元素 y,从而扩展1位

4. 最终结果为所有位数最后一位选取的和 sum(dp[i][(1<<n)-1])

5. 由于含有重复元素,结果需要除以所有重复元素阶乘的积

class Solution {
    public int numSquarefulPerms(int[] nums) {
        Map<Integer,Set<Integer>> graph = new HashMap<>();

        int n = nums.length;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                if(i==j) continue;
                if(isConnect(nums[i],nums[j])) graph.computeIfAbsent(i,o->new HashSet<>()).add(j);
            }
        }


        long[][] dp = new long[n][1<<n];//最后一个加入的是谁
        for(int i = 0; i < n; i++){
            dp[i][1<<i]=1;
        }


        for(int i = 1; i < (1<<n); i++){
            //枚举最后加入的两个元素
            for(int j = 0; j < n; j++){
                if((i&(1<<j))>0) {
                    for(Integer k:graph.getorDefault(j,new HashSet<>())){
                        if((i&(1<<k))==0){
                            dp[k][i|(1<<k)]+=dp[j][i];
                        }
                    }
                }
            }
        }

        //去掉重复
        Map<Integer,Integer> times = new HashMap<>();
        for(int v:nums){
            times.put(v,times.getorDefault(v,0)+1);
        }

        long ans = 0;
        for(int i = 0; i < n; i++){
            ans += dp[i][(1<<n)-1];
        }
        for(Integer v:times.values()){
            ans = ans/mul(v);
        }
        return (int) ans;
    }

    private int mul(int v){
        return v==0?1:v*mul(v-1);
    }

    private boolean isConnect(int a, int b){
        int sum = a+b;
        int sqrt = (int) Math.sqrt(sum);
        return sqrt*sqrt==sum;
    }
}

 方法2:回溯

上面算组合数什么的很麻烦,那有没有不用算组合数的方式呢?也有

1. 通过数字构建图,而非索引,然后堆数字分类计数

2. 枚举第一个元素,然后DFS逐步枚举后续元素,如果能枚举到最后一个,则增加1种

3. 注意数目为0的元素不能再枚举

4. 需要恢复现场,比如 123,先枚举1,1消耗一个,计算完数目后,需要恢复一个以满足,2开头的消耗

class Solution {
    public int numSquarefulPerms(int[] nums) {
        Map<Integer,Set<Integer>> graph = new HashMap<>();
        Map<Integer,Integer> times = new HashMap<>();
        for(int num:nums){
            times.put(num,times.getorDefault(num,0)+1);
        }

        for(int num1:nums){
            for(int num2:nums){
                if(isConnect(num1,num2)) graph.computeIfAbsent(num1,o->new HashSet<>()).add(num2);
            }
        }

        int ans = 0;
        for(int key:graph.keySet()){
            times.put(key,times.get(key)-1);
            ans += dfs(graph,times,nums.length-1,key);
            times.put(key,times.get(key)+1);
        }
        return ans;
    }

    private int dfs(Map<Integer,Set<Integer>> graph,Map<Integer,Integer> times,int last,int id){
        if(last == 0) return 1;
        int ans = 0;
        for(Integer item:graph.get(id)){
            if(times.get(item)==0) continue;
            times.put(item,times.get(item)-1);
            ans += dfs(graph,times,last-1,item);
            times.put(item,times.get(item)+1);
        }
        return ans;
    }

    private boolean isConnect(int a, int b){
        int sum = a+b;
        int sqrt = (int) Math.sqrt(sum);
        return sqrt*sqrt==sum;
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐