Leetcode学习之递归、回溯与分治(1)

开宗明义:本系列基于小象学院林沐老师课程《面试算法 LeetCode 刷题班》,刷题小白,旨在理解和交流,重在记录,望各位大牛指点!


Leetcode学习之递归、回溯与分治(1)


文章目录

    • 1、递归函数与回溯算法
      • 1.1 什么是递归函数?
      • 1.2 什么是回溯法?
    • 2、求子集1(无重复元素)(回溯法、位运算法)Leetcode 78.
      • 2.1 回溯法
        • 2.1.1 思路:循环的思想产生固定的三个 subsets=[[1],[1,2],[1,2,3]]。
        • 2.1.2 思路:递归的思想产生固定的三个 subsets=[[1],[1,2],[1,2,3]]。
        • 2.1.3 思路:回溯法求所有子集
      • 2.2 位运算法
    • 3、求子集2(有重复元素)(回溯法)Leetcode 90.
    • 4、组合数之和2(回溯法、剪枝)Leetcode 40.


1、递归函数与回溯算法

1.1 什么是递归函数?

递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件。 当函数在一直递推,直到遇到结束条件后返回。
递归两要素:①结束条件;②递推关系

测试代码

#include 
#include 

void compute_sum_3(int i, int &sum) {//i=3,将sum=sum+3
	sum += i;
}

void compute_sum_2(int i, int &sum) {//i=2,将sum=sum+2,调用compute_sum_3
	sum += i;
	compute_sum_3(i + 1, sum);
}

void compute_sum_1(int i, int &sum) {//i=1,将sum=sum+1,调用compute_sum_2
	sum += i;
	compute_sum_2(i + 1, sum);
}

int main() {
	int sum = 0;
	compute_sum_1(1, sum);  //计算1+2+3
	printf("sum = %d\n", sum);//将结果存储至sum,并打印sum
	system("pause");
	return 0;
}

上述代码存在冗余,我们可以写成递归形式

#include 
#include 

void compute_sum(int i, int &sum) {
	if (i > 3) {
		return;//递归结束条件,i>3
	}
	sum += i;//sum累加i
	compute_sum(i + 1, sum);//递归调用,下一次调用会累加i+1
}

int main() {
	int sum = 0;
	compute_sum(1, sum);
	printf("sum = %d\n", sum);
	system("pause");
	return 0;
}

再来个链表递归例子:

#include 
#include 
#include 

using namespace std;

struct ListNode {  //链表的数据结构
	int val;
	ListNode *next;
	ListNode(int x):val(x),next(NULL){}
};

void add_to_vector(ListNode *head, vector<int> &vec) {
	if (!head) {//如果head为空则结束递归
		return;
	}
	vec.push_back(head->val);//将当前遍历的节点值push进入vec
	add_to_vector(head->next, vec);//继续递归后续链表
}

int main() {
	ListNode a(1), b(2), c(3), d(4), e(5);
	a.next = &b;
	b.next = &c;
	c.next = &d;
	d.next = &e;
	vector<int> vec;
	add_to_vector(&a, vec);
	for (int i = 0; i < vec.size(); i++) {
		printf("[%d]", vec[i]);
	}
	system("pause");
	return 0;
}

效果图
Leetcode学习之递归、回溯与分治(1)_第1张图片


1.2 什么是回溯法?

回溯法又称为试探法,但当探索到某一步时,发现原先选择达不到目标,就退回一步重新选择,这种走不通就退回再走的方法称为回溯法。
Leetcode学习之递归、回溯与分治(1)_第2张图片


2、求子集1(无重复元素)(回溯法、位运算法)Leetcode 78.

题目来源: L e e t c o d e   78.   S u b s e t s Leetcode \ 78. \ Subsets Leetcode 78. Subsets
题目描述已知一组数(其中无重复元素),求这组数可以组成的所有子集,结果中不可有重复的子集
要求描述
Leetcode学习之递归、回溯与分治(1)_第3张图片


2.1 回溯法

分析
Leetcode学习之递归、回溯与分治(1)_第4张图片

2.1.1 思路:循环的思想产生固定的三个 subsets=[[1],[1,2],[1,2,3]]。

测试代码:

#include //nums[]=[1,2,3],只打印这三个subsets=[[1],[1,2],[1,2,3]]
#include 
using namespace std;

