每日算法32_2020-12-08

来源:题库 - 力扣 (LeetCode) (leetcode-cn.com)。侵删。

题目一

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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值