代码随想录刷题03.10
回溯算法5
LeetCode题目
解题思路
1.去重的两种主要思路:
1)值查找:
a)使用unordered_setused;
b)每使用一个数,则将该元素存入容器中used.insert(nums[i]);
c)当used.find(nums[i])!=used.end(),说明改值在当前for循环层已被使用过;
2)值标记:
a)使用数组或动态数组(标记好大小,以方便查找)vectorjudge(201,false)或者int used[201]={0};
b)每使用一个数,则将对应位置上的值改为true或1:judge[nums[i]+100]=true或者int[nums[i]+100]=1;
c)当快速查找定位到该位置时,若对应的值为true或1,则说明改值在当前循环中已被使用过judge[nums[i]+100]==true或者int[nums[i]+100]==1;
**注意:**无论是使用unordered_set还是数组,都将容器定义在递归函数内,且容器不向下传递,这样在每一层内,容器都是新的,也就直接避免了树枝去重的误操作。
2.在搜索中去掉部分不要的节点时(比如去重),就在for循环下进行控制:当使用break时,表示跳出当前小层的for循环,当使用continue时,表示在当前for循环层进入下一个元素。
3.记好回溯遍历中N叉树的数据流过程。
代码过程
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
void backtracking(vector<int>& nums,int index){
if(path.size()>1)result.push_back(path);
if(index==nums.size())return;
unordered_set<int>used;
//vector<bool>judge(201,false);
for(int i=index;i<nums.size();i++)
{
if((!path.empty()&&nums[i]<path.back())||used.find(nums[i])!=used.end())continue;
path.push_back(nums[i]);
//judge[nums[i]+100]=true;
used.insert(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
return;
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
int index=0;
backtracking(nums,index);
return result;
}
};
LeetCode题目
解题思路
1.在回溯N叉树图的数据流向中,同一层的数据传递是既向下递归也会借助for循环(同一层数据公用)传递到同层的其他节点内!!!因此,要及时回溯!!!
2.本题依然可以使用上节中所总结的去重的两种方式来做;
然而,本题有一个独特的操作地方:针对元素自身重复取值的去重操作。使用used容器来记录哪个位置(本题也可以使用和上一节一样的利用值来记录,因为题目说了给定的元素里面不会重复,但最好采用元素在数组中的位置来进行标记!)的元素在上一层已经被取过了!
利用位置来标记去重:
a)定义一个数组used,数组大小为题目给定数组内元素数量个数;
b)(在回溯的情况下)若使用了某个位置上的元素,则标记该位置的数组的值为true:used[i]=true;
c)根据回溯与递归关系,上一层的used数组内容会被传递到下一层,在下一层从头开始遍历时,若发现used[i]==true,说明这个i位置处的元素在上层已被用过,本层不因使用,直接跳过continue即可。
3.分析数据流向图时,从节点的尾状态出发分析每一节点的数据状态;
分析N叉树图来思考解题策略时,从节点的首状态出发分析每一节点的数据状态。
N叉树图的预期应和代码写好后数据流向图分析相一致!!!
代码过程
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
vector<bool>used=vector<bool>(21,false);
void backtracking(vector<int>& nums){
if(path.size()==nums.size()){
result.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)
{
if(used[nums[i]+10]==true)continue;
path.push_back(nums[i]);
used[nums[i]+10]=true;
backtracking(nums);
path.pop_back();
used[nums[i]+10]=false;
}
return;
}
vector<vector<int>> permute(vector<int>& nums) {
backtracking(nums);
return result;
}
};
LeetCode题目
解题思路
1.去重操作总结:
1)树层去重(横向)与树枝去重(纵向):
A.数层去重:节点内定义标记数组;
B.树枝去重:定义全局的标记数组,并做好回溯;
C.两个方向任意去重:将题目给定数组重排序+若前后选择的元素相等则跳过(这种方案需要借助外部的纵向的标记全局数组来确立到底横向(树层)去重还是纵向(继承节点向下,即树枝)去重。本质上其实还是横向的值去重,但是结合纵向标记数组后,可以表现出使沿横向还是沿纵向去重。
2)自身去重(位置去重)与值去重:
A.自身去重:指元素自身不应该重复使用——可以使用和题目给定数组元素数量等大的used(全局回溯)数组,用used[i]来标记位置处元素的使用信息;
B.值去重:指相同数值的元素应进行去重——可以使用unordered_set容器进行值查找或使用数组进行真假判断;
3)值查找判断与真假判断:针对值去重的情况进一步细分
A.值查找判断:unordered_set容器,使用set.find(nums[i])!=set.end()来判断;
B.真假判断:(动态)数组容器,固定数组容器大小,利用judge(nums[i])==true来判断。
2.本题思路:综合树层去重与树枝去重,树层去重需使用值去重,树枝去重需使用自身去重。值去重中进一步选择值查找方式。
代码过程
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
vector<bool>used=vector<bool>(8,false);
void backtracking(vector<int>& nums){
unordered_set<int>judge;
if(path.size()==nums.size()){
result.push_back(path);
return;
}
for(int i=0;i<nums.size();i++){
if(judge.find(nums[i])!=judge.end()||used[i]==true)continue;
path.push_back(nums[i]);
judge.insert(nums[i]);
used[i]=true;
backtracking(nums);
path.pop_back();
used[i]=false;
}
return;
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
backtracking(nums);
return result;
}
};