int main() {
	vector<int> nums;
	nums.push_back(1);
	nums.push_back(2);
	nums.push_back(3);//总数组

	vector<int> item;//生成各个子集的数组
	vector<vector<int>> result;//subsets最终形态

	for (int i = 0; i < nums.size(); i++) {//当i=0时,item=[1]
		item.push_back(nums[i]);//当i=1时,item=[1,2]
		result.push_back(item);//当i=2时,item=[1,2,3]
	}

	for (int i = 0; i < result.size(); i++) {
		for (int j = 0; j < result[i].size(); j++) {
			printf("[%d]", result[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

效果图
Leetcode学习之递归、回溯与分治(1)_第5张图片


2.1.2 思路:递归的思想产生固定的三个 subsets=[[1],[1,2],[1,2,3]]。

测试代码:

#include  
#include //nums[]=[1,2,3],将子集[[1],[1,2],[1,2,3]]递归的加入result,每次递归的将下一个nums元素放入生成子集的数组item,产生对应的子集
using namespace std;

void generate(int i, vector<int> &nums, vector<int> &item,//输入的数,生成子集的数组
	vector<vector<int>> &result) {//最终结果数组
	if (i >= nums.size()){//递归结束条件
		return;
	}
	item.push_back(nums[i]);
	result.push_back(item);
	generate(i + 1, nums, item,result);
}

int main() {
	vector<int> nums;
	nums.push_back(1);
	nums.push_back(2);
	nums.push_back(3);
	vector<int> item;
	vector<vector<int>> result;
	generate(0, nums, item, result);
	
	for (int i = 0; i < result.size(); i++) {
		for (int j = 0; j < result[i].size(); j++) {
			printf("[%d]", result[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

效果图
Leetcode学习之递归、回溯与分治(1)_第6张图片


2.1.3 思路:回溯法求所有子集

分析
Leetcode学习之递归、回溯与分治(1)_第7张图片
测试代码:

#include 
using namespace std;

class Solution {
public:
	vector<vector<int>> subsets(vector<int> &nums) {//输入一个vector数组,返回是一个vector的vector
		vector<vector<int>> result;//存储最终结果的result
		vector<int> item;//回溯时,产生各个子集的数组
		result.push_back(item);//将空集push进去
		generate(0, nums,item,result);//计算各个子集,开头,里面再分两个
		return result;
	}
private:
	// i,输入的数组,参生各个子集的数组,最终结果的数组
	void generate(int i, vector<int> &nums, vector<int> &item, vector<vector<int>> &result) {
		if (i >= nums.size()) {//递归结束的条件
			return;
		}
		item.push_back(nums[i]);
		result.push_back(item);//将当前生成的子集添加到result中
		generate(i + 1, nums, item, result);//第一次递归调用
		item.pop_back();
		generate(i + 1, nums, item, result);//第二次递归调用
	}
};

int main() {
	vector<int> nums;
	nums.push_back(1);
	nums.push_back(2);
	nums.push_back(3);
	vector<vector<int>> result;
	Solution solve;
	result = solve.subsets(nums);//调用
	for (int i = 0; i < result.size(); i++) {
		if (result[i].size() == 0) {
			printf("[]");
		}
		for (int j = 0; j < result[i].size(); j++) {
			printf("[%d]", result[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

效果图
Leetcode学习之递归、回溯与分治(1)_第8张图片


2.2 位运算法

分析
Leetcode学习之递归、回溯与分治(1)_第9张图片
Leetcode学习之递归、回溯与分治(1)_第10张图片思路
Leetcode学习之递归、回溯与分治(1)_第11张图片
测试代码:

#include 
using namespace std;

class Solution {
public:
	vector<vector<int>> subsets(vector<int> &nums) {//输入一个vector数组,返回是一个vector的vector
		vector<vector<int>> result;//存储最终结果的result
		int all_set = 1 << nums.size();//设置全部集合的最大值加1,这边也就是1<<3,也就是0001->1000,变成1<

		for (int i = 0; i < all_set; i++) {//遍历所有集合
			vector<int> item;
			for (int j = 0; j < nums.size(); j++) {//3位,如nums=[1,2,3]
				if (i&(1 << j)) {//这边的i就代表集合(A,B,C)对应的所有整数,而(1<
					item.push_back(nums[j]);//上述条件为真就放入构造的子集
				}
			}
			result.push_back(item);
		}
		return result;
	}
};

int main() {
	vector<int> nums;
	nums.push_back(1);
	nums.push_back(2);
	nums.push_back(3);
	vector<vector<int>> result;
	Solution solve;
	result = solve.subsets(nums);//调用
	for (int i = 0; i < result.size(); i++) {
		if (result[i].size() == 0) {
			printf("[]");
		}
		for (int j = 0; j < result[i].size(); j++) {
			printf("[%d]", result[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

效果图
Leetcode学习之递归、回溯与分治(1)_第12张图片


3、求子集2(有重复元素)(回溯法)Leetcode 90.

题目来源: L e e t c o d e   90.   S u b s e t s   I I Leetcode \ 90. \ Subsets \ II Leetcode 90. Subsets II
题目描述已知一组数(其中有重复元素),求这种数可以组成的所有子集,结果中无重复的子集。
要求描述
Leetcode学习之递归、回溯与分治(1)_第13张图片
分析
Leetcode学习之递归、回溯与分治(1)_第14张图片
思路
Leetcode学习之递归、回溯与分治(1)_第15张图片
测试代码:

#include 
#include 
#include 
using namespace std;

class Solution {
public:
	vector<vector<int>> subsetWithDup(vector<int> &nums){
		vector<vector<int>> result;//储存最终结果
		vector<int> item;//子集
		set<vector<int>> res_set;//去重使用的集合set
		sort(nums.begin(), nums.end());//对nums数组进行排序
		result.push_back(item);//赋空值
		generate(0, nums, result, item, res_set);
		return result;
	}
private:
	void generate(int i, vector<int>&nums, vector<vector<int>>&result, vector<int> &item, set<vector<int>>res_set) {
		if (i >= nums.size()) {//递归结束条件
			return;
		}
		item.push_back(nums[i]);//放入

		if (res_set.find(item) == res_set.end()) {  //如果res_set集合中找不到item
			result.push_back(item);//将item放入result里面
			res_set.insert(item);//将item放入去重集合res_set中
			generate(i + 1, nums, result, item, res_set);
		}
		item.pop_back();//弹出,回溯
		generate(i + 1, nums, result, item, res_set);
	}
};

int main() {
	vector<int> nums;
	nums.push_back(2);
	nums.push_back(1);
	nums.push_back(2);
	nums.push_back(2);
	vector<vector<int>> result;//存储最后结果
	Solution solve;
	result = solve.subsetWithDup(nums);
	for (int i = 0; i < result.size(); i++) {
		if (result[i].size() == 0) {
			printf("[]");
		}
		for (int j = 0; j < result[i].size(); j++) {
			printf("[%d]", result[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

效果图
Leetcode学习之递归、回溯与分治(1)_第16张图片


4、组合数之和2(回溯法、剪枝)Leetcode 40.

题目来源: L e e t c o d e   40.   C o m b i n a t i o n   S u m   I I Leetcode \ 40. \ Combination \ Sum \ II Leetcode 40. Combination Sum II
题目描述已知一组数(其中有重复元素),求这组数可以组成的所有子集中,子集中的各个元素和为整数target的子集,子集需要无重复
要求描述
Leetcode学习之递归、回溯与分治(1)_第17张图片
分析明显上面程序里面加个 if 判断语句,也可以完成。但是时间复杂度太高了
Leetcode学习之递归、回溯与分治(1)_第18张图片
Leetcode学习之递归、回溯与分治(1)_第19张图片
思路
Leetcode学习之递归、回溯与分治(1)_第20张图片
测试代码:

#include 
#include 
#include 
using namespace std;

class  Solution {
public:
	vector<vector<int>> combintionSum2(vector<int> &candidatas, int target) {
		vector<vector<int>> result;
		vector<int> item;
		set<vector<int>> res_set;
		sort(candidatas.begin(), candidatas.end());//排序
		generate(0, candidatas, result, item, res_set, 0, target);
		return result;
	}
private:
	void generate(int i, vector<int> &nums, vector<vector<int>> &result, vector<int> &item, 
		set<vector<int>>&res_set, int sum, int target) {
		//递归结束的条件
		if (i >= nums.size() || sum > target) {
			return;//当元素已经选完或者item中的元素和sum已超过target
		}
		sum += nums[i];//求和
		item.push_back(nums[i]);//push进入
		//当item的元素的和为target且该结果未添加
		if (target == sum && res_set.find(item) == res_set.end()) {//添加符合情况的
			result.push_back(item);
			res_set.insert(item);
		}
		generate(i + 1, nums, result, item, res_set, sum, target);
		sum = sum - nums[i];
		item.pop_back();
		generate(i + 1, nums, result, item, res_set, sum, target);
	}
};

int main() {
	vector<int> nums;
	nums.push_back(10);
	nums.push_back(1);
	nums.push_back(2);
	nums.push_back(7);
	nums.push_back(6);
	nums.push_back(1);
	nums.push_back(5);
	vector<vector<int>>  result;

	Solution solve;
	result = solve.combintionSum2(nums,8);
	for (int i = 0; i < result.size(); i++) {
		if (result[i].size() == 0) {
			printf("[]");
		}
		for (int j = 0; j < result[i].size(); j++) {
			printf("[%d]", result[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

效果图
Leetcode学习之递归、回溯与分治(1)_第21张图片

你可能感兴趣的:(Leetcode)