算法训练营DAY28|93.复原IP地址、78.子集、90.子集II

93. 复原 IP 地址 - 力扣(LeetCode)https://leetcode.cn/problems/restore-ip-addresses/复原IP地址这道题也是一道切割字符串的问题,也是略有难度的一道题,起初我做的时候,想不通是怎么精确的把字符串分割成四段做终止条件的,后来看了题解知道,并不是一开始就能够确定切出多大一块,而是一点点试错的,虽然我们知道,是要将该字符串序列分成四份,由三个点分割开来,但是我们代码实现时候是无法做到一开始就确定前半部分是切割前几个数字的,这是因为字符串序列可能是不一样的,题目要求数字前面不能由前导0构成,只有0是可以的,且每一块分割出来的数字大于等于0小于等于255,由于字符序列的不同,有的时候我们第一次只能切一个数字,就是0的情况,0不能做前导,有的时候我们可以多切一点,这是无法固定的,所以我们要一点点切割递归下去,好在我们有函数递归可以帮助我们完成这一难题。

其他的困难部分我们在上一期的切割回文子串已经讲的差不多了,无非也就是如何表示切割线,如何传进去切割区间做判断,这种问题上一期已经讲的很清楚了,不明白可以去看那一期。

有区别的是,这道题的终止判断条件也是有所不同的,它的终止条件是我们已经放入了三个逗点做分割了,这时候我们进入终止判断,分割问题,通常都是由题目要求的关键信息,作为终止条件,它和这几期做的那些组合题不一样,不是固定的套路,需要自行想出。

class Solution {
public:
vector<string>result;
void backtraking(string& s,int start,int pointsum){
    if(pointsum==3){
        if(isvaill(s,start,s.size()-1))
        result.push_back(s);return;
    }
    for(int i=start;i<s.size();i++){
        if(isvaill(s,start,i)){
            s.insert(s.begin()+i+1,'.');
            backtraking(s,i+2,pointsum+1);//注意是+2而不是+1
            s.erase(s.begin()+i+1);
        }
        else continue;
    }
}
bool isvaill(string s,int begin,int end){
   if(begin>end)return false;
   if(s[begin]=='0'&&begin!=end)return false;
   int sum=0;
   for(int i=begin;i<=end;i++){
       if(s[i]<'0'||s[i]>'9')return false;
       sum=(s[i]-'0')+sum*10;
       if(sum>255)return false;

   }
   return true;
}
    vector<string> restoreIpAddresses(string s) {
        backtraking(s,0,0);
        return result;
    }
};

还有三点需要注意的细节,第一点是除了在for循环里要判断该切割区间是否合法,我们在终止条件还要额外判断一次,这是为什么呢?原因在于我们终止条件是三个逗点就插入字符串,而我们之前只是判断了三次切割串是否合法,因为判断一次要放一个标点所以肯定是只判断了三次,我们在这里加一次判断的目的是为了知道,这最后剩余的部分是否依然具有合法性。第二点是字符串的insert成员函数插入标点是传入下标的前一个位置,所以要进行+1传入,而且我们在进行递归下一层时,传入的下一层开始遍历起始位置是i+2,而不是i+1,因为加入了一个标点的缘故。第三点是判断部分代码的细节,下面的for循环来依次取各个数相加判断和是否大于255,其实这里的要点主要在于对每一个字符的判断,防止它是字符1-9之外的数,但是leetcode上已经明确说了传入的都是数字序列,可是题目仍然要求判断,感觉可能是题目描述没有全改过来的缘故。


78. 子集 - 力扣(LeetCode)icon-default.png?t=MBR7https://leetcode.cn/problems/subsets/子集问题和前面讲过很多的组合问题,解法如出一辙,只是在处理数据加入结果集里有所不同。

直接看代码

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

和组合问题一样,需要注意的是处理数据,是每一次都将节点放入结果数组,因为求的是子集要保留所有的节点,每一个节点都是它的子集,所以不存在剪枝操作。那空集是如何放入的呢?实际上就是没有节点时候直接放入了path,这一点也很好理解。单层递归是用一个for循环这一点就是组合的内容,不懂的可以翻看前面组合的章节。


90. 子集 II - 力扣(LeetCode)icon-default.png?t=MBR7https://leetcode.cn/problems/subsets-ii/子集II也是简单的,只要充分理解前面所讲组合问题的去重方法就可以了,整体代码和子集差不多,只是加入了组合的去重逻辑。

class Solution {
public:
vector<vector<int>>result;
vector<int>path;
void backtraking(vector<int>&nums,int start,vector<int>&nums2){
    result.push_back(path);
    for(int i=start;i<nums.size();i++){
        if(i>=1&&nums[i]==nums[i-1]&&nums2[i-1]==0)continue;
        nums2[i]=1;
        path.push_back(nums[i]);
        backtraking(nums,i+1,nums2);
        path.pop_back();nums2[i]=0;
    }
}
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<int>nums2(nums.size(),0);
        sort(nums.begin(),nums.end());
        backtraking(nums,0,nums2);
        return result;
    }
};

用一个辅助数组记录我们所要添加数的数组,各个数字的加入情况。这里再简单讲一下去重的逻辑,画一个树形图辅助理解,当我们在一个数组里加入元素,这道题说是给定数组有重复元素,当我们构成一个答案数据时候i,由于每次递归会i+1,走到下一个数据位置,作为下一次放元素的起始位置,所以我们不需要担心会一个数据取两次,但是如果有两个数据,我们第一次用到它取的一系列答案数据,再你第二次用到的时候会将这些答案数据又取一次,因为第一次用的这个数字包含了你第二次用的时候取到的全部答案了,这一点画出树形图很容易理解,所以要将它去掉,也就是去重的逻辑,需要做的是将给定数组排序,让重复数据挨在一起,然后用一个数组去辅助做判断,如果本次要放入的数据和上一个位置数据值相等,且上一个相等数据本次没有放入,则说明我们要删除它,这是数层上的去重逻辑。


以上代码均可ac。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习算法的杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值