【dawn·数据结构】string转Fibonacci序列问题(C++)

简要说明:
(1)题目来源LeetCode。
链接:https://leetcode-cn.com/problems/split-array-into-fibonacci-sequence/
(2)由于作者水平限制和时间限制,代码本身可能仍有一些瑕疵,仍有改进的空间。也欢迎大家一起来讨论。
——一个大二刚接触《数据结构》课程的菜鸡留

题目简介

给定一个字符串S,进行若干次分隔,使结果为Fibonacci序列。
假设输入字符串S=“123456579”,可以将它分割成一种Fibonacci序列[123, 456, 579]
对于Fibonacci序列F,要求满足:

● F.length ≥ 3,即满足要求的Fibonacci序列至少要有三项。
● 0 ≤ F[i] ≤ 231-1,即Fibonacci序列的每一项都是非负整数,并且符合32位有符号整数类型。
●F[i+2] = F[i+1] + F[i] ( 0 ≤ i ≤ F.length-2 ),即每一项都等于其前两项之和(如果有的话)。

此外,F[i]不得以“0”开头,即数字02是不合法的,除非这个数字本身是0。
要求返回从字符串S拆分得到的任一组Fibonacci序列块,如果不能拆分则返回[]
参考样例1:

输入: “123456579”
输出: [123, 456, 579]

参考样例2:

输入: “112358130”
输出: []

思路分析

注:仅代表个人思路。

(1) 与上一篇博客解数独问题有些类似,这道题我第一反应到的也是回溯法,即试探每一个可能的拆分,看看是否满足Fibonacci序列的要求。如果满足,可以继续拆分;不满足则分情况讨论,是延长当前块还是回到之前重新拆分。
(2) 由于返回值要求是一个序列,因此使用vector容器来实现。为了方便期间,之前拆分的记忆我也存放在vector中(这在之后会有体现)。
(3) 继续第1点的讨论,满足条件的拆分可以继续向后查看,不满足条件时需要分成两种情况。首先,如果目前拆分块翻译得到的int值大于预期值时,那么就需要回退了;如果小于,说明目前拆分块太短,可以继续粘下一个字符,查看是否新的拆分块翻译出来的int值合乎要求,如此往复。
(4) 那么如何实现回退的步骤?一种方法是放在stack中,但问题又是,stack应该储存什么内容呢?是具体的string还是?在这里,我用vector类似地实现了stack的功能,储存的是上一次操作的下标(因为代码中实际上关于vector的增删只用到了push_back()和pop_back(),这和stack的push()和pop()大同小异)。
(5) 此外还需要注意几点。一个是当试探前两个数即F[0]和F[1]时,是不需要判断是否满足条件的。其次,得到的划分块S未必能翻译成int型,可能越界,也可能以0开始(这在输入要求中被指明是不合法的),就必须要避免可能带来的overflow的问题。1

代码部分2

int STRtoINT(string& S) {  //将string转换为int并返回.
    int co=1;
    int sum=0;
    int i;
    for (i=S.length()-1;i>0;i--) {
        sum+=(S[i]-'0')*co;
        co*=10;
    }
    sum+=(S[i]-'0')*co;  //最后一步单独执行以防co自乘10时产生溢出.
    return sum;
}

bool canSTRtoINT(string& S) {  //判断给定string是否可以转换为int.
    if (S.length()>1&&S[0]=='0')  return false;  //如"02"是不合法的.
    if (S.length()<10)  return true;
    if (S.length()>10)  return false;
    char c[10]={'2','1','4','7','4','8','3','6','4','7'};
    int sign;
    for (int i=0;i<10;i++) {
        sign=S[i]-c[i];
        if (sign>0)  return false;
        if (sign<0)  return true;
    }
    return true;
}

