个人主页:@Sherry的成长之路
学习社区:Sherry的成长之路(个人社区)
专栏链接:练题
长路漫漫浩浩,万事皆有期待
216. 组合总和 III - 力扣(LeetCode)
组合总和3和上一期的组合思路上差不太多,用数字1-9相当于上一道组合题的1-n的范围求解答案,而这道题多了一个要想加等于一个固定的数值。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum,int k,int sum,int startIndex)
{
if(path.size()==k)
{
if(sum==targetSum)
{
result.push_back(path);
}
}
for(int i=startIndex;i<=9;i++)
{
sum+=i;
path.push_back(i);
backtracking(targetSum,k,sum,i+1);
sum-=i;
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
result.clear();
path.clear();
backtracking(n,k,0,1);
return result;
}
};
我们直接向数组里加入数据,因为我们暂时不能确定怎样的组合才能凑够n,所以我们当数组里元素等于k个数据时,直接判断一下,如果此时总和等于我们想要的答案,那么直接加入答案数组,否组向上一层直接返回。而下面的单层递归逻辑是我们仍然用一个start作为开始的下标来记录避免取到重复下标,将1-9每一个数都加入进来试错,直到找出正确答案。
这道题也同样存在可以剪枝的部分,这道题可以分成两部分的剪枝,也很巧妙。
第一部分的剪枝原因在于这道题是组合求和,它给出了一个相加之和为n的组合,这个时候我们可以判定当我们此时递归的时候,sum当前组合内的值,如果大于了给定的目标值n,那么很明显我们一定要return了,因为是在1-9中取数,都是正数,怎么加也不可能找的到了。所以第一部分剪枝一定是写在判断部分的代码里,这和上一期的组合是有所区别的。
相似的剪枝是第二部分的剪枝, 仍然是采用之前的做法剪枝,path.size()是当前数组中所存的数据有几个,k-path.size()是还需要几个数字,9-(k-path.size())+1,是我们最多可以从哪个数字向下进行递归,这里的数是从1-9的,不是从数组中取数,最开始不是0而是1,所以要加1。
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
void backtraking(int k,int n,int start,int sum){
if(sum>n) return;
if(path.size()==k)
if(sum==n){
result.push_back(path);return;
}
for(int i=start;i<=9-(k-path.size())+1;i++){
path.push_back(i);
backtraking(k,n,i+1,sum+i);
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtraking(k,n,1,0);
return result;
}
};
个人认为的缺陷
第一部分的剪枝是为了避免当sum>n时候继续递归,第二部分剪枝是告诉最多从哪里开始递归,第二部分的剪枝是完全根据传进答案的个数做的剪枝,与最终结果n无关。
那么当n=100时候,k=2时候我们还是无法快速的判断当前是无法找的出正确的答案的,看到这里可能会去想,如果是这种情况还是能很快的判断啊,通过第二次剪枝很快就根据k跳出递归了,那如果n很大,k也很大,但是k大也不足以凑出n呢?那还是会进行很多次递归-回溯的过程吧,做了很多次的搜索但是却一个结果都无法返回,但是好像暂时也没有其他什么方法能够比它更好用了。
17. 电话号码的字母组合 - 力扣(LeetCode)
这道题也是组合题,不过略有一些难度。首先我们要根据它给出的电话按键,用map或者数组将其数值所对应的字母都保存起来。然后再构思回溯函数,同样的也是创建一个结果数组,创立一个中间存数据的数组。这里我们并不需要像start一样作用的变量来指示我们下一次要走向哪一个下标,因为这里我们是同时操作多个数组,往里面加入数据,我们需要的是一个变量告诉我们该遍历哪一个电话按键了它指向的是传进来的电话号码序列的下标。
由于这样的缘故,所以我们结束条件可以确定是该变量等于函数传进来的电话号码字符串的字符个数,这里有一些疑问,为什么我们这个这个变量是表示下标你还要它等于字符序列总个数呢?而不是总序列个数减一,原因是我们在遍历到最后一个字符时我们仍然需要将最后一个数字对应的字母排列进来,而不是直接跳出循环,所以我们要等待它这个下标指向最后一个字符的下一个时候,才能来做判断。当它与字符序列的个数相等时我们收获结果,并想上一层返回,以寻求其他结果。
class Solution {
public:
string lettermap[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
vector<string> result;
string s;
void backtracking(const string& digits,int index)
{
if(index==digits.size())
{
result.push_back(s);
return;
}
int digit=digits[index]-'0';
string letters=lettermap[digit];
for(int i=0;i<letters.size();i++)
{
s.push_back(letters[i]);
backtracking(digits,index+1);
s.pop_back();
}
}
vector<string> letterCombinations(string digits)
{
s.clear();
result.clear();
if(digits.size()==0)
{
return result;
}
backtracking(digits,0);
return result;
}
};
这里有一些需要我们注意的,我们要在循环内创立一个整形来存储该字符序列的某个位置它所代表的数字是什么,然后才是用另一个字符串变量来记录该数字所对应的字母序列,我们仅需要一个变量来指示下标的原因在上面已经说过了,而当它返回到上一层之后,我们如何找到上一次对应的电话号码的字母的下一位呢?这是递归返回后i++所要做的事情,我们完全不需要担心,其他的单层逻辑和那些组合题大体一样,不做赘述。
今天我们完成了组合总和 III\电话号码的字母组合两道题,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~