代码随想录算法训练营第二十四天(回溯 三)

注意:在不清楚回溯模板是什么的可以先看前天的内容:

传送门:代码随想录算法训练营第二十二天(回溯 一)-CSDN博客

力扣题部分:

93.复原IP地址

题目链接:. - 力扣(LeetCode)

题面:

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245""192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

思路:

这道题挺难的,但是如果前几天的回溯理解到位,还是可以独立完成的。

        首先我们需要像昨天分割回文串一样分割题面给的字符串,可能有人觉得这里的分割不像前面一样简单——只能分三刀,并且分下来的长度又有要求,内容也有要求,而且分完还要要求不能有剩余等等......

        我在解决的时候为了思路清晰,多设置了几个函数:

首先是change函数,功能是把所给的合法字符串变成数字,如果长度过长,有其他字符或者前导0,直接返回- 1 标志着这段字符串不符合要求。

然后是judgepart函数,功能是通过调用change函数,输入切割的开始和末尾,判断切割后的字符串是否合法。

递归的主题代码函数的切割手法和分割回文串是一样的,这里切割就不讲了,想了解的可以看昨天的131.分割回文串那题。

传送门:代码随想录算法训练营第二十三天(回溯 二)-CSDN博客

关于分割还有人可能会疑惑:不是最多只能切三刀吗?昨天的切割方法能直接套用吗?

其实加个计数器就好了,终止函数部分只有切第三刀后第四刀前时才能记录当前切法。

当然,为了保证每段符合要求,套回溯模板的for循环下需要判断截的部分合不合法(通过调用judgepart函数)

还有一个做题遇到的小问题没提过:答案的每种切法中的"."我是怎么加进去的呢?

我是通过创造一个字符串数组来记录切下来合法的每段字符串,在终止前把里面的字符串和"."拼起来再放入答案数组里的。

 代码实现:

class Solution {
public:
    vector<string>result;
    string rightparts[6] = {""};
    string rightip;
    int change(string nums)
    {
        if(nums.size() > 3) return -1;
        if(nums == "0") return 0;
        bool flag0 = true;
        int i, num = 0;
        for(i = 0; i < nums.size(); i ++)
        {
            if(!isdigit(nums[i]) || nums[i] == '0' && flag0) return -1;
            else flag0 = false;
            num = num * 10 + nums[i] - '0';
        }
        return num;
    }
    bool judgepart(const string &s, int begin, int end)
    {
        string part = s.substr(begin,end);
        if(change(part) >= 0 && change(part) <= 255) return true;
        else return false;
    }
    void judgeip(const string &s, int cutindex, int count)
    {
        if(cutindex >= s.size())
        {
            if(count == 4)
            {
                rightip = "";
                for(int i = 1; i < 4; i ++)
                {
                    rightip += rightparts[i];
                    rightip += ".";
                }
                rightip += rightparts[4];
                result.push_back(rightip);
            }
            return;
        }
        for(int i = cutindex; i < s.size(); i ++)
        {
            if(judgepart(s,cutindex,i - cutindex + 1) && count <= 4)
            {
                string rightpart = s.substr(cutindex, i - cutindex + 1);
                rightparts[++count] = rightpart;
                judgeip(s, i + 1, count);
                count --;
            }
        }
    }
    vector<string> restoreIpAddresses(string s) {
        judgeip(s, 0, 0);
        return result;
    }
};

78.子集

题目链接:. - 力扣(LeetCode)

题面:

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

思路:

这个就涉及到回溯法的不同与前面的功能了:分子集。

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。

那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

有同学问了,什么时候for可以从0开始呢?

求排列问题的时候,就要从0开始,因为集合是有序的,{1, 2} 和{2, 1}是两个集合,排列问题我们后续的文章就会讲到的。

以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:

从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合

按照这样的思路和套用回溯模板,我们不难写出下面的代码:

 代码实现:

class Solution {
public:
    vector<vector<int>>result;
    vector<int>son;
    void findsets(vector<int> nums, int cutindex)
    {
        result.push_back(son);
        if(cutindex >= nums.size()) return;
        for(int i = cutindex; i < nums.size(); i ++)
        {
            son.push_back(nums[i]);
            findsets(nums, i + 1);
            son.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        findsets(nums, 0);
        return result;
    }
};

90.子集II

题目链接:. - 力扣(LeetCode)

题面:

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

思路:

对于看懂了昨天40.组合总和II和今天的上一个题目的人来说,这道题没有任何新的困难可言。

如果上面那道题看明白了,理解完去重的方法实现原理题目就会做了。 

因为这个去重原理非常抽象,我就不再重复了,想看关键去重代码原理的可以看昨天题目40.组合总和II的内容:

传送门:代码随想录算法训练营第二十三天(回溯 二)-CSDN博客

 代码实现:

class Solution {
public:
    vector<vector<int>>result;
    vector<int>son;
    void findset(vector<int> nums, int cutindex, vector<bool> &used)
    {
        result.push_back(son);
        if(cutindex >= nums.size()) return;
        for(int i = cutindex; i < nums.size(); i ++)
        {
            if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;
            son.push_back(nums[i]);
            used[i] = true;
            findset(nums,i + 1, used);
            son.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool>used(nums.size(),false);
        sort(nums.begin(), nums.end());
        findset(nums, 0, used);
        return result;
    }
};



 

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值