vector<int> splitIntoFibonacci(string S) {
    vector<int> result;
    vector<int> ptr;
    int i=0;
    int end;
    long judge;
    string cut;
    ptr.push_back(0);
    while (i<S.length()) {
        //从ptr记录的尾指针向i截取string, 即可能产生的结果.
        end=ptr.size()-1;
        cut.clear();
        for (int k=ptr[end];k<=i;k++)  cut.push_back(S[k]);
        //如果result已大于等于2个元素,则比较是否符合Fibonacci数列的要求.
        //否则,直接放入作为可能的前两个数的划分. 如果无法组成,之后会回退到这里.
        if (result.size()>1) {
            //判断截取到的string转换int值是否等于前两项之和.
            //分为三种情况,大于、等于和小于.
            //首先还需要判断cut是否可以转换为int值以免越界. 不能的话就需要直接回退.
            if (!canSTRtoINT(cut))  judge=1;  //直接设judge=1, 即需要回退.
            else {
               judge=STRtoINT(cut)-result[result.size()-1];
               judge-=result[result.size()-2];             
            }
            if (judge==0) {  //相等的情况
                //直接放入result,指针后移.
                result.push_back(STRtoINT(cut));
                ptr.push_back(++i);
            }
            else if (i==S.length()-1||judge>0) {  //需要回退的情况(包括大于)
                //说明需要回退. 如果i已经到最后一位下标了且不满足judge==0,那么也需要回退.
                //在回退时,需要更改int的值,即下一次截取的终点.
                //同时还会有一种极端情况,如果ptr单元素,说明无法截取到Fibonacci序列, 此时返回空vector.
                if (ptr.size()==1) {
                    ptr.pop_back();
                    return ptr;  //此时ptr即空vector.
                }
                else {
                    i=ptr[ptr.size()-1];
                    ptr.pop_back();
                    result.pop_back();
                }
            }
            else {  //小于的情况,那么指针仍然需要后移.
                ++i;
            }
        }
        else {  //result元素数小于2,只需要考虑加入元素
            //此时考虑到有两种需要回退的情况.
            //第一种是i已经到了尾部.
            //第二种是cut无法转换为int类型,同样需要回退.
            //回退时考虑到ptr.size(),判断是否确认必找不到Fibonacci序列.
            if (i==S.length()-1||!canSTRtoINT(cut)) {  //无法转换为int,此时需要回退.
                if (ptr.size()==1) {
                    ptr.pop_back();
                    return ptr;  //此时返回空vector.
                }
                else {  //操作同回退操作.
                    i=ptr[ptr.size()-1];
                    ptr.pop_back();
                    result.pop_back();
                }
            }
            else {
                result.push_back(STRtoINT(cut));
                ptr.push_back(++i);   
            }
        }
    }
    return result;
}

●关于这段代码,其实如果空说也很难说,因为这也是我几乎磨了一个小时有余磨出来的代码(虽然当时在参加一个活动并且我甚至有种它在干扰我的感觉【苦笑】),很多地方也已经有些抽象了。可以在纸上或白板上画一张图,模拟一下这个步骤。我在调试bug的时候,也是一步步画来确定一些变量到底需要设为几、是先出vector后改值还是先改值之类的。
●为什么ptr不用stack的原因,我给自己找了个好理由:(其实有想过但是后来没有改)在一些返回空vector的情况下,这样只需要操作ptr即可。

改进空间

(1) 说句实话目前我在LeetCode上做题,消耗时间的测量似乎不太精准,偶尔蹦出个0ms的反馈。不过这道题内存能超过近70%的用户,已经是突破个人到目前的最高记录了。(希望以后还能破)不过也是找到了些问题。
(2) 承接上文,事实上在具体操作时,不出现太奇怪的问题的话,ptr向量的长度总是恰等于result的长度加1,因此在外层while循环体的第一句if分支处内部的判断ptr.length()==1与否是多余的。
(3) 代码中的judge部分为了方便起见使用了long型(因为可能前两项都比较大导致使用int时会溢出),可能会有更好的解决方案。坦率地说,这类溢出的问题是我个人感到挺头疼的问题,所幸单单一个long和int内存使用差距并不大。
(4) 还是那句话,总会有一些更好的算法及数据结构。

补充部分

(1) 代码可以修改成输出所有可能的合法的Fibonacci序列划分,由于时间原因我并没有改。
(2) 暂时想不到什么了。说句实话,这道题的难度比之前做到的那些每日一题都更困难些,所花费的时间也比前几天更多了。不过也正是如此,题目难度在加大,我也并非在原地踏步。期待之后的学习。


  1. 实际上我也总是会忽略这一点,之前也有做过一道关于string转int的题目,也是在调试时根据反馈在想起这一点。虽说接触时间尚短,这个问题还是需要解决和克服的。 ↩︎

  2. 代码部分只关注重点代码,不关心具体的输入方法和输出方法(即如何从vector转化为一整数列)。 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值