回溯中的组合问题(电话号码的字母组合)

文章目录

  • 回溯算法
  • 一、组合
  • 二、电话号码的字母组合
  • 三、组合总和
  • 四、组合总和II
  • 五、组合总和III


回溯算法

经过对回溯算法的简单学习并做了几道相关的题之后,做一下这一部分的总结。回溯算法与DFS比较像,思路都是顺着一条路往下走,在走的过程中记录我们需要的相关信息,当此条路走到头时,回退一步,尝试别的路。
回溯中的组合问题(电话号码的字母组合)_第1张图片
每一层都是一次递归,每次递归都要遍历当前层的所有节点。两个重要点:递归的返回条件(当记录的信息满足题目条件时);回退前恢复原状态(在递归前将结点加入到已遍历的结点当中,当递归函数返回后,要将结点从已遍历的结点中剔除,以不影响同一层下一结点的递归)。

一、组合

原题目为:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。
这个题比较简单,在了解了回溯算法的基本原理之后,就可以写出来了,过程见注释。

class Solution {
private:
	vector<vector<int>>result;//用来存储最后的结果
	vector<int>path;//用来存储中间遍历的路径

	void backtracing(int n, int k, int startIndex)
	{
		//满足条件将path放入result中,然后直接返回
		if (path.size() == k)
		{
			result.push_back(path);
			return;
		}
		//因为之前的结点已经选择过了,所以从当前到剩余结点进行回溯
		//比如[2,3,4,5],k=2,第一层递归,此时元素2已被选择,剩余一个元素只能从[3,4,5]中选择
		for (int i = startIndex; i <= n; i++)
		{
			path.push_back(i);
			backtracing(n, k, i + 1);
			path.pop_back();//恢复原状态
		}
		return;
	}

public:
	vector<vector<int>> combine(int n, int k) {
		//进入回溯
		backtracing(n, k, 1);
		return result;
	}
};

二、电话号码的字母组合

原题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
回溯中的组合问题(电话号码的字母组合)_第2张图片
与上一题类似,我们可以使用同样的思路进行解题,只不过不同的是规定了数字的范围,而给出了数字对应字母的可能的取值范围,因此进入回溯的条件发生了变化,详细过程见注释。

class Solution {
private:
	vector<string>result;
	string path;
	vector<string>phone{"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};//每个数字可选的字母
	void backTracing(string digits, int k,int startIndex)
	{
		if (path.size() == k)
		{
			result.push_back(path);
			return;
		}
		//判断当前数字对应字母数是4个还是3个
		int num = digits[startIndex]=='7'||digits[startIndex]=='9'?4:3;
		//遍历当前数字对应的字母
		for (int i = 0; i < num; i++)
		{
			//取到当前数字对应phone数组的索引
			int phoneIndex = digits[startIndex] - '2';
			path.push_back(phone[phoneIndex][i]);
			backTracing(digits, k, startIndex + 1);//递归下一个数字
			path.pop_back();//恢复原状态
		}
		return;
	}

public:
	vector<string> letterCombinations(string digits) {
		if (digits.size() == 0)
			return result;
		int size = digits.size();
		backTracing(digits, size, 0);
		return result;
	}
};

三、组合总和

原问题:
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。

本题要求同一个数字可以无限制重复被选取,因此对于某个数字就存在两种可能性即选取与不选取。

class Solution {
private:
	vector<vector<int>>result;
	vector<int>path;
	void backTracing(vector<int>& candidates, int target, int startIndex,int currentSum)
	{
		//直接跳过时可能存在数组索引越界
		if (startIndex == candidates.size())
			return;

		if (currentSum == target)
		{
			result.push_back(path);
			return;
		}
		//直接跳过
		backTracing(candidates, target, startIndex + 1, currentSum);
		//当当前和与目标和大于等于candidates[startIndex]时,可以重复取值
		if (target - currentSum >= candidates[startIndex])
		{
			path.push_back(candidates[startIndex]);
			backTracing(candidates, target, startIndex, currentSum + candidates[startIndex]);
			path.pop_back();
		}
		return;
	}
public:
	vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
		int size = candidates.size();
		
		backTracing(candidates, target,0,0);
		return result;
	}
};

四、组合总和II

原问题:
给你一个由候选元素组成的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个元素在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。

本题的要求与上一题的区别是候选集和存在重复元素且每个元素在每个组合中只能使用一次。如果采用与上题相同的做法就会产生重复的集合,比如数组[2,2],产生重复组合[2]、[2]。刚开始我并没有想出解决办法,看到官方题解也觉得不难,可是为什么一开始自己就想不到呢,可能写的代码还是少吧~~

/*
	这里解决重复组合问题的办法是在每次递归时对相同元素一块处理,这样在以后的递归过程中就不会再次取到该元素了,也就不会产生重复的组合了。
*/
class Solution {
private:
	vector<vector<int>>result;
	vector<int>path;
	vector<pair<int, int>>single;//first:原数组中的元素值;second:每个元素对应的个数
	void backTracing(vector<int>& candidates, int target, int startIndex, int currentSum)
	{
		if (currentSum == target)
		{
			result.push_back(path);
			return;
		}
		//不选的递归条件可能会导致数组越界
		if (startIndex == single.size())
			return;
		//不选
		backTracing(candidates, target, startIndex + 1, currentSum);
		//选
		int num = single[startIndex].first;
		//判断当前元素可以选择的次数,(target - currentSum) / num和single[startIndex].second中的较小值
		int most = min((target - currentSum) / num, single[startIndex].second);
		for (int i = 1; i <= most; ++i)
		{
			path.push_back(single[startIndex].first);
			backTracing(candidates, target, startIndex + 1, currentSum + i*single[startIndex].first);
		}
		for (int i = 1; i <= most; i++)
			path.pop_back();
		return;
	}
public:
	vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
		sort(candidates.begin(), candidates.end());
		
		for (auto &num:candidates)
		{
			if (single.empty() || num != single.back().first)
				single.emplace_back(num, 1);
			else
				++single.back().second;
		}
		backTracing(candidates, target, 0, 0);
		return result;
	}
};

五、组合总和III

原题目:
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。

这个问题与问题一比较相似,解决方法相同。

class Solution {
private:
	vector<vector<int>>result;
	vector<int>path;
	void backtracing(int k, int n, int startIndex)
	{
		if (path.size() == k)
		{
			//这里也可以把sum当作函数的参数进行传递,在递归过程中进行更新
			int sum = 0;
			for (int elm : path)
				sum += elm;
			if (sum == n)
			{
				result.push_back(path);
				return;
			}
		}

		for (int i = startIndex; i <= 9; i++)
		{
			path.push_back(i);
			backtracing(k, n, i + 1);
			path.pop_back();
		}
		return;
	}
public:
	vector<vector<int>> combinationSum3(int k, int n) {
		backtracing(k, n, 1);
		return result;
	}
};

你可能感兴趣的:(算法,算法,leetcode)