A 第四课 递归_回溯_分治
内容概述及预备知识
预备知识:递归函数与回溯算法
#include <iostream> #include <vector> using namespace std; class ListNode{ public: int val; ListNode * next; ListNode(int x):val(x),next(NULL){ } }; // 将 链表中的 数据递归加入到 vector 中 void addToVec(ListNode* header,vector<int>& vec){ // 递归条件 if(header == NULL){ return; } vec.push_back(header->val); // 递归链条 addToVec(header->next,vec); } int main(){ ListNode a(1); ListNode b(2); ListNode c(3); ListNode d(4); ListNode e(5); a.next = &b; // 链接 链表 b.next = &c; c.next = &d; d.next = &e; vector<int> vec; addToVec(&a,vec); // 遍历 vec for(auto it=vec.cbegin();it!=vec.cend();it++){ cout << *it << " "; } cout << endl; return 0; }
回溯法:
回溯一般要用递归来实现,
例1-a:求子集(LeetCode No.78 )
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
思路及代码
1,循环,不容易往后退,
2, 如何生成 [1] ,[1,2] [1,2,3] ,
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> nums; nums.push_back(1); nums.push_back(2); nums.push_back(3); // 生成item :type is vector<int> int size = nums.size(); vector<int> item; vector<vector<int>> result; for(int i=0;i<size;i++){ item.push_back(nums[i]); result.push_back(item); } // 打印 result for(auto it= result.cbegin();it!=result.cend();it++){ for(auto it2 = (*it).cbegin();it2!=(*it).cend();it2++){ cout << *it2<<" "; } cout << endl; } return 0; } /* 1 1 2 1 2 3 */
#include <iostream> #include <vector> using namespace std; void generate(int idx,vector<int>& nums,vector<int>& item,vector<vector<int>>& result){ // 递归条件 if(idx > (int)nums.size() - 1){ return; } item.push_back(nums[idx]); result.push_back(item); // 递归链条 generate(idx+1,nums,item,result); return; } int main(){ vector<int> nums; nums.push_back(1); nums.push_back(2); nums.push_back(3); vector<int> item; vector<vector<int>> result; // 递归生成 result generate(0,nums,item,result); // 打印 result for(auto it= result.cbegin();it!=result.cend();it++){ for(auto it2 = (*it).cbegin();it2!=(*it).cend();it2++){ cout << *it2<<" "; } cout << endl; } return 0; } /* 1 1 2 1 2 3 */
算法思路:
#include <iostream> #include <vector> using namespace std; void generate(int idx,vector<int>nums,vector<int>& item,vector<vector<int>>& res){ // 递归条件 if(idx > (int)nums.size() -1){ return; } item.push_back(nums[idx]); res.push_back(item); // 递归链条 (第一次递归) generate(idx+1,nums,item,res); item.pop_back(); // 递归链条 (第二次递归) generate(idx+1,nums,item,res); } vector<vector<int>> subsets(vector<int>& nums) { vector<int> item; vector<vector<int>> res; res.push_back(item); // 将空集 push 进去 generate(0,nums,item,res); return res; } int main(){ vector<int> nums; nums.push_back(1); nums.push_back(2); nums.push_back(3); nums.push_back(4); vector<vector<int>> ret = subsets(nums); // 打印 ret for(auto it= ret.cbegin();it!=ret.cend();it++){ for(auto it2 = (*it).cbegin();it2!=(*it).cend();it2++){ cout << *it2<<" "; } cout << endl; } return 0; }
方法二:位运算法:
#include <iostream> #include <vector> using namespace std; vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int>> res; int size = nums.size(); int totalNum = 1 << size; // 1<< n 即 2^n for(int i=0;i<totalNum;i++){ vector<int> item; for(int j= 0;j<size;j++){ // 遍历nums中元素, // 使用元素对应的整数 和 i 比较 if(i & 1<<(size - j - 1)){ // [a,b,c] // a:100 b:100 c:001 item.push_back(nums[j]); } } res.push_back(item); } return res; } int main(){ vector<int> nums; nums.push_back(1); nums.push_back(2); nums.push_back(3); vector<vector<int>> ret = subsets(nums); // 打印 ret for(auto it= ret.cbegin();it!=ret.cend();it++){ for(auto it2 = (*it).cbegin();it2!=(*it).cend();it2++){ cout << *it2<<" "; } cout << endl; } return 0; }
例1-b:求子集2(LeetCode No.90)
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入 nums = [1,2,2]
按上面的算法结果是:
1
1 2
1 2 2
1 2
2
2 2
2
注意:结果中1,2 和 1,2 重复,
思考和代码:
#include <iostream> #include <set> #include <vector> #include <algorithm> using namespace std; void generate(int idx,vector<int>nums,vector<int>& item,vector<vector<int>>& res,set<vector<int>> & res_set){ // 递归条件 if(idx > (int)nums.size() -1){ return; } item.push_back(nums[idx]); if(res_set.find(item) == res_set.end()){ // 如果 在res_set 中 不能找到 item res.push_back(item); res_set.insert(item); } // 递归链条 (第一次递归) generate(idx+1,nums,item,res,res_set); item.pop_back(); // 递归链条 (第二次递归) generate(idx+1,nums,item,res,res_set); } vector<vector<int>> subsets(vector<int>& nums) { vector<int> item; vector<vector<int>> res; set<vector<int>> res_set; // 去重用的集合 sort(nums.begin(),nums.end()); // 排序 res.push_back(item); // 将空集 push 进去 generate(0,nums,item,res,res_set); return res; } int main(){ vector<int> nums; nums.push_back(1); nums.push_back(2); nums.push_back(2); vector<vector<int>> ret = subsets(nums); // 打印 ret for(auto it= ret.cbegin();it!=ret.cend();it++){ for(auto it2 = (*it).cbegin();it2!=(*it).cend();it2++){ cout << *it2<<" "; } cout << endl; } return 0; }
例1-c:组合数之和2(LeetCode No.40)
思路及代码
#include <iostream> #include <set> #include <vector> #include <algorithm> using namespace std; void generate(int idx,vector<int>nums,vector<int>& item,vector<vector<int>>& res,set<vector<int>> & res_set){ // 递归条件 if(idx > (int)nums.size() -1){ return; } item.push_back(nums[idx]); if(res_set.find(item) == res_set.end()){ // 如果 在res_set 中 不能找到 item res.push_back(item); res_set.insert(item); } // 递归链条 (第一次递归) generate(idx+1,nums,item,res,res_set); item.pop_back(); // 递归链条 (第二次递归) generate(idx+1,nums,item,res,res_set); } vector<vector<int>> subsets(vector<int>& nums,int target) { vector<int> item; vector<vector<int>> res; set<vector<int>> res_set; // 去重用的集合 sort(nums.begin(),nums.end()); // 排序 res.push_back(item); // 将空集 push 进去 generate(0,nums,item,res,res_set); vector<vector<int>> target_res; // 存储最终的结果 for(int i=0;i<(int)res.size();i++){ int sum = 0; for(int j =0;j<(int)res[i].size();j++){ sum += res[i][j]; } if(sum == target){ target_res.push_back(res[i]); } } return target_res; } int main(){ vector<int> nums; nums.push_back(1); nums.push_back(2); nums.push_back(2); vector<vector<int>> ret = subsets(nums); // 打印 ret for(auto it= ret.cbegin();it!=ret.cend();it++){ for(auto it2 = (*it).cbegin();it2!=(*it).cend();it2++){ cout << *it2<<" "; } cout << endl; } return 0; }
如何解决上面的问题,通过剪枝
#include <iostream> #include <vector> #include <set> #include <algorithm> using namespace std; void generate(int idx,vector<int>& nums,vector<int>& item,vector<vector<int>>& res,set<vector<int>>& res_set,int sum,int target){ int size = nums.size(); // base case if(idx > size -1 || sum > target){ return; } item.push_back(nums[idx]); sum += nums[idx]; if(sum == target && res_set.find(item) == res_set.cend()){ // 在res_set 中没找到 item res_set.insert(item); res.push_back(item); } // 递归链条(第一次) generate(idx+1,nums,item,res,res_set,sum,target); item.pop_back(); // 回溯 sum -= nums[idx]; // 递归链条(第一次) generate(idx+1,nums,item,res,res_set,sum,target); } vector<vector<int>> subsets(vector<int>& nums,int target){ sort(nums.begin(),nums.end()); vector<int> item; vector<vector<int>> res; set<vector<int>> res_set; generate(0,nums,item,res,res_set,0,target); return res; } int main(){ vector<int> nums; nums.push_back(1); nums.push_back(3); nums.push_back(2); vector<vector<int>> ret; ret = subsets(nums,4); // 打印 for(auto it1 = ret.cbegin();it1!=ret.cend();it1++){ for(auto it2 = (*it1).cbegin();it2!=(*it1).cend();it2++){ cout <<*it2<<" "; } cout << endl; } return 0; }
例2:生成括号(LeetCode No.22)
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
思路及代码
思考:
#include <iostream> #include <vector> #include <string> using namespace std; void generate(string item,int n,vector<string>& res){ // base case int size= item.size(); if(size == 2*n){ res.push_back(item); return; } // 递归链条 generate(item+"(",n,res); generate(item+")",n,res); } int main(){ vector<string> ret; generate("",2,ret); //2 组括号 for(auto it=ret.cbegin();it!=ret.cend();it++){ cout << *it << endl; } return 0; } /* output (((( ((() (()( (()) ()(( ()() ())( ())) )((( )(() )()( )()) ))(( ))() )))( )))) */
合法性:
#include <iostream> #include <vector> #include <string> using namespace std; void generate(string item,int n,vector<string>& res,int lfNum,int rtNum){ // lf rt为 左右括号的个数 // base case int size= item.size(); if(lfNum > n || rtNum >n){ // 肯定是不合法的 return; } if(size == 2*n){ res.push_back(item); return; } // 递归链条 generate(item+"(",n,res,lfNum+1,rtNum); if(lfNum > rtNum){ // 此时才能进行 右括号的添加 generate(item+")",n,res,lfNum,rtNum+1); } } int main(){ vector<string> ret; generate("",2,ret,0,0); //2 组括号 for(auto it=ret.cbegin();it!=ret.cend();it++){ cout << *it << endl; } return 0; }
class Solution { private: void generate(string item,int lfNum,int rtNum,vector<string>& res){ // lf rt为 当前还可以放置的数量 if(lfNum == 0 && rtNum == 0){ res.push_back(item); return; } if(lfNum >0){ generate(item+"(",lfNum-1,rtNum,res); } if(lfNum<rtNum){ generate(item+")",lfNum,rtNum-1,res); } } public: vector<string> generateParenthesis(int n) { vector<string> ret; generate("",n,n,ret); return ret; } };
例3:N皇后(LeetCode No.51)
思路及代码
皇后的攻击范围:
棋盘和皇后表示:
放完皇后如何更新棋盘?(通过方向数组解决)
#include <iostream> #include <vector> using namespace std; void put_down_the_queen(int x,int y,vector<vector<int>>& mark){ // 方向数组 static const int dx[] = {-1,1,0,0,-1,-1,1,1};// dx 和dy 要对应 static const int dy[] = {0,0,-1,1,-1,1,-1,1}; mark[x][y] = 1; int size = mark.size(); for(int i=1;i<size;i++){ for(int j=0;j<8;j++){ int newX = x+i*dx[j]; int newY = y+i*dy[j]; if(newX>=0&&newX<size && newY>=0&&newY<size){ mark[newX][newY] = 1; } } } } void myPrint(vector<vector<int>> mark){ for(int i=0;i<8;i++){ for(int j=0;j<8;j++){ cout <<mark[i][j]<< " "; } cout << endl; } } int main(){ vector<vector<int>> mark; // 生成 mark vector<int> temp; for(int i=0;i<8;i++){ for(int j=0;j<8;j++){ temp.push_back(0); } mark.push_back(temp); temp.clear(); } // 打印 mark myPrint(mark); cout << "======put the queen to the pos(3,2)====="<< endl; // 放置皇后, 更新 mark put_down_the_queen(3,2,mark); // 放在 4行 3列 // 打印 mark myPrint(mark); return 0; } /* output: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ======put the queen to the pos(3,2)===== 0 0 1 0 0 1 0 0 1 0 1 0 1 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 0 1 0 1 0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 1 0 */
算法思路:
用递归放皇后到每一行,然后回溯列(放在该列,完成后再尝试不放在该列),
#include <iostream> #include <vector> using namespace std; void put_down_the_queen(int x,int y,vector<vector<int>>& mark){ // 方向数组 static const int dx[] = {-1,1,0,0,-1,-1,1,1};// dx 和dy 要对应 static const int dy[] = {0,0,-1,1,-1,1,-1,1}; mark[x][y] = 1; int size = mark.size(); for(int i=1;i<size;i++){ for(int j=0;j<8;j++){ int newX = x+i*dx[j]; int newY = y+i*dy[j]; if(newX>=0&&newX<size && newY>=0&&newY<size){ mark[newX][newY] = 1; } } } } void generate(int idx,int n,vector<string>& item,vector<vector<string>>& res,vector<vector<int>>& mark){ // 放置idx 皇后 ,总共n个皇后 // base case if(idx > n-1){ res.push_back(item); return; } // 一一尝试 n 列 for(int i=0;i<n;i++){ if(mark[idx][i] == 0){ // 此时可以 放置皇后 vector<vector<int>> temp_mark = mark; // 备份 mark item[idx][i] = 'Q'; put_down_the_queen(idx,i,mark); // 递归链条 generate(idx+1,n,item,res,mark); // 递归下一行 皇后 // 回溯 mark = temp_mark; item[idx][i] = '.'; } } } vector<vector<string>> solveNQueens(int n){ vector<vector<string>>res; // 最终的结果 vector<vector<int>> mark; // 标记棋盘的 二维数组 vector<string> item; // 存储 某个结果 // 初始化 mark 和 item for(int i=0;i<n;i++){ mark.push_back(vector<int>()); for(int j=0;j<n;j++){ mark[i].push_back(0); // 初始 mark } // 初始化 item item.push_back(""); item[i].append(n,'.'); // item[0] = n个 点 } generate(0,n,item,res,mark); return res; } void myPrint(vector<vector<string>>& ret){ for(int i=0;i<(int)ret.size();i++){ for(int j=0;j<(int)ret[i].size();j++){ cout <<ret[i][j]<<endl;; } cout << "=========" << endl; } } int main(){ vector<vector<string>> ret = solveNQueens(4); myPrint(ret); return 0; } /* .Q.. ...Q Q... ..Q. ========= ..Q. Q... ...Q .Q.. ========= */
预备知识:分治算法与归并排序
代码实现:
#include <iostream> #include <vector> using namespace std; void mergeTwoSortedVec(vector<int>& vec1,vector<int>& vec2,vector<int>& res ){ int i = 0; int j = 0; int size1 = vec1.size(); int size2 = vec2.size(); while(i<size1 && j<size2){ if(vec1[i] <= vec2[j]){ // 相等的话,放到前一个数组 res.push_back(vec1[i]); i++; }else{ res.push_back(vec2[j]); j++; } } // 将 vec1 和 vec2 剩余的元素 push 进 res for(;i<size1;i++){ res.push_back(vec1[i]); } for(;j<size2;j++){ res.push_back(vec2[j]); } } void myPrint(vector<int> vec){ for(auto it = vec.cbegin();it != vec.cend();it++){ cout <<*it << " "; } cout << endl; } int main(){ vector<int> vec1 = {2,5,8,20}; vector<int> vec2 = {1,3,5,7,30,50}; vector<int> res; myPrint(vec1); myPrint(vec2); mergeTwoSortedVec(vec1,vec2,res); myPrint(res); return 0; }
分治算法 排序
分治算法的时间复杂度:
#include <iostream> #include <vector> using namespace std; void mergeTwoSortedVec(vector<int>& vec1,vector<int>& vec2,vector<int>& res ){ int i = 0; int j = 0; int size1 = vec1.size(); int size2 = vec2.size(); while(i<size1 && j<size2){ if(vec1[i] < vec2[j]){ res.push_back(vec1[i]); i++; }else{ res.push_back(vec2[j]); j++; } } // 将 vec1 和 vec2 剩余的元素 push 进 res for(;i<size1;i++){ res.push_back(vec1[i]); } for(;j<size2;j++){ res.push_back(vec2[j]); } } void mergeSort(vector<int>& vec){ // base case int size = vec.size(); if(size <= 1){ // 如果问题足够小 直接求解 return; } int mid = vec.size()/2; vector<int> sub_vec1; // 将原问题拆分为 两个 规模相同的问题 vector<int> sub_vec2; for(int i=0;i<mid;i++){ sub_vec1.push_back(vec[i]); } for(int i=mid;i<size;i++){ sub_vec2.push_back(vec[i]); } // 递归链条 mergeSort(sub_vec1); mergeSort(sub_vec2); vec.clear(); mergeTwoSortedVec(sub_vec1,sub_vec2,vec); // 合并 } void myPrint(vector<int> vec){ for(auto it = vec.cbegin();it != vec.cend();it++){ cout <<*it << " "; } cout << endl; } int main(){ vector<int> vec = {5,-7,9,1,1}; myPrint(vec); mergeSort(vec); myPrint(vec); return 0; }
例4:逆序数(LeetCode No.315)
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
思路及代码
使用分治来解决,
思考:
算法思路:
插入j对应的元素时,就是直接j 对应的值,
count 的顺序问题:
#include <iostream> #include <vector> using namespace std; void mergeTwoSortedVec(vector<pair<int,int>>& vec1,vector<pair<int,int>>& vec2,vector<pair<int,int>>& res,vector<int>& count ){ int i = 0; int j = 0; int size1 = vec1.size(); int size2 = vec2.size(); while(i<size1 && j<size2){ if(vec1[i].first <= vec2[j].first){ count[vec1[i].second] += j; res.push_back(vec1[i]); i++; }else{ res.push_back(vec2[j]); j++; } } // 将 vec1 和 vec2 剩余的元素 push 进 res for(;i<size1;i++){ count[vec1[i].second] += j; res.push_back(vec1[i]); } for(;j<size2;j++){ res.push_back(vec2[j]); } } void mergeSort(vector<pair<int,int>>& vec,vector<int>& count){ // base case int size = vec.size(); if(size <= 1){ // 如果问题足够小 直接求解 return; } int mid = vec.size()/2; vector<pair<int,int>> sub_vec1; // 将原问题拆分为 两个 规模相同的问题 vector<pair<int,int>> sub_vec2; for(int i=0;i<mid;i++){ sub_vec1.push_back(vec[i]); } for(int i=mid;i<size;i++){ sub_vec2.push_back(vec[i]); } // 递归链条 mergeSort(sub_vec1,count); mergeSort(sub_vec2,count); vec.clear(); mergeTwoSortedVec(sub_vec1,sub_vec2,vec,count); // 合并 } void myPrint(vector<int> vec){ for(auto it = vec.cbegin();it != vec.cend();it++){ cout <<*it << " "; } cout << endl; } vector<int> countSmaller(vector<int>& nums) { vector<pair<int,int>> vec; // 将 nums[i] 和 i 绑定到一起 放入vec vector<int> count; for(int i=0;i<(int)nums.size();i++){ vec.push_back(make_pair(nums[i],i)); count.push_back(0); // 初始为0 } mergeSort(vec,count); // 使用vec中pair 的第一个数进行排序 return count; } int main(){ vector<int> data = {5,-7,9,1,1}; countSmaller(data); return 0; }
class Solution { private: void mergeSort(vector<pair<int,int>>& vec,vector<int>& count){ // base case int size = vec.size(); if(size <= 1){ // 如果问题足够小 直接求解 return; } int mid = vec.size()/2; vector<pair<int,int>> sub_vec1; // 将原问题拆分为 两个 规模相同的问题 vector<pair<int,int>> sub_vec2; for(int i=0;i<mid;i++){ sub_vec1.push_back(vec[i]); } for(int i=mid;i<size;i++){ sub_vec2.push_back(vec[i]); } // 递归链条 mergeSort(sub_vec1,count); mergeSort(sub_vec2,count); vec.clear(); mergeTwoSortedVec(sub_vec1,sub_vec2,vec,count); // 合并 } void mergeTwoSortedVec(vector<pair<int,int>>& vec1,vector<pair<int,int>>& vec2,vector<pair<int,int>>& res,vector<int>& count ){ int i = 0; int j = 0; int size1 = vec1.size(); int size2 = vec2.size(); while(i<size1 && j<size2){ if(vec1[i].first <= vec2[j].first){ count[vec1[i].second] += j; res.push_back(vec1[i]); i++; }else{ res.push_back(vec2[j]); j++; } } // 将 vec1 和 vec2 剩余的元素 push 进 res for(;i<size1;i++){ count[vec1[i].second] += j; res.push_back(vec1[i]); } for(;j<size2;j++){ res.push_back(vec2[j]); } } public: vector<int> countSmaller(vector<int>& nums) { vector<pair<int,int>> vec; // 将 nums[i] 和 i 绑定到一起 放入vec vector<int> count; for(int i=0;i<(int)nums.size();i++){ vec.push_back(make_pair(nums[i],i)); count.push_back(0); // 初始为0 } mergeSort(vec,count); // 使用vec中pair 的第一个数进行排序 return count; } };