代码随想录算法训练营day27

题目:93.复原IP地址、78.子集、90.子集II

参考链接:代码随想录

93.复原IP地址

思路:本题的思路和上题切割回文串类似,也是先要写一个判断函数,然后一个个切割。对返回条件,如果路径长度已经为4,则可以判断是否切割完毕,如果已经切割到最后,则可以把path加入结果;for循环用于判断end即切割位置;递归用于逐个往后切割。时间复杂度O(n*2^n)。

class Solution {
public:
    bool isValidIp(string s,int begin,int end){//同样我们尽量不用子串,使用begin和end,[begin,end)
        if(end-begin==1||end-begin>1&&end-begin<=3&&s[begin]!='0'){//长度符合要求,长度为1时可以为任何值,长度为2和3的时候必须满足首位不为0
            string s1=string(s.begin()+begin,s.begin()+end);//生成子串,使用substr函数也可以
            int i=stoi(s1);//转换为int
            if(i>=0&&i<=255){
                return true;
            }
        }
        return false;
    }
    string strToIP(vector<string> &path){//将向量转换为IP地址
        string ip;
        ip+=path[0];
        ip.push_back('.');
        ip+=path[1];
        ip.push_back('.');
        ip+=path[2];
        ip.push_back('.');
        ip+=path[3];
        return ip;
    }
    vector<string> ans;
    vector<string> path;//用于记录路径
    void backtracking(const string &s,int begin,int end){//end才是第一次切割的位置!
        if(path.size()==4){//已经达到4的长度
            if(end>s.size()){//end越界说明已经全部选完了
                ans.push_back(strToIP(path));
            }
            return;
        }
        for(int i=end;i<=s.size();i++){
            if(!isValidIp(s,begin,i)){//第一次切割不满足,直接往后切
                continue;
            }
            path.push_back(string(s.begin()+begin,s.begin()+i));
            backtracking(s,i,i+1);//如果切到结尾,i+1=size+1
            path.pop_back();
        }
    }
    vector<string> restoreIpAddresses(string s) {
        backtracking(s,0,1);
        return ans;
    }
};

这里有一个问题,比如25525511135,如果切割了2,5,5,这时判断最后一个不管怎么切割,都要进入下一层递归,实际上这时候已经不可能完成结果,可以进行剪枝处理。可以根据begin和path大小来剪枝,当path长度为1时,如果剩余长度size-begin>9则剪枝,当path长度为2时,剩余长度size-begin>6则剪枝,当path长度为3时,剩余长度size-begin>3则剪枝。可以将其写入for循环的条件判断中。

for(int i=end;i<=s.size()&&s.size()-begin<=3*(4-path.size());i++)

想这个剪枝用了很长时间,在面试的时候如果能通过就不要细想剪枝了。
标答使用的是左闭右闭区间,增加了一个变量portNum,用于记录逗点个数,直接在原来的字符串上增加逗点分割,空间占用比我们更小。标答也没有完全剪枝,只是粗略的根据总长度剪枝了一下。
标答:

class Solution {
private:
    vector<string> result;// 记录结果
    // startIndex: 搜索的起始位置,pointNum:添加逗点的数量
    void backtracking(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) { // 逗点数量为3时,分隔结束
            // 判断第四段子字符串是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.size() - 1)) {
                result.push_back(s);
            }
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
            if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
                s.insert(s.begin() + i + 1 , '.');  // 在i的后面插入一个逗点
                pointNum++;
                backtracking(s, i + 2, pointNum);   // 插入逗点之后下一个子串的起始位置为i+2
                pointNum--;                         // 回溯
                s.erase(s.begin() + i + 1);         // 回溯删掉逗点
            } else break; // 不合法,直接结束本层循环
        }
    }
    // 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
    bool isValid(const string& s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }
public:
    vector<string> restoreIpAddresses(string s) {
        result.clear();
        if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
        backtracking(s, 0, 0);
        return result;
    }
};

时间复杂度O(3^4),直接根据IP地址的构成计算的时间复杂度。

78.子集

思路:本题要考虑子集的元素个数,在主函数中根据元素个数分类,从0到nums.size()。在回溯函数中,把元素个数k作为一个固定值,当路径长度等于k时返回。其他步骤和前面题目就类似了。剪枝不再过细考虑。时间复杂度O(n^3*2^n),和前面的题目我们多写了一个for循环。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracking(vector<int>& nums,int k,int begin){//k为长度,一直保持不变,begin为开始取的位置
        if(path.size()==k){//路径长度为k,返回
            ans.push_back(path);
            return;
        }
        for(int i=begin;i<=nums.size()-1;i++){
            path.push_back(nums[i]);
            backtracking(nums,k,i+1);//开始选下一个
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        for(int k=0;k<=nums.size();k++){//根据长度分类,0为空集
            backtracking(nums,k,0);
        }
        return ans;
    }
};

看完标答发现可以不用在主函数中写for循环,可以在每一层取元素的时候就直接将其加入ans中,第一层取1个元素,第二层取2个元素。
标答:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracking(vector<int>& nums,int begin){//k为长度,一直保持不变,begin为开始取的位置
        if(begin==nums.size()){//开始位置已经到达末尾
            return;
        }
        for(int i=begin;i<=nums.size()-1;i++){
            path.push_back(nums[i]);
            ans.push_back(path);//每一层遍历都要直接将结果加入ans
            backtracking(nums,i+1);//开始选下一个
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        ans.push_back(path);//先加入一个空集
        backtracking(nums,0);
        return ans;
    }
};

时间复杂度O(n*2^n)。

90.子集II

思路:结合之前used去重的思路,加入上题,即为答案。时间复杂度O(n*2^n)。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracking(vector<int>& nums,int begin,vector<int>& used){
        ans.push_back(path);//先加入自己,这样不用额外添加空集
        if(begin==nums.size()){
            return;
        }
        for(int i=begin;i<nums.size();i++){
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==0){//两个相邻,且前一个未使用过,说明是同一层的,直接跳过
                continue;
            }
            path.push_back(nums[i]);
            used[i]=1;
            backtracking(nums,i+1,used);
            path.pop_back();
            used[i]=0;
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<int> used(nums.size(),0);//先全部初始化为0
        backtracking(nums,0,used);
        return ans;
    }
};

也可以不用used,不过我们初学者最好用,好理解。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值