DFS算法入门(二)

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
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基本单元的逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值