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]);
}
}
};
//为什么要快那么多?只是因为自己的方法每次都从头寻找吗?这里没有多余的寻找过程。但是是怎么做到的呢?
//给以后做题有什么启发?应该选择哪种方法?
//为什么从后往前又要快很多??
总的来说,最重要的还是一、二两种方法。
浙公网安备 33010602011771号