回溯法解题思想及相关例题整理

这篇博客总结了回溯法在LeetCode中解决子集、组合、组合总和、排列等经典问题的思路和代码实现。通过构建递归树、设置结束条件、选择列表、剪枝等步骤,有效地求解各种组合问题。示例包括无重复和有重复元素的情况,以及字符串的全排列和字母大小写的全排列。同时,还介绍了回溯法在单词搜索和N皇后问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

回溯法

整理了一些在LeetCode上做的回溯法的题目

思想

  • 画出递归树,找到状态变量(回溯函数的参数)
  • 根据题意,确立结束条件
  • 找准选择列表(与函数参数相关)
  • 判断是否需要剪枝
  • 作出选择,递归调用,进入下一层
  • 撤销选择

子集组合

子集(元素不重复)

/**
 * Leetcode_78
 * 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)
 */
class Solution {
public:
	vector<vector<int>> subsets(vector<int>& nums) {
		vector<vector<int>> res;
		vector<int> cur;
		backtrack(nums, res, cur, 0);
		return res;
	}
	
	void backtrack(vector<int>& nums, vector<vector<int>>& res, vector<int> cur, int start)
	{
		res.push_back(cur);
		for (int i = start; i < nums.size(); i++)
		{
			cur.push_back(nums[i]);
			backtrack(nums, res, cur, i + 1);
			cur.pop_back();
		}
	}
};

子集(元素重复)

/**
 * Leetcode_90
 * 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
 */
class Solution {
public:
	vector<vector<int>> subsetsWithDup(vector<int>& nums) {
		vector<vector<int>> res;
		vector<int> cur;
		sort(nums.begin(), nums.end()); // 排序 比较前后元素进行剪枝
		backtrack(nums, res, cur, 0);
		return res;
	}

	void backtrack(vector<int>& nums, vector<vector<int>>& res, vector<int> cur, int start)
	{
		res.push_back(cur);
		for (int i = start; i < nums.size(); i++)
		{
			if(i>start && nums[i]==nums[i-1]) //剪枝
				continue;
			cur.push_back(nums[i]);
			backtrack(nums, res, cur, i + 1);
			cur.pop_back();
		}
	}
};

组合

/**
 * Leetcode_77
 * 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
 */
class Solution {
public:
    vector<vector<int>> res;
	vector<int> cur;
	vector<vector<int>> combine(int n, int k) {		
		backtrack(n, 1, k);
		return res;
	}

	void backtrack(int n, int start, int k)
	{
		if (cur.size() + (n - start+1) < k) //剪枝
			return;
		if (cur.size() == k)
		{
			res.push_back(cur);
			return;
		}
		for (int i = start; i <= n; i++)
		{
			cur.push_back(i);
			backtrack(n, i+1, k);
			cur.pop_back();
		}
	}
};

组合总和(每个数字可以重复被选取)

/**
 * Leetcode_39
 * 给定一个无重复元素的数组 candidates 和一个目标数 target ,
 * 找出 candidates 中所有可以使数字和为 target 的组合
 */
class Solution {
public:	
	vector<vector<int>> res;
	vector<int> cur;
	vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
		int sum = 0;
		sort(candidates.begin(), candidates.end());
		backtrack(target, sum, candidates, 0);
		return res;
	}
	void backtrack(int target, int sum, vector<int>& candidates, int start) {
		if (sum == target)
		{
			res.push_back(cur);
			return;
		}
		else if (sum > target)
			return;
		for (int i = start; i < candidates.size();i++)
		{
			if(sum+candidates[i]>target) //剪枝
				break;
			cur.push_back(candidates[i]);
			sum += candidates[i];
			backtrack(target, sum, candidates, i);
			cur.pop_back();
			sum -= candidates[i];
		}
	}
};

组合总和2(每个数字只能使用一次)

/**
 * Leetcode_40
 * 给定一个数组 candidates 和一个目标数 target ,
 * 找出 candidates 中所有可以使数字和为 target 的组合
 */
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
	vector<vector<int>> res;
	vector<int> cur;
	vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
		int sum = 0;
		sort(candidates.begin(), candidates.end());
		backtrack(target, sum, candidates, 0);
		return res;
	}
	void backtrack(int target, int sum, vector<int>& candidates, int start) {
		if (sum == target)
		{
			res.push_back(cur);
			return;
		}
		else if (sum > target)
			return;
		for (int i = start; i < candidates.size(); i++)
		{
			// 剪枝
			if (sum + candidates[i]>target) // 后面更大的元素和更大
				break;
			// 重复子集
			if (i - start > 0 && candidates[i] == candidates[i - 1])
				continue;
			cur.push_back(candidates[i]);
			sum += candidates[i];
			backtrack(target, sum, candidates, i+1);
			cur.pop_back();
			sum -= candidates[i];
		}
	}
};

