Leetcode-526 优美的排列

本文分享了解决LeetCode 526问题的两种方法:深度优先搜索(DFS)和状态压缩技巧。通过DFS实现回溯,利用二进制表示数字选择状态,优化了状态转移过程。适合理解动态规划和搜索算法在优化问题中的应用。
摘要由CSDN通过智能技术生成

Leetcode-526 优美的排列

第二天再写博客是真的有用,今天写昨天做的题,第一眼竟然是我做过吗?真的离谱,第二天做还是有助于回忆的。

题目

假设有从 1 到 N 的 N 个整数,如果从这 N 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:

第 i 位的数字能被 i 整除
i 能被第 i 位上的数字整除
现在给定一个整数 N,请问可以构造多少个优美的排列?

输入: 2
输出: 2
解释: 

第 1 个优美的排列是 [1, 2]:
  第 1 个位置(i=1)上的数字是1,1能被 i(i=1)整除
  第 2 个位置(i=2)上的数字是2,2能被 i(i=2)整除

第 2 个优美的排列是 [2, 1]:
  第 1 个位置(i=1)上的数字是2,2能被 i(i=1)整除
  第 2 个位置(i=2)上的数字是1,i(i=2)能被 1 整除

说明:

  1. N 是一个正整数,并且不会超过15。

解答

方法一 DFS

这里我看到题目第一眼想到的就是回溯,当然我更喜欢叫搜索。这一题其实与全排列差不多,只是对每个位置多加了一个限制条件,也就是第 i 位的数字 j,需要 i 能整除 j 或者 j 能整除 i。这样只需要在排列的时候多加一个判断条件即可,如果不满足就返回。

具体思路是先用一个 set 集合存储所有已经使用过的数字。从第一个位置开始判断,第一个位置有可能是从 1 ~ n 的 n 种可能,那么第 2 个位置上的数字有 n -1 种可能。用一个 for 循环检查每一位的所有可能情况,如果没被使用过且可以被整除,就搜索下去,当所有数字用完,说明找到了一个组合,排列数加一。

class Solution {
public:
    int res = 0;
    set<int> se;
    int countArrangement(int n) {
        dfs(0, 1, n);
        return res;
    }

    void dfs(int cnt, int num, int maxn) {
        if (cnt == maxn) {
            res++;
            return ;
        }
        for (int i = 1; i <= maxn; i++) {
            if (se.count(i))
                continue;
            if (i % (cnt + 1) != 0 && (cnt + 1) % i != 0)
                continue;
            se.insert(i);
            dfs(cnt + 1, i, maxn);
            se.erase(i);
        }
    }
};
方法二 状态压缩

因为 n 小于 15,可以使用一个二进制来表示当前哪些位可选,哪些位不可选。假设变量 state 存储了当前的数字使用情况,也就是 state 的二进制对应为 1 的位已经被使用了,并且 state 为 1 的位共有 k 个。那么 state 对应的下一个选择的情况为遍历的数字多一,也就是 state 中的一个为 0 的位会变为 1,同时变化的位与 k + 1 可以整除。

那么定义二维数组 vec[i][state],i 表示正在给位置 i 选择数据,并且前 i -1 个数据选择完毕,state 表示给位置 i 选择完毕后二进制数的状态。考虑状态转移,很明显,f[i] 需要由 f[i - 1] 转化而成。对于状态 state,它的前一个状态应该是 state 中的某一位为 0 时对应的数据,用公式表示就是 state & (!(1 << k))(0 <= k < n,且 state 的第 i 位为 1)。而 state 为 1 的位很可能不止一个,因此需要进行累加。具体代码表示如下:

class Solution {
public:
    int countArrangement(int n) {
        int mask = 1 << n;
        vector<vector<int>> vec(n + 1, vector<int>(mask, 0));
        vec[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int state = 0; state < mask; state++) {
                for (int k = 1; k <= n; k++) {
                    if (((state >> (k - 1)) & 1) == 0)
                        continue;
                    if (k % i && i % k)
                        continue;
                    vec[i][state] += vec[i - 1][state & (~(1 << (k - 1)))];
                }
            }
        }
        return vec[n][mask - 1];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值