动态规划之"可作为Fibonacci数列前缀的非空子序列个数 [微软面试题]"

问题定义
给定一个序列{an}, 求可作为fibonacci数列的非空子序列个数。 最后结果 mod 1,000,000,007!
输入
One line with an integer n.
Second line with n integers, indicating the sequence {an}.
For 30% of the data, n<=10.
For 60% of the data, n<=1000.
For 100% of the data, n<=1000000, 0<=ai<=100000.

fibonacci 数列:
F1 = 1, F2 = 1
Fn = Fn-1 + Fn-2, n>=3

样例输入
2 1 1 2 2 3
样例输出
7

解题思路

为区分样例中的每个元素, 假设重写样例为 21 12 13 24 25 36. 下标代表其在序列中索引号.
部分fibonacci数列: 1, 1, 2, 3, 5, 8, .....
上述例子中,符合条件的子序列:
{12},
{13},
{12 13},
{12 13 24},
{12 13 25},
{12 13 25 36},
{12 13 24 36}

首先明白子序列的含义: 子序列是指从原序列中任意选定一些项,保持其相对顺序不变,得到的序列为原序列的子序列。
也就是说,对于当前枚举的原序列第i项,可以和原序列第1~i-1项中任意一个子序列构成一个新的子序列。
本题中要求构成的序列必须满足斐波拉契数列的前缀,所以在第1到第i-1项中符合要求的子序列一定满足下面这个条件:
假设原序列中第1到第i-1项的某个子序列{s1 ,..., sm}是Fibonacci数列的前缀, 该子序列和原序列第i项构成新的新子序列如果是Fibonacci子序列,那么a[i], sm必须是Fibonacci数列的相邻项。

因此,不用考虑原序列中的非Fibonacci数。
此外,还需要标记出原序列中每一个留下来的项对应于斐波拉契数列的第几项

动态规划思想:
f(i,j) 表示原数列中前i个数中,以Fibonacci 数列中第 j项 (从下标1开始) 作为子序列结尾元素的子序列个数.

假设枚举到原序列的第 i 项,a[i] 是Fibonacci序列的第 j 项 (a[i]>1), 则
边界条件:f(i, 0)=1, f(i, j)=0; i 取 {0, 1, 2, ..., n}中的值;j 取 {1, 2, ..., n}中的值
递推公式:f(i, j) = f(i-1, j-1) + f(i-1, j)

原序列前 i 项中,以Fibonacci序列第j项作为结尾元素的子序列的构成方式:

  1. 当原序列的第i个元素 a[i] 在其前的元素里没有出现过时: 原序列的第 i 项,与原序列中前 i-1 个元素以Fibonacci序列的第 j-1 项构成的子序列结合,因此 有 f(i-1, j-1)个是Fibonacci前缀新子序列。
  2. 当原序列的第i个元素 a[i] 在其前的元素里出现过时:假设在原序列前 i-1 个元素中,出现过和 a[i] 相等的元素 a[i'],其中 i' < i. 由于a[i]是Fibonacci序列的第 j 项,a[i']也是Fibonacci序列的第 j 项,因此 f(i-1, j) 也表示原序列前 i 个元素以a[i]作为结束元素的符合要求的子序列的个数。如果在前i-1个元素中不存在和当前元素a[i]相等的元素,该项为0.

Fibonnaci数列中前两项具有相同值,因此需要特殊处理。
a[i] == 1时:
f(i, 1) = f(i-1, 1)
f(i, 2) = f(i-1, 2) + f(i-1, 1)

假设序列的长度为n, 则 可作为Fibonacci数列前缀的非空子序列个数: f(n,1)+...+f(n,K) , K 是最大fabonacci数在fabonacci数列中的下标 (从1开始)。【按照题目要求,记得 在计算过程中要记得MOD 100,000,007】

题目中给定原序列中数字最大为100,000,假设斐波拉契数列中小于等于100,000的有K项, (可提前计算出K=25)
则该递推公式的时间复杂度为O(KN)

时空优化方案

在上面的递推公式中,我们可以发现在转移过程中,f(i, j)中大部分数都是直接继承的f(i-1, j)。同时f(i, . )只会在枚举到第 i 项的时候更新,只会在计算第i+1项时用到。
因此,我们可以只使用一维数组存储解 f(j).

在更新f(j)时,需要注意: 当 a[i]==1时
要先更新作为斐波拉契数列第2项的1,再计算作为斐波拉契数列第1项的1。
这是因为f(1)更新后会影响到f(2)的计算,而反过来不会。

优化后,时间复杂度:O(n), 空间复杂度:O(n)

代码如下

// 优化后的算法
```java
import java.io.BufferedInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    static final int Max=1000010;
    static int[] fibs=new int[Max];
    static Map<Integer,Integer> indFibs = new HashMap<>();
    static void init(){
        fibs[0]=fibs[1]=1;
        for(int i=2; ; ++i){
            fibs[i]=fibs[i-2]+fibs[i-1];
            indFibs.put(fibs[i], i+1);// 记录fib数在序列中的位置
            if(fibs[i]>=Max)
                break;
        }
        indFibs.put(1, 2);
    }
    static void display(int[] f){
        for(int i=0; i<f.length; ++i){
            System.out.print(f[i] +" ");
        }
        System.out.println();
    }
    static void solver(){
        init();
        int[] f = new int[26];
        f[0]=1;
        final int M = 1000000007;
        Scanner scan = new Scanner(new BufferedInputStream(System.in));
        int n = scan.nextInt();
        for(int i=0; i<n; ++i){
            int x = scan.nextInt(); 
            if(indFibs.containsKey(x)){
                int j = indFibs.get(x);
                f[j] = (f[j-1]+f[j]) % M;
                if(x==1){
                    f[1]=(f[1]+f[0]) % M;
                }
            }
        }
        scan.close();
        int ans = 0;
        for(int i=1; i<=25; ++i){
            ans = (ans+f[i])%M;
        }
        System.out.println(ans);
    }
    public static void main(String[] args) {
        solver();
    }
}

题目来源:hihoCoder

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 1.把二元查找树转变成排序的双向链表 题目: 输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。 要求不...
    曲终人散Li阅读 3,287评论 0 19
  • 一年级语文上册生字表 生字表一(共400字) 啊(ā)爱(ài)安(ān)岸(àn)爸(bà)八(bā)巴(bā)...
    meychang阅读 2,755评论 0 6
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,621评论 0 89
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,719评论 0 33
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399