每日算法刷题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+1pre = 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+1pre2=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+1tmp.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.给定两个整数 n 和 k,返回范围 [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;}// 不选iddfs(id + 1, n, k);// 选idtmp.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) {// 当前待选idint d = k - tmp.size(); // 还要选d个if (d == 0) {res.push_back(tmp);return;}// 不选id,可选[id+1,n]共n-id个,需满足n-id>=dif (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;}
};
