系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
DSF算法的学习和案例分析
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
随着案例的复杂度变高,需要分析dfs的基本单元逻辑的难度也随之变大,下面将按照由浅入深的方式分析各个案例。
提示:以下是本篇文章正文内容,下面案例可供参考
一、组合总和 II(Leetcode 40中等)
1.问题描述
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。
2.算法概述
2.1 确定入参和出参的数据结构
1.首先问题是根据提供的数组找到所有组合,所以入参里需要组合的原始数据
2.返回的结果是二位数组,也可以用vector<vector>
来表示
3.一些中间参数可以后续在说明
2.2 出参条件
1.出参的条件很显然是遍历到了结尾、freq[i].first>target(在排好序的情况下,当前值已经大于目标值)
2.因此当前遍历的index、结束标志就是digits.size()
3.中间参数由此扩展两个i,n
2.3 编写最小单元的处理逻辑
首先我们将此题和leecode39 组合综合联系起来,两题的唯一不同是39题可以无限使用数组的值,本题只能使用一次。这就意味着在深度遍历上需要控制该值的适应次数。
- 既然要控制每个值的适应次数,首先将数组里的值和次数做成map结构方便查阅。
- 在遍历时按照从小到大的值遍历
代码分析:
- sort(candidates.begin(),candidates.end());这是将数组里的值从小到大排序
- int most=min(target/freq[i].first,freq[i].second);这是取一个值得最大次数,target/freq[i].first表示的剩余的目标值/该值,freq[i].second表示该值的总个数。两者取最小值
- dfs(i+1,target-j*freq[i].first,temp,res);表示每取一次,dfs遍历一次
- 注意最后的pop_back,必须是放在后面,不能合并,因为回溯要等这个值得所有次数全部用完后才能回溯。另一方式是在上一个for内,push_back时放进去对应次数的值,才能在那个for循环内回溯。
3.代码展示:
代码如下(示例):
class Solution {
private:
vector<pair<int,int>> freq;
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<int> temp;
vector<vector<int>> res;
sort(candidates.begin(),candidates.end());
for (int num: candidates) {
if (freq.empty() || num != freq.back().first) {
freq.emplace_back(num, 1);
} else {
++freq.back().second;
}
}
dfs(0,target,temp,res);
return res;
}
void dfs(int i,int target,vector<int>& temp,vector<vector<int>>& res)
{
if(target==0)
{
res.push_back(temp);
return;
}
if(i==freq.size()||freq[i].first>target)
{
return;
}
dfs(i+1,target,temp,res);
int most=min(target/freq[i].first,freq[i].second);
for(int j=1;j<=most;++j)
{
temp.push_back(freq[i].first);
dfs(i+1,target-j*freq[i].first,temp,res);
}
for(int j=1;j<=most;++j)
{
//如果要写在上个循环里,除非j==1,push_back 一个,j==2 push_back 两个
temp.pop_back();
}
}
};
二、目标和(Leetcode 494中等)
1.问题描述
给你一个整数数组 nums 和一个整数 target 。不多详述
2.算法概述
2.1 确定入参和出参的数据结构
1.首先问题是根据提供的数组找到所有组合,所以入参里需要组合的原始数据
2.返回的结果是所有可能
3.一些中间参数可以后续在说明
2.2 出参条件
1.出参的条件很显然是遍历到了结尾
2.因此当前遍历的index、结束标志就是digits.size()
3.中间参数由此扩展两个i,n
2.3 编写最小单元的处理逻辑
此问题有点类似背包问题,其实就是每次遍历到有个数字,都有两种选择:+或者-。如此分析,问题就会简单很多,而且问题中没有让我们返回如何做到的,只是返回结果的个数,因此不需要回溯。
代码分析:
- i==nums.size() 因为所有数都得用上,所以只能以遍历到结尾才停止
- dfs(nums,i+1,target,cur-nums[i],res);这个值取“-”后的dfs遍历
- dfs(nums,i+1,target,cur+nums[i],res);这个值取“+”后的dfs遍历
3.代码展示:
代码如下(示例):
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int res=0;
dfs(nums,0,target,0,res);
return res;
}
void dfs(vector<int>& nums,int i,int target,int cur,int& res)
{
if(i==nums.size())
{
if(cur==target) res++;
return;
}
if(i>nums.size())
{
return;
}
dfs(nums,i+1,target,cur-nums[i],res);
dfs(nums,i+1,target,cur+nums[i],res);
}
};
总结
提示:这里对文章进行总结:
本期通过两个实例具体分析了当进行深度遍历时所应该关注的点,即遍历时附加的条件。这种条件需要根据题目给的信息取分析内在的联系,也可以根据类似问题的解法,多思考两者的不同和相同点,从而快速的分析出dfs基本单元的逻辑。