排列

全排列

/**
 * Leetcode_46
 * 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。
 * 你可以 按任意顺序 返回答案。
 */
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
		vector<vector<int>> res;
		if (nums.size() == 0)
			return{};
		dfs(res, nums, 0);
		return res;
	}
	void dfs(vector<vector<int>>& res, vector<int>&nums, int first)
	{
		if (first == nums.size())
		{
			res.push_back(nums);
			return;
		}

		for (int i = first; i < nums.size(); i++)
		{
			// 选择
			if(first !=i)
				swap(nums[first], nums[i]);
			// 回溯
			dfs(res, nums, first+1);
			// 撤销选择
			if (first != i)
				swap(nums[first], nums[i]);
		}
	}
};


全排列2

/**
 * Leetcode_47
 * 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列
 */
// swap
class Solution0 {
public:
	vector<vector<int>> res;
	vector<vector<int>> permuteUnique(vector<int>& nums) {
		sort(nums.begin(), nums.end());
		backtrack(nums, 0);
		return res;
	}

	void backtrack(vector<int>& nums, int start) {
		if (start == nums.size())
		{
			res.emplace_back(nums);
			return;
		}
		for (int i = start; i < nums.size(); i++)
		{
			// 交换后,后面的元素可能无序
			sort(nums.begin() + start, nums.end());
			if (i > start && nums[i] == nums[i - 1])
				continue;
			if (i != start)
				swap(nums[i], nums[start]);
			backtrack(nums, start + 1);
			if (i != start)
				swap(nums[i], nums[start]);
		}
	}
	
};
// 使用visited数组
class Solution {
	vector<vector<int>> res;
	vector<int> path;
	vector<int> vis;
public:
	
	vector<vector<int>> permuteUnique(vector<int>& nums) {
		sort(nums.begin(), nums.end());
		vis.resize(nums.size());
		backtrack(nums);
		return res;
	}

	void backtrack(vector<int>& nums) {
		if (path.size() == nums.size())
		{
			res.emplace_back(path);
			return;
		}
		for (int i = 0; i < nums.size(); i++)
		{
            // 1、这个数字选过了跳过
			// 2、剪枝:当前数字和前一个数字相等,且前一个数字未被选过
			if (vis[i] || (i> 0 && nums[i] == nums[i - 1] && !vis[i-1]))
				continue;
			path.emplace_back(nums[i]);
			vis[i] = true;
			backtrack(nums);
			path.pop_back();
			vis[i] = false;
		}
	}
};

字符串的全排列

/**
 * 剑指Offer_387
 * 输入一个字符串,打印出该字符串中字符的所有排列
 */
// swap

class Solution {
	vector<string> res;
public:
	vector<string> permutation(string s) {
		backtrack(s, 0);
		return res;
	}
	void backtrack(string s, int start) {
		if (start == s.length())
		{
			res.emplace_back(s);
			return;
		}
		for (int i = start; i < s.length();i++)
		{
			sort(s.begin()+start, s.end());
			if(i>start && s[i]==s[i-1])
				continue;
			if (i != start)
				swap(s[i], s[start]);
			backtrack(s, start + 1);
			if (i != start)
				swap(s[i], s[start]);
		}
	}
};

// 使用visited数组
class Solution {
	vector<string> res;
	string path;
	vector<int> vis;
public:
	vector<string> permutation(string s) {
		vis.resize(s.length());
		sort(s.begin(), s.end());
		backtrack(s);
		return res;
	}
	void backtrack(string s) {
		if (path.length() == s.length())
		{
			res.emplace_back(path);
			return;
		}
		for (int i = 0; i < s.length(); i++)
		{
			if (vis[i] || (i>0 && s[i]==s[i-1] && !vis[i-1])) 
				continue;
			path.push_back(s[i]);
			vis[i] = true;
			backtrack(s);
			path.pop_back();
			vis[i] = false;
		}
	}
};

字母大小写全排列

