题目描述:
刚学会的回溯(见46题全排列),这里稍作参考,修改一下
//fin记录当前填入多少数字到了arry
void BackTrack(vector<int> arry, int k,int fin)
{
if(fin==k)
{
//注意:这里传入的arry长为n,需要截断
arry.erase(arry.begin()+k,arry.end());
//填入
result.push_back(arry);
return;
}
else
{
for(int i=fin+1;i<arry.size();i++)//将所有未选元素中依次填入fin处
{
swap(arry[fin],arry[i]);//挑一个后面的放到fin处
BackTrack(arry,k,fin+1);
swap(arry[fin],arry[i]);//回溯,换回来,准备将下一个元素填入fin
}
}
}
vector<vector<int>> result;
vector<vector<int>> combine(int n, int k)
{
vector<int> tmp;
//传一个[1,n]的数组进去
for(int i=1;i<n+1;i++)
tmp.push_back(i);
BackTrack(tmp,k,0);
return result;
}
那当然结果是不对, 截断之后会认为[1,4] 和 [4,1]是两个排列,但是此题视作一个排列。可以加一个大小的限制,只将比当前vector大的元素加入。
故做如下修改:
//给定上界n,每次只将更大的元素加入,取代了上代码tmp
//max记录target中最大的值,其实就是最后一个元素,为了避免判断target为空的情况,多传个参
void Select(int n,int k, vector<int> &target,int max)
{
if(k==target.size())
{
result.push_back(target);
return;
}
else
{
//target的范围是[1,max],i需要[max+1,n]
for(int i=max+1;i<n+1;i++)
{
target.push_back(i);
Select(n,k,target,i);
//回溯,依次填入下个元素
target.pop_back();
}
}
}
vector<vector<int>> combine(int n, int k)
{
vector<int> tmp;
//一开始tmp为空,max为0
Select(n,k,tmp,0);
return result;
}
注意::target前面的&对传参的时空优化都很重要!自己体会优化前后:
到这里其实感觉优化到头了, 看看dl的解答,发现了另一个优化点:
for循环中的 i 其实不一定能取到 n,如:
给定序列1~7,k=5,只要拿出两个元素即可;这就意味着:序列[1,5]开头的目标序列不存在!因为默认每次加入的元素都比5大,而跳过了2,3,4,这样的序列最长都只有[1,5,6,7],循环应该结束,在此状态下如果让i遍历到7无疑是一种浪费。
故 i 和n的关系为:
①target填充到k个元素,还需 个元素;
②当前剩余未填充的元素范围 [ i , n ] 共 个元素;
③如果要循环继续,剩余的元素一定要比还需要的元素多,即:
化简有:
修改判断条件之后表现nice
(别问为什么内存不优化,问就是空间换时间,绝对不是菜)