八、 回溯算法学习1(代码随想录学习)

1. 理论基础

代码随想录:回溯算法理论基础

  • 什么是回溯算法
    回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯是递归的副产品,只要有递归就会有回溯。

  • 回溯法的效率
    回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,回溯法并不是什么高效的算法。

  • 回溯法解决的问题
    组合问题:N个数里面按一定规则找出k个数的集合
    切割问题:一个字符串按一定规则有几种切割方式
    子集问题:一个N个数的集合里有多少符合条件的子集
    排列问题:N个数按一定规则全排列,有几种排列方式
    棋盘问题:N皇后,解数独等等

  • 如何理解回溯法
    回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。

  • 回溯法模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

2. 组合问题

leetcode链接

思路: 使用回溯法,记录当前已记录过的添加进path的组合。使用for循环+递归,遍历剩余的可能添加的组合。传入一个索引,for循环从当前开始。

class Solution {
private:
	vector<vector<int>> res; // 保存最后的结果
	vector<int> path; // 保存单一的结果

public:
	// 回溯法,startIndex记录开始的下标(从1开始),保证不重复
	void backtracing(int n ,int k ,int startIndex) { 
		// 终止条件
		if (path.size() == k) {
			res.push_back(path);
			return;
		}

		// 横向遍历,前面不变,当前位置每次变化,回溯
		for (int i = startIndex; i <= n; i++) {
			path.push_back(i);
			backtracing(n, k, i + 1);
			path.pop_back();
		}
		return;

	}

	vector<vector<int>> combine(int n, int k) {
        res.clear();
        path.clear();
		backtracing(n, k, 1);
        return res;
	}
};

时间复杂度: O(n*2^n)
空间复杂度: O(n)

3. 组合优化

思路: 在for循环时,判断剩余数的个数与当前数组加起来是否大于等于k,若小于k,则可以直接跳出循环。

class Solution {
private:
	vector<vector<int>> res; // 保存最后的结果
	vector<int> path; // 保存单一的结果

public:
	// 回溯法,startIndex记录开始的下标(从1开始),保证不重复
	void backtracing(int n ,int k ,int startIndex) { 
		// 终止条件
		if (path.size() == k) {
			res.push_back(path);
			return;
		}

		// 横向遍历,前面不变,当前位置每次变化,回溯
		// 优化,i的值范围举例即可得到
		for (int i = startIndex; i <= path.size()+n+1-k; i++) {
			path.push_back(i);
			backtracing(n, k, i + 1);
			path.pop_back();
		}
		return;

	}

	vector<vector<int>> combine(int n, int k) {
		res.clear();
		path.clear();
		backtracing(n, k, 1);
		return res;
	}
};

4.组合总和Ⅲ

leetcode链接

思路: 和上题类似,只是多了剪枝操作,总体相同

class Solution {
private:
	vector<vector<int>> res;
	vector<int> path;

	// start为当前需添加的数,count 为当前path中的元素和
	void backtracing(int n,int k, int start, int count) {
		if (path.size() == k) {
			if (count == n)
				res.push_back(path);
			return;
		}

		for (int i = start;i <= 10 + path.size() - k; i++) {
			if (count + i > n)  // 剪枝
				break;
			count += i;
			path.push_back(i);
			backtracing(n, k, i + 1, count);
			count -= i;
			path.pop_back();
		}
		return;
	}

public:
	vector<vector<int>> combinationSum3(int k, int n) {
		res.clear();
		path.clear();
		backtracing(n, k, 1, 0);
		return res;
	}
};

时间复杂度: O(n*2^n)
空间复杂度: O(n)

5. 电话号码的字母组合

leetcode链接

思路: 利用回溯算法,先使用map将数字与字符数组对应,再回溯添加每个符合条件的元素。for循环中,遍历每个数字对应的字符数组的所有元素。

class Solution {
private:
	// 定义一个字符数组用于映射电话号码,也可以用map映射
	const string letterMap[10] = {
		// 0,1,2,3,4,5,6,7,8,9
		"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"	
	};
	vector<string> res;
	vector<char> path;  // 记录单次结果,

	// start为当前digits中的下标
	void backtracing(const string digits,int start) {
		if (start == digits.size()) {
			string s = "";
			for (char c : path)
				s += c;
			res.push_back(s);  // 添加结果
			return ;
		}
		int cur = digits[start] - '0';  // 记录当前的字符的int型
		// 回溯,添加所有元素
		for (int i = 0; i < letterMap[cur].size(); i++) {
			// 也可使用string的push_back和pop_back进行回溯
			path.push_back(letterMap[cur][i]);
			backtracing(digits, start + 1);
			path.pop_back();
		}
		return;
	}

public:
	vector<string> letterCombinations(string digits) {
		res.clear();
		path.clear();
		if (digits == "") return res;
		backtracing(digits, 0);
		return res;
	}
};

时间复杂度: O(3^m * 4^n) m 是对应三个字母的数字个数,n 是对应四个字母的数字个数
空间复杂度: O(3^m * 4^n)

6.回溯周末总结

代码随想录:回溯周末总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值