每日算法刷题Day81:10.29:leetcode 回溯5道题,用时2h

3. 1593. 拆分字符串使唯一子字符串的数目最大(中等,学习剪枝思想)

1593. 拆分字符串使唯一子字符串的数目最大 - 力扣(LeetCode)

思想

1.给你一个字符串 s ,请你拆分该字符串,并返回拆分后唯一子字符串的最大数目。

字符串 s 拆分后可以得到若干 非空子字符串 ,这些子字符串连接后应当能够还原为原字符串。但是拆分出来的每个子字符串都必须是 唯一的

注意:子字符串 是字符串中的一个连续字符序列。

2.因为是求最大值,所以到当前位置可以得到最大拆分字符串长度,若加上已有长度仍小于已知最大值,则直接返回(剪枝)

代码
复制代码
class Solution {
public:
    int n;
    set<string> st;
    int res = 0;
    void dfs(int id, int start, string& s) {
        if (id == n) {
            if (start == n) {
                res = max(res, (int)st.size());
            }
            return;
        }
        if (id < n - 1)
            dfs(id + 1, start, s);
        // [start,id]
        string tmp = s.substr(start, id - start + 1);
        if (!st.count(tmp)) {
            st.insert(tmp);
            dfs(id + 1, id + 1, s);
            st.erase(tmp);
        }
    }
    int maxUniqueSplit(string s) {
        n = s.size();
        dfs(0, 0, s);
        return res;
    }
};

剪枝:

复制代码
// 最多拆[start,n)共n-start个
if (n - start + st.size() <= res)
	return; // 剪枝
4. 1849. 将字符串拆分为递减的连续值(中等,学习字符串局部求和最好不用stoi,而是逐元素算,并判断溢出)

1849. 将字符串拆分为递减的连续值 - 力扣(LeetCode)

思想

1.给你一个仅由数字组成的字符串 s

请你判断能否将 s 拆分成两个或者多个 非空子字符串 ,使子字符串的 数值降序 排列,且每两个 相邻子字符串 的数值之 等于 1

  • 例如,字符串 s = "0090089" 可以拆分成 ["0090", "089"] ,数值为 [90,89] 。这些数值满足按降序排列,且相邻值相差 1 ,这种拆分方法可行。
  • 另一个例子中,字符串 s = "001" 可以拆分成 ["0", "01"]["00", "1"]["0", "0", "1"] 。然而,所有这些拆分方法都不可行,因为对应数值分别是 [0,1][0,1][0,0,1] ,都不满足按降序排列的要求。
    如果可以按要求拆分 s ,返回 true ;否则,返回 false
    子字符串 是字符串中的一个连续字符序列。
代码
复制代码
class Solution {
public:
    typedef long long ll;
    int n;
    bool res = false;
    ll pre = LLONG_MAX;
    void dfs(int id, int start, string& s, ll cnt) {
        if (res)
            return;
        // 剪枝
        if (id == n) {
            if (start == n && cnt >= 2) { // 全部拆分才更新答案
                res = true;
            }
            return;
        }
        if (id < n - 1) // 最后一个必须拆
            dfs(id + 1, start, s, cnt);
        // [start,id]
        ll val = 0;
        for (int i = start; i <= id; ++i) {
            if (val >= pre || val > LLONG_MAX / 10) // 不能溢出
                return;                        // 剪枝
            val = val * 10 + s[i] - '0';
        }
        if (pre == LLONG_MAX || pre - val == 1) { // 判断该字符串是否符合条件
            ll tmpPre = pre;
            pre = val;
            dfs(id + 1, id + 1, s, cnt + 1); // start更新为id+1
            pre = tmpPre;                    // 回溯
        }
    }
    bool splitString(string s) {
        n = s.size();
        dfs(0, 0, s, 0);
        return res;
    }
};
5. 306.累加数(中等)

306. 累加数 - 力扣(LeetCode)

思想

1.累加数 是一个字符串,组成它的数字可以形成累加序列。

一个有效的 累加序列 必须 至少 包含 3 个数。除了最开始的两个数以外,序列中的每个后续数字必须是它之前两个数字之和。

给你一个只包含数字 '0'-'9' 的字符串,编写一个算法来判断给定输入是否是 累加数 。如果是,返回 true ;否则,返回 false

**说明:**累加序列里的数,除数字 0 之外,不会 以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。

代码
复制代码
class Solution {
public:
    typedef long long ll;
    bool res;
    int n;
    ll pre1=LLONG_MIN,pre2=LLONG_MIN;
    void dfs(int id, int start, string& s, int cnt) {
        if(res) return;
        if(s[start]=='0' && id!=start)  return; // 剪枝0开头
        if (id == n) {
            if (start == n && cnt>=3) { // 全部拆分才更新答案 (cnt可以更精确判断拆分了几个)
                res = true;
            }
            return;
        }
        if (id < n - 1) // 最后一个必须拆
            dfs(id + 1, start, s, cnt);
        ll val=0;
        for(int i=start;i<=id;++i){
            if(val>LLONG_MAX/10 || (pre1!=LLONG_MIN && pre2!=LLONG_MIN && val>pre1+pre2)) return; // 溢出判断
            val=val*10+s[i]-'0';
        }
        if (pre1==LLONG_MIN || pre2==LLONG_MIN || val==pre1+pre2) { // 判断该字符串是否符合条件
            ll tmpPre1=pre1;
            pre1=pre2;
            pre2=val;
            dfs(id + 1, id + 1, s, cnt+1); // start更新为id+1
            pre2=pre1;
            pre1=tmpPre1;
        }
    }
    bool isAdditiveNumber(string num) {
        n=num.size();
        dfs(0,0,num,0);
        return res;
    }
};
6. 842. 将数组拆分成斐波那契数列(中等)

