LeetCode:526. Beautiful Arrangement

在这里插入图片描述

这题一开始想复杂了,想到了回溯,但是没想到回溯就行。。。
方法一:
最基本的回溯:

class Solution {
public:
    int countArrangement(int N) {
        vector<bool> used(N+1, false);
        int retCnt = 0;
        helper(used, 1, retCnt);
        return retCnt;
    }
private:
    void helper(vector<bool>& used, int idx, int& retCnt) {
        if (idx == used.size()) {
            ++retCnt;
            return;
        }
        for (int i = 1; i < used.size(); ++i) 
            if (!used[i] && (idx % i == 0 || i % idx == 0)) {
                used[i] = true;
                helper(used, idx+1, retCnt);
                used[i] = false;
            }
    }
};

基本就是把所有情况实验一遍,比较慢。

方法二:
top-down+memo。
老朋友了,想到这个方法需要观察到:上面的方法重复计算了一些子问题,比如:情况一:位置1选择2,位置2选择8。情况二:位置1选择8,位置2选择2.这两种情况的计算结果都是一样的。因为都是把剩下的那些放到3-最后上面。
正式地:把状态定义为:当前手中还剩下一些数,把这些数放到最后的那些位置上,共有多少种放法。

class Solution {
public:
    int countArrangement(int N) {
        unordered_map<int, int> m;
        vector<bool> used(N+1, false);
        return helper(used, m, 1);
    }
private:
    int helper(vector<bool>& used, unordered_map<int, int>& m, int idx) {
        if (idx == used.size())
            return 1;//这里是返回1,而不是0,因为这里代表了到头了,代表了一种方法
        int key = getKey(used);
        if (m.find(key) != m.end())
            return m[key];
        int ret = 0;
        for (int i = 1; i < used.size(); ++i) {
            if (!used[i] && (i % idx == 0 || idx % i == 0)) {
                used[i] = true;
                ret += helper(used, m, idx+1);
                used[i] = false;
            }
        }
        m[key] = ret;
        return ret;
    }

    int getKey(vector<bool>& used) {
        int ret = 0;
        for (bool b : used) {
            if (b)
                ret = (ret << 1) | 1;
            else
                ret = ret << 1;
        }
        return ret;
    }
};

因为说了不超过15个,所以可以用int的位来表示有哪些用了,有哪些没有用。
注意,只用used数组就可以完全地表示状态,idx可以有used推出来(看看有多少已经用了),单独计算idx只是为了方便,所以map的key只用used数组就可以表示了。
应该可以感觉到和之前一题很像了,https://blog.csdn.net/weixin_43462819/article/details/100667574
几乎一模一样。但是遗憾的是这一题没有自己想出来这个做法(难在不容易看出重叠的子问题)。

方法三:
一种交换的方法。
和上面不太一样,上面都是比如说:我手里有一些数字,我一个一个地放;这里是数字都在,我对它们进行交换。
就是把每个符合当前位置要求的数字都放到这儿来试一下。
这个比较难想出来,看看吧:

class Solution {
public:
    int countArrangement(int N) {
        vector<int> nums;
        for (int i = 0; i <= N; ++i)
            nums.push_back(i);
        int retCnt = 0;
        backtrack(nums, 1, retCnt);
        return retCnt;
    }
private:
    void backtrack(vector<int>& nums,int idx, int& retCnt) {
        if (idx == nums.size()) {
            ++retCnt;
            return;
        }
        for (int i = idx; i < nums.size(); ++i) {
            swap(nums[idx], nums[i]);
            if ((nums[idx] % idx == 0 || idx % nums[idx] == 0))//不能再要求i位置符合要求,因为到位置的时候可能会进行其他交换。
                backtrack(nums, idx+1, retCnt);
            swap(nums[idx], nums[i]);
        }
    }
};

然后从后往前找的话,会快很多,也不是太明白。。

class Solution {
public:
    int countArrangement(int N) {
        if (N == 0)
            return 0;
        vector<int> nums;
        for (int i = 0; i <= N; ++i)
            nums.push_back(i);
        int ret = 0;
        backtrack(nums, N, ret);
        return ret;
    }
private:
    void backtrack(vector<int>& nums, int index, int& ret) {
        if (index == 0) {
            ++ret;
            return;
        }
        for (int i = index; i > 0; --i) {
            swap(nums[index], nums[i]);
            if (nums[index] % index == 0 || index % nums[index] == 0)
                backtrack(nums, index-1, ret);
            swap(nums[index], nums[i]);
        }
    }
};
//为什么要快那么多?只是因为自己的方法每次都从头寻找吗?这里没有多余的寻找过程。但是是怎么做到的呢?
//给以后做题有什么启发?应该选择哪种方法?
//为什么从后往前又要快很多??

总的来说,最重要的还是一、二两种方法。

posted @ 2019-09-16 15:05  于老师的父亲王老爷子  阅读(18)  评论(0)    收藏  举报