0 全排列算法
0.1 全排列实现
- 1 一个排列,其优先交换靠右的位置的数字来获得下一个排列,比如1 2 3,他下一个必定是交换2 3,1不会参与其中,
- 2 意识到一个排列,左侧属于高位,打个比方,若全排列对应的都有一个数字表示该排列的大小,那么左侧值越大,那么排列越大
# ref: https://blog.51cto.com/u_4925054/884291
全排列(非递归求顺序)算法
1、建立位置数组,即对位置进行排列,排列成功后转换为元素的排列;
2、按如下算法求全排列:
设P是1~n(位置编号)的一个全排列:p = p1,p2...pn = p1,p2...pj-1,pj,pj+1...pk-1,pk,pk+1...pn
(1)从排列的尾部开始,找出第一个比右边位置编号小的索引j(j从首部开始计算),即j = max{i | pi < pi+1}
(2)在pj的右边的位置编号中,找出最后一个比pj大的位置编号索引k,即 k = max{i | pi > pj} (k > j)
(3)交换pj与pk
(4)再将pj+1...pk-1,pk,pk+1...pn翻转得到排列p' = p1,p2...pj-1,pj,pn...pk+1,pk,pk-1...pj+1
(5)p'便是排列p的下一个排列
例如:
24310是位置编号0~4的一个排列,求它下一个排列的步骤如下:
(1)从右至左找出排列中第一个比右边数字小的数字2;
(2)在该数字后的数字中找出比2大的数中编号最大的3;
(3)将2与3交换得到34210;
(4)将原来2(当前3)后面的所有数字翻转,即翻转4210,得30124;
(5)求得24310的下一个排列为30124。
这里给出官方的可能实现:
5 6 1 2 3 4的下一个排列是: 6 1 2 3 4 5
0.2 迭代实现
// 比如求 6 5 4 2 3 1的下一个排列
template<class BidirIt>
bool next_permutation(BidirIt first, BidirIt last)
{
if (first == last) return false;
BidirIt i = last;
if (first == --i) return false;
while (true) {
BidirIt i1, i2;
i1 = i;
if (*--i < *i1) { // i = prev(i1), 找到了一个 i < i1的(2 < 3)相邻对子
i2 = last;
while (!(*i < *--i2)) // 从i的右边找最后一个比i大的数字
;
std::iter_swap(i, i2); // 交换 i和i2 (2,3),变成6 5 4 3 2 1
std::reverse(i1, last); // 从i1(原始数据中的3处)到最后,得到 6 5 4 3 1 2
return true;
}
if (i == first) { // 若果找不到两个相邻的数,使得右边<左边,则认为是最大排列
std::reverse(first, last);
return false;
}
}
}
- 2 回溯
void chooseN3(vector<char>& dq, int n, priority_queue<string, vector<string>, std::function<bool(string, string)>>& res, string& tmpRes) {
if(n == 0) {
res.push(tmpRes);
// std::cout << "-d2 " << tmpRes << " with n = " << tmpRes.size() <<std::endl;
return;
}
for(int i = 0; i < dq.size(); ++i) {
tmpRes.push_back(dq[i]);
vector<char> tmpDq;
tmpDq.insert(tmpDq.end(), dq.begin(), dq.begin() + i);
tmpDq.insert(tmpDq.end(), dq.begin() + i + 1, dq.end());
chooseN3(tmpDq, n - 1, res, tmpRes);
tmpRes.pop_back();
}
}
2014 重复 K 次的最长子序列
1 题目
https://leetcode-cn.com/problems/largest-color-value-in-a-directed-graph/
2 解题思路
- 1 自己的思路: 首先查出来哪些字符有k个,压入dq中,若一个字符有2k个,则压入两次(代表最终结果里可以有两个该字符),而后将其逆序排序
- 2 那么最后的结果一定是从dq中来,我们可以枚举dq的所有子序列即可(枚举子序列需要按照逆序字典序,这样找到的第一个子序列即为我们的目标)
- 关键: 如何获得逆序子序列呢?
- 1.1 最简单的想法,从dq(已经是最大的子序列了)中选择长度为n(7到1)的子序列,获取每个子序列(由于父序列为最大序列,那么子序列也一定是最大序列)所有的全排列
- 1.2 但是这样做有问题,比如 有fd,fe两个子序列,那么优先对于fe去看其所有的排列是否满足要求,然而若是ef满足要求,那么其检测顺序在fd前面,
- 这样就没有满足按照逆序字典序的方法去检测,于是对于长度为n的子序列,需要先生成所有的全排列,然后按照字典序排序,
最后中实现总代码:
class Solution {
public:
string longestSubsequenceRepeatedK(string s, int k) {
// 找出大于等于k的字符记作kSet
int n = s.size();
vector<int> cnt(26, 0);
vector<char> dq;
for(int i = 0; i < n; ++i) {
int charNo = s[i]-'a';
++cnt[charNo];
if(cnt[charNo] % k == 0) {
dq.push_back(s[i]);
}
}
// 将kSet按照逆序字典序排序
auto cmp = [](char a, char b) {return a > b;};
sort(dq.begin(), dq.end(), cmp);
// string tmp = "";
// for(char& c : dq) tmp += c;
// std::cout << "-d1 " << tmp << std::endl;
// 找到第一个满足的最大长度为dq的字典序逆序的子序列的排列即为结果
// 从子模式串的最大长度dq.size()一直遍历到1
for(int len = dq.size(); len >= 1; --len) {
// vector<char> tmp;
// vector<vector<char>> res;
// chooseN(dq, len, 0, res, tmp);
// string tmp;
// vector<string> res;
// chooseN2(dq, len, 0, res, tmp);
// // 构建出同len的所有permutaion,然后sort然后再来以此比较
// vector<string> sameLenStr;
// for(int i = 0; i < res.size(); ++i) {
// do {
// // string tmp4 = "";
// // for(char& c : res[i]) tmp4 += c;
// // std::cout << "-d4 " << tmp4 << " with n = " << len <<std::endl;
// sameLenStr.push_back(res[i]);
// }while(prev_permutation(res[i].begin(), res[i].end()));
// }
// sort(sameLenStr.begin(), sameLenStr.end(), [](string a, string b){return a > b;});
// for(int subIdx = 0; subIdx < sameLenStr.size(); ++subIdx) {
// // do {
// // 构造seq*k
// string tmpDq;
// for(int i = 0; i < k; ++i) {
// tmpDq += sameLenStr[subIdx];
// }
//
// // string tmp3 = "";
// // for(char& c : tmpDq) tmp3 += c;
// // std::cout << "-d3 " << tmp3 << " with n = " << tmpDq.size() <<std::endl;
//
// // 判断构造的seq*k是否存在于s中
// int tmpIdx = 0;
// int realIdx = 0;
// int cnt = 0;
// for(; tmpIdx < tmpDq.size(); ++tmpIdx) {
// for(; realIdx < n; ++realIdx) {
// if(s[realIdx] == tmpDq[tmpIdx]) {
// ++cnt;
// ++realIdx;
// break;
// }
// }
// }
//
//
// if(cnt == tmpDq.size()) {
// return sameLenStr[subIdx];
// }
//
// }
string tmp;
std::function<bool(string, string)> cmp = [](string a, string b)->bool{return a < b;};
priority_queue<string, vector<string>, std::function<bool(string, string)>> sameLenStr(cmp);
chooseN3(dq, len, sameLenStr, tmp);
// 看该子序列是否可以
while(!sameLenStr.empty()) {
string curStr = sameLenStr.top();
sameLenStr.pop();
string tmpDq;
for(int i = 0; i < k; ++i) {
tmpDq += curStr;
}
// string tmp3 = "";
// for(char& c : tmpDq) tmp3 += c;
// if(curStr.size() <= 10)std::cout << "-d3 " << curStr << " with n = " << tmpDq.size() <<std::endl;
// 判断构造的seq*k是否存在于s中
int tmpIdx = 0;
int realIdx = 0;
int cnt = 0;
for(; tmpIdx < tmpDq.size(); ++tmpIdx) {
for(; realIdx < n; ++realIdx) {
if(s[realIdx] == tmpDq[tmpIdx]) {
++cnt;
++realIdx;
break;
}
}
}
if(cnt == tmpDq.size()) {
return curStr;
}
}
}
return "";
}
void chooseN2(vector<char>& dq, int n, int st, vector<string>& res, string& tmpRes) {
if(n == 0) {
res.push_back(tmpRes);
// std::cout << "-d2 " << tmpRes << " with n = " << tmpRes.size() <<std::endl;
return;
}
for(int i = st; i < dq.size() - n + 1; ++i) {
tmpRes.push_back(dq[i]);
chooseN2(dq, n - 1, i + 1, res, tmpRes);
tmpRes.pop_back();
}
}
void chooseN3(vector<char>& dq, int n, priority_queue<string, vector<string>, std::function<bool(string, string)>>& res, string& tmpRes) {
if(n == 0) {
res.push(tmpRes);
// std::cout << "-d2 " << tmpRes << " with n = " << tmpRes.size() <<std::endl;
return;
}
for(int i = 0; i < dq.size(); ++i) {
tmpRes.push_back(dq[i]);
vector<char> tmpDq;
tmpDq.insert(tmpDq.end(), dq.begin(), dq.begin() + i);
tmpDq.insert(tmpDq.end(), dq.begin() + i + 1, dq.end());
chooseN3(tmpDq, n - 1, res, tmpRes);
tmpRes.pop_back();
}
}
};
0047 全排列去重
1 题目
https://leetcode-cn.com/problems/permutations-ii/
2 解题思路
class Solution {
public:
unordered_set<string> hash;
vector<vector<int>> permuteUnique(vector<int>& nums) {
// vector<vector<int>> res;
// vector<int> tmp;
// string tmpStr = "";
// chooseN(nums, nums.size(), res, tmp, tmpStr);
// vector<vector<int>> res;
// vector<int> tmp;
// string tmpStr = "";
// chooseN2(nums, 0, nums.size(), res, tmpStr);
vector<vector<int>> res;
vector<int> tmp;
vector<bool> vis(nums.size(), false);
sort(nums.begin(), nums.end());
chooseN3(nums, 0, nums.size(), res, vis, tmp);
return res;
}
// 160ms
void chooseN(vector<int>& nums, int n, vector<vector<int>>& res, vector<int>& tmp, string tmpStr) {
if(n == 0) {
if(hash.count(tmpStr) == 0) {
std::cout << tmpStr << std::endl;
hash.insert(tmpStr);
res.emplace_back(tmp);
}
}
for(int i = 0; i < nums.size(); ++i) {
vector<int> tmpNums;
tmpNums.insert(tmpNums.end(), nums.begin(), nums.begin() + i);
tmpNums.insert(tmpNums.end(), nums.begin() + i + 1, nums.end());
tmp.emplace_back(nums[i]);
tmpStr.push_back(static_cast<char>(nums[i] + 107));
chooseN(tmpNums, n-1, res, tmp, tmpStr);
tmpStr.pop_back();
tmp.pop_back();
}
}
// chooseN由于每次构造新的串子,这样降低了速度,只用swap到最左边就行了
// 32ms
void chooseN2(vector<int>& nums, int st, int n, vector<vector<int>>& res, string tmpStr) {
if(st == nums.size()) {
if(hash.count(tmpStr) == 0) {
std::cout << tmpStr << std::endl;
hash.insert(tmpStr);
res.emplace_back(nums);
}
}
for(int i = st; i < nums.size(); ++i) {
vector<int> tmpNums;
tmpStr.push_back(static_cast<char>(nums[i] + 107));
swap(nums[st], nums[i]);
chooseN2(nums, st + 1, nums.size(), res, tmpStr);
swap(nums[st], nums[i]);
tmpStr.pop_back();
}
}
// 由于使用string来判断还是降低了速度,于是在搜索过程中判断是否能够压入
void chooseN3(vector<int>& nums, int st, int n, vector<vector<int>>& res, vector<bool>& vis, vector<int>& tmp) {
if(st == nums.size()) {
res.emplace_back(tmp);
}
for(int i = 0; i < nums.size(); ++i) {
// 如果当前数作为第st层的回溯已经用过了,则不再使用 || 若当前数字和st层上一次使用数字相同,则也不再使用
if(vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
continue;
}
// if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
// continue;
// }
vector<int> tmpNums;
tmp.emplace_back(nums[i]);
vis[i] = 1;
chooseN3(nums, st + 1, nums.size(), res, vis, tmp);
vis[i] = 0;
tmp.pop_back();
// swap(nums[st], nums[i]);
}
}
};