/**
 * Leetcode_784
 * 给定一个字符串S,通过将字符串S中的每个字母转变大小写,
 * 我们可以获得一个新的字符串。返回所有可能得到的字符串集合
 */
class Solution {
private:
	vector<string> res;
	void backtrack(string s, int start) {
		if (start == s.length())
		{
			res.push_back(s);
			return;
		}

		if (isalpha(s[start])) { // 字母
			if(isupper(s[start]))
				s[start] = tolower(s[start]);
			backtrack(s, start + 1);
			if (islower(s[start]))
				s[start] = toupper(s[start]);
			backtrack(s, start + 1);
		}
		else {
			backtrack(s, start + 1);
		}
	}
public:
	vector<string> letterCasePermutation(string s) {
		backtrack(s, 0);
		return res;
	}	
};

搜索

单词搜索

/**
 * Leetcode_79
 * 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。
 * 如果 word 存在于网格中,返回 true ;否则,返回 false 。
 * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,
 * 其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。
 * 同一个单元格内的字母不允许被重复使用。
 */
#include <vector>
using namespace std;
class Solution {
public:
	bool exist(vector<vector<char>>& board, string word) {
		if (board.size() == 0 || board[0].size() == 0 || word.length() == 0)
			return false;
		for (int i = 0; i < board.size();i++)
		{
			for (int j = 0; j < board[0].size();j++)
			{
				if (board[i][j] == word[0] && dfs(board, word, 0, i, j))
					return true;
			}
		}
		return false;
	}
private:
	bool dfs(vector<vector<char>>& board, string word, int start, int i, int j) {
		if (i < 0 || i >= board.size() || j<0 || j>=board[0].size() || board[i][j] != word[start])
			return false;
		if (start == word.size()-1) {
			return true;
		}
		char temp = board[i][j];
		board[i][j] = '\0';
		bool res = dfs(board, word, start + 1, i+1, j)
			|| dfs(board, word, start + 1, i-1, j)
			|| dfs(board, word, start + 1, i, j+1)
			|| dfs(board, word, start + 1, i, j-1);
		board[i][j] = temp;
		return res;
	}
};

N皇后

/**
 * Leetcode_51
 * n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
 * 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
 * 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
 */
class Solution {
public:
	vector<vector<string>> solveNQueens(int n) {
		string a;
		for (int i = 0; i < n; i++)
			a.push_back('.');
		for (int i = 0; i < n; i++)
			path.push_back(a);

		dfs(0, n);

		return res;
	}
private:
	vector<vector<string>> res;
	vector<string> path;
	bool col[9], gl[9], ugl[9]; // 列、斜线、斜线
	void dfs(int start, int n) {
		if (start == n)
		{
			res.push_back(path);
			return;
		}

		// 第start个皇后从n列中选位置
		for (int i = 0; i < n; i++)
		{
			if (!col[i] && !gl[start + i] && !ugl[n - start + i])
			{
				col[i] = gl[start + i] = ugl[n - start + i] = true;
				path[start][i] = 'Q';
				dfs(start + 1, n);
				path[start][i] = '.';
				col[i] = gl[start + i] = ugl[n - start + i] = false;
			}
		}
	}
};

分割回文串

/*
 * 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
 * 返回 s 所有可能的分割方案。
*/
#include <vector>
using namespace std;
class Solution {
public:
	vector<vector<string>> partition(string s) {
		len = s.length();
		dp.assign(len, vector<int>(len, true));
		for (int i = len-1; i >=0;i--)
		{
			for (int j = i+1; j < len; j++)
			{
				dp[i][j] = (s[i] == s[j]) && dp[i + 1][j - 1];				
			}
		}
		dfs(s, 0);
		return res;
	}
private:
	vector<vector<string>> res;
	vector<string> path;
	vector<vector<int>> dp; // dp[i][j]: s[i:j]是否是回文串
	int len;
	void dfs(string s, int start) {
		if (start == len) {
			res.emplace_back(path);
			return;
		}
		for (int i = start; i < s.length();i++)
		{
			if(!dp[start][i])
				continue;
			string subS = s.substr(start, i - start + 1);
			path.emplace_back(subS);
			dfs(s, i + 1);
			path.pop_back();
		}
	}
};

参考
https://leetcode-cn.com/problems/subsets/solution/c-zong-jie-liao-hui-su-wen-ti-lei-xing-dai-ni-gao-/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值