代码随想录算法训练营打卡Day28 | LeetCode93 复原 IP 地址、LeetCode78 子集、LeetCode90 子集II

摘要

  • 分割问题首先要明确如何对字符串分割,构造树形结构来分析问题。
  • 子集问题需要收集树形结构中的每个节点,所以要将收集结果的语句放在递归终止条件之前。

LeetCode93 复原 IP 地址

93. 复原 IP 地址 - 力扣(Leetcode)

  • 这也是一道分割字符串的问题,首先要确定分割的规则,先确定每次分割出的子串应该符合的规则:

    • 每次分割出的子串的长度最多为3
    • 子串中不能含有前导0
    • 子串中不能含有非数字的字符
    • 子串对应的整数数值在[0, 255]
  • 用以上的子串规则作为剪枝条件,不难写出以下函数

bool isValid(const string& s) {
    if (s.size() > 3) return false;// 长度最多为3
    if (s[0] == '0' && s.size() > 1) return false;// 不能含有前导0
    int temp = -1;
    try {
        temp = stoi(s);
    }
    catch (invalid_argument) {
        return false;// 不能含有非数字的字符
    }
    if (temp < 0 || temp > 255) return false;// 子串对应的整数数值应在[0, 255]中
    return true;
}
  • 确定了剪枝函数,再套用回溯法的递归函数模板
    • 递归函数的终止条件:整个输入字符串被分割完成且分割出的子串数量恰好为4,说明应收集答案;分割出的子串数量大于4,说明分割出的子串不能组合出合法的IP地址,应直接返回。
    • 递归函数的参数列表:输入的字符串s,结果集res,当前分割出的子串列表cur,下一次分割的起始位置start,当前分割出的子串数量part
    • 单层递归的逻辑:start作为当前子串的起始位置,i作为当前子串的终止位置,左闭右闭区间,来枚举在本层可能分割出的子串,并通过检查分割出的子串是否能组成合法IP进行剪枝。

完整的题解代码如下,采用vector<string>来暂存子串,便于回溯(直接pushpop即可,比字符串操作方便一些,但效率好像不如直接操作字符串)。注意part初始化为0,对应的是cur中的子串数量。

class Solution {
public:
    bool isValid(const string& s) {
        if (s.size() > 3) return false;
        if (s[0] == '0' && s.size() > 1) return false;
        int temp = -1;
        try {
            temp = stoi(s);
        }
        catch (invalid_argument) {
            return false;
        }
        if (temp < 0 || temp > 255) return false;
        return true;
    }
    void backtracking(const string& s, vector<string>& res,
                    vector<string>& cur, int start, int part)
    {
        if (part > 4) return;
        if (start >= s.size()) {
            if (part != 4) return;
            string temp;
            for (auto& iter : cur) {
                temp += iter;
                if (--part) temp += ".";
            }
            res.push_back(temp);
            return;
        }
        for (int i = start; i < s.size(); i++) {
            if (isValid(s.substr(start, i - start + 1))) {
                cur.push_back(s.substr(start, i - start + 1));
                backtracking(s, res, cur, i + 1, part + 1);
                cur.pop_back();
            }
        }
    }
    vector<string> restoreIpAddresses(string s) {
        vector<string> res;
        vector<string> cur;
        backtracking(s, res, cur, 0, 0);// part 初始化为 0
        return res;
    }
}; 

直接操作字符串的版本,注意part要初始化为1,直接操作字符串是以插入.的形式来分割的,在未插入.时,将整个串看作一个子串,所以part初始化为1

class Solution {
public:
    bool isValid(const string& s) {
        if (s.size() > 3) return false;
        if (s[0] == '0' && s.size() > 1) return false;
        int temp = -1;
        try {
            temp = stoi(s);
        }
        catch (invalid_argument) {
            return false;
        }
        if (temp < 0 || temp > 255) return false;
        return true;
    }
    void backtracking(string& s, vector<string>& res,
                    int start, int part)
    {
        if (part > 4) return;
        if (part == 4) {
            if (isValid(s.substr(start))) res.push_back(s);// 判断最后一个子串是否合法
            return;
        }
        for (int i = start; i < s.size(); i++) {
            if (isValid(s.substr(start, i - start + 1))) {
                s.insert(s.begin() + i + 1, '.');
                backtracking(s, res, i + 2, part + 1);
                s.erase(s.begin() + i + 1);
            }
        }
    }
    vector<string> restoreIpAddresses(string s) {
        vector<string> res;
        vector<string> cur;
        backtracking(s, res, 0, 1);// part 初始化为 1
        return res;
    }
}; 

LeetCode78 子集

78. 子集 - 力扣(Leetcode)

  • 这道题目与之前的区别就是要收集树形结构中的每个节点进入结果集,所以关键在于收集结果的语句要放在递归终止的语句之前。
  • [1, 2, 3]为例,其子集的构造树如下图,回溯法能遍历到每个节点,所以只需要每次递归都收集结果即可。

完整的题解代码如下

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

LeetCode90 子集II

90. 子集 II - 力扣(Leetcode)

  • 本题除了要在回溯法的树形结构中的每个节点收集结果外,还要做“树层去重”。

  • [1, 2, 2]为例,其子集构造的树形结构如下

  • 在这里复习“树枝去重”和“树层去重”
    • 树枝去重:为了防止使用相同的元素,每次向下搜索时,可用元素的起始位置start应向右移一位。
    • 树层去重:结果集中出现重复组合的原因不是使用了相同的元素,而是在树的同一层中使用值相同的不同元素。观察子集构造的树形结构,因为树枝去重,所以有多个值相同的元素出现时,选取其中第一个元素之后得到的组合就包含了在本层中选取该元素的所有可能。例如上面的第二层的第一个1,从第二层的第一个1开始搜索得到的组合,包含了从第二层的第二个1开始搜索得到的所有组合。所以,只需要保留从第一个1搜索得到的结果即可完成树层去重。
    • 对原数组进行排序的意义就在于让值相同的元素在数组中连续分布,便于去重。

完整的题解代码如下

class Solution {
public:
    void backtracking(vector<int>& nums, vector<vector<int>>& res,
                    vector<int>& cur, int start)
    {
        res.push_back(cur);
        if (start >= nums.size()) return;
        for (int i = start; i < nums.size(); i++) {
            if (i - start && nums[i - 1] == nums[i]) continue;
            cur.push_back(nums[i]);
            backtracking(nums, res, cur, i + 1);
            cur.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> cur;
        sort(nums.begin(), nums.end());
        backtracking(nums, res, cur, 0);
        return res;
    }
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352