题目一
842. 将数组拆分成斐波那契序列
给定一个数字字符串 S,比如 S = “123456579”,我们可以将它分成斐波那契式的序列 [123, 456, 579]。
形式上,斐波那契式序列是一个非负整数列表 F,且满足:
0 <= F[i] <= 2^31 - 1,(也就是说,每个整数都符合 32 位有符号整数类型);
F.length >= 3;
对于所有的0 <= i < F.length - 2,都有 F[i] + F[i+1] = F[i+2] 成立。
另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。
返回从 S 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []。
示例 1:
输入:“123456579”
输出:[123,456,579]
示例 2:
输入: “11235813”
输出: [1,1,2,3,5,8,13]
示例 3:
输出: []
解释: 这项任务无法完成。
示例 4:
输入:“0123”
输出:[]
解释:每个块的数字不能以零开头,因此 “01”,“2”,“3” 不是有效答案。
示例 5:
输入: “1101111”
输出: [110, 1, 111]
解释: 输出 [11,0,11,11] 也同样被接受。
提示:
1 <= S.length <= 200
字符串 S 中只含有数字。
递归加回溯
class Solution {
List<Integer> res;
public List<Integer> splitIntoFibonacci(String S) {
res = new ArrayList<>();
return dfs(0, S, 0, 0, 0) ? res : new ArrayList<>();
}
/**
*
* @param cur: 当前指向字符串字符的位置
* @param s:字符串
* @param preLastLast: 前前数字
* @param preLast: 前数字
* @param deep: 当前是第几个数字
* @return 可拆分成斐波那契数列就返回true
*/
public boolean dfs(int cur, String s, int preLastLast, int preLast, int deep){
int len = s.length(); // 字符串长度
if(cur == len) return deep >= 3; // 如果当前的指向的字符已经是字符串长度,字符串不够三个数字就return false
// Integer.MAX_VALUE = 2147483647 最多10位数
for(int i = 1; i <= 10; i++){ // i是控制字符串从cur往后切割出数字的长度的,最多10位数
// 判断切割时是否会超出字符串的范围
// 并且不能以0开头,但是可以是单独的0这样的数字,不能01。0123这样,所以要i > 1
if(cur + i > len || (s.charAt(cur) == '0' && i > 1)) break;
// 截取字符串
String sub = s.substring(cur, cur + i);
long numL = Long.parseLong(sub); // 转成长整型,防止溢出
// 判断是否超出Integer.MAXVALUE
// 或者,deep不是0或1
// 或者大于 前前数字 和 前数字的 和
if(numL > Integer.MAX_VALUE || (deep != 0 && deep != 1 && numL >(preLastLast + preLast))) break;
// 到这说明,截取的字符串转成的数字没有整形溢出,所以直接转成整形
Integer numI = (int) numL;
// 当当前数是第一个数或者第二个数时,或者前两个数之和等于当前截取得到的数
// 都要将当前数放进返回list中,并准备截取下一个数
if(deep == 0 || deep == 1 || numI.equals(preLastLast + preLast)){
res.add(numI);
// 这是下一个数字字符串的起始字符是cur+i
// 下一个数字的lastlast是当前的last,下一个数字的last是当前数字
// 同时让deep加1,表示res中已有几个数字
// 满足条件就往下 递归截取
if(dfs(cur+i, s, preLast, numI, deep+1)) return true;
// 当截取的数字不满足条件时
// 比如 下一个数字无论怎么截取都不等于它的前两个数字之和时,会返回false
// 这时就要使得当前这层递归的当前数字从返回list中移除当前数字
// 这也就是回溯。
res.remove(numI);
/*
举个例子,字符串为:113243,返回为list
对于程序来说,就是
上来第一个for将 1 放进list
第二个for将 1 放进list
这时dfs(2, 113243, 1, 1, 2)
那么开始第三个for,截取3,不满1 + 1 == 3,截取32不行,324,3243都不行,for循环结束,返回false
返回第二个for的中,先将1移除list,再跳到for的下一个循环中
即第二个for的i++为2,截取13,放进list
这时dfs(3, 113243, 1, 13, 2)
开始新的第三个for,截取2,不行截取24不行,截取243不行,for循环结束,返回false
。。。
第二个for截取一遍后都无法满足条件,就返回false
第一个for移除第一个1,进入其下一个i,截取11,再开启新的第二个for循环
。。。
依次类推
*/
}
}
return false;
}
}
参考https://leetcode-cn.com/problems/split-array-into-fibonacci-sequence/solution/java-dfs-jia-jian-zhi-ji-bai-liao-9306-by-capta1n/
官方答案:回溯+剪枝
class Solution {
public List<Integer> splitIntoFibonacci(String S) {
List<Integer> list = new ArrayList<Integer>();
backtrack(list, S, S.length(), 0, 0, 0);
return list;
}
public boolean backtrack(List<Integer> list, String S, int length, int index, int sum, int prev) {
if (index == length) {
return list.size() >= 3;
}
long currLong = 0;
for (int i = index; i < length; i++) {
if (i > index && S.charAt(index) == '0') {
break;
}
currLong = currLong * 10 + S.charAt(i) - '0';
if (currLong > Integer.MAX_VALUE) {
break;
}
int curr = (int) currLong;
if (list.size() >= 2) {
if (curr < sum) {
continue;
} else if (curr > sum) {
break;
}
}
list.add(curr);
if (backtrack(list, S, length, i + 1, prev + curr, curr)) {
return true;
} else {
list.remove(list.size() - 1);
}
}
return false;
}
}
复杂度分析
时间复杂度:
O
(
n
log
2
C
)
O(n \log^2 C)
O(nlog2C)
,其中 n 是字符串的长度,C 是题目规定的整数范围 2^{31}-1。在回溯的过程中,实际上真正进行「回溯」的只有前 2 个数,而从第 3 个数开始,整个斐波那契数列是可以被唯一确定的,整个回溯过程只起到验证(而不是枚举)的作用。对于前 2 个数,它们的位数不能超过
log
10
C
\log_{10} C
log10C
,那么枚举的空间为
O
(
log
2
C
)
O(\log^2 C)
O(log2C)
;对于后面的所有数,回溯的过程是没有「分支」的,因此时间复杂度为 O(n),相乘即可得到总时间复杂度
O
(
n
log
2
C
)
O(n \log^2 C)
O(nlog2C)
空间复杂度:O(n),其中 n是字符串的长度。除了返回值以外,空间复杂度主要取决于回溯过程中的递归调用层数,最大为 n。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/split-array-into-fibonacci-sequence/solution/jiang-shu-zu-chai-fen-cheng-fei-bo-na-qi-ts6c/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。