842. 将数组拆分成斐波那契序列 - 力扣(LeetCode)

思想

1.给定一个数字字符串 num,比如 "123456579",我们可以将它分成「斐波那契式」的序列 [123, 456, 579]

形式上,斐波那契式 序列是一个非负整数列表 f,且满足:

  • 0 <= f[i] < 2^31 ,(也就是说,每个整数都符合 32 位 有符号整数类型)
  • f.length >= 3
  • 对于所有的0 <= i < f.length - 2,都有 f[i] + f[i + 1] = f[i + 2]
    另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。
    返回从 num 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []
    2.题目说2^31,是int的上界,判断溢出得用INT_MAX,不能用LLONG_MAX
代码
复制代码
class Solution {
public:
    typedef long long ll;
    vector<int> res, tmp;
    int n;
    void dfs(int id, int start, string& s, int cnt) {
        if (!res.empty())
            return;
        if (s[start] == '0' && start != id)
            return;
        if (id == n) {
            if (start == n &&
                cnt >= 3) { // 全部拆分才更新答案 (cnt可以更精确判断拆分了几个)
                res = tmp;
            }
            return;
        }
        if (id < n - 1) // 最后一个必须拆
            dfs(id + 1, start, s, cnt);
        // 提取字符串局部和
        ll val = 0;
        for (int i = start; i <= id; ++i) {
            if (val > INT_MAX / 10 ||
                (tmp.size() >= 2 &&
                 val > 1LL * tmp[tmp.size() - 1] + 1LL * tmp[tmp.size() - 2]))
                return; // 溢出判断
            val = val * 10 + s[i] - '0';
        }
        if (tmp.size() < 2 ||
            val == 1LL * tmp[tmp.size() - 1] +
                       1LL * tmp[tmp.size() - 2]) { // 判断该字符串是否符合条件
            tmp.push_back(val);
            dfs(id + 1, id + 1, s, cnt + 1); // start更新为id+1
            tmp.pop_back();
        }
    }
    vector<int> splitIntoFibonacci(string num) {
        n = num.size();
        dfs(0, 0, num, 0);
        return res;
    }
};

四、组合型回溯

1.套路

1.有个数上的约束 。也算作子集型回溯。

2.递归条件变成剩余数量是否为0,不选的时候判断剩下最多可选的是否够来剪枝,比递归函数刚开始判断更好些(要进入一次递归函数)

2.题目描述
3.学习经验
1. 77.组合(中等,学习另一种写法)

77. 组合 - 力扣(LeetCode)

思想1

1.给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

代码

递归进入后刚开始剪枝(常规)

复制代码
class Solution {
public:
    vector<vector<int>> res;
    vector<int> tmp;
    void dfs(int id, int n, int k) {
        // [id,n]共n-id+1个可选,已选tmp.size(),还差k-tmp.size()个待选
        if (n - id + 1 < k - tmp.size())
            return;
        if (tmp.size() == k) {
            res.push_back(tmp);
            return;
        }
        // 不选id
        dfs(id + 1, n, k);
        // 选id
        tmp.push_back(id);
        dfs(id + 1, n, k);
        tmp.pop_back();
    }
    vector<vector<int>> combine(int n, int k) {
        dfs(1, n, k);
        return res;
    }
};

另一种写法(在递归进入前判断剪枝更好):

复制代码
class Solution {
public:
    vector<vector<int>> res;
    vector<int> tmp;
    void dfs(int id, int& n, int& k) {
        // 当前待选id
        int d = k - tmp.size(); // 还要选d个
        if (d == 0) {
            res.push_back(tmp);
            return;
        }
        // 不选id,可选[id+1,n]共n-id个,需满足n-id>=d
        if (n - id >= d)
            dfs(id + 1, n, k);
        tmp.push_back(id);
        dfs(id + 1, n, k);
        tmp.pop_back();
    }
    vector<vector<int>> combine(int n, int k) {
        dfs(1, n, k);
        return res;
    }
};
相关推荐
大千AI助手6 小时前
Householder变换:线性代数中的镜像反射器
人工智能·线性代数·算法·决策树·机器学习·qr分解·householder算法
Mr.H01276 小时前
迪杰斯特拉(dijkstra)算法
算法
南方的狮子先生6 小时前
【数据结构】从线性表到排序算法详解
开发语言·数据结构·c++·算法·排序算法·1024程序员节
派大星爱吃猫6 小时前
快速排序和交换排序详解(含三路划分)
算法·排序算法·快速排序·三路划分
焜昱错眩..7 小时前
代码随想录第四十八天|1143.最长公共子序列 1035.不相交的线 53. 最大子序和 392.判断子序列
算法·动态规划
AI妈妈手把手7 小时前
YOLO V2全面解析:更快、更准、更强大的目标检测算法
人工智能·算法·yolo·目标检测·计算机视觉·yolo v2
极客智造7 小时前
编程世界的内在逻辑:深入探索数据结构、算法复杂度与抽象数据类型
数据结构·算法·数学建模
好好学习啊天天向上7 小时前
多维c++ vector, vector<pair<int,int>>, vector<vector<pair<int,int>>>示例
开发语言·c++·算法
MicroTech20258 小时前
MLGO微算法科技 LOP算法:实现多用户无线传感系统中边缘协同AI推理的智能优化路径
人工智能·科技·算法