循环和递归-Python刷题笔记

斐波那契数列

大家都知道斐波那契数列,
现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39

  1. 用列表循环增加 fibonacci series 的下一项, 最后pop()第n项
# -*- coding:utf-8 -*-
class Solution:

    def Fibonacci(self, n):
        # write code here
        a = [0,1,1]
        if n <3:
            return a[n]
        for i in range(3,n+1):
            a.append(a[i-1]+a[i-2])
        return a.pop()

tips】:

  • 题目要求有第0项为0
  • for loop 边界
  1. 用a,b分别存前两项,循环a,b = b,a+b推到第n项直接输出。
# -*- coding:utf-8 -*-
class Solution:

    def Fibonacci(self, n):
        # write code here
        a,b = 0,1
        while n > 0:
            a,b = b,a+b
            n -= 1
        return a

tips】:

  • 理解 a,b = b,a+b
  • return a / return b ?

跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

  1. 倒推来分解问题,最后跳法个数可以分解为倒数两级是怎么跳的, 也就是说f(n) = f(n-1) + f(n-2)。 确定是斐波那契数列。 解法同上
# -*- coding:utf-8 -*-
class Solution:
    def jumpFloor(self, number):
        # write code here
        '''
        f(n) = f(n-1) + f(n-2)
        f(1) = 1
        f(2) = 2
        '''
        res = [None,1,2] #None为占位
        if number < 3:
            return res[number]
        for i in range(3,number+1):
            res.append(res[i-1] + res[i-2])
        return res[number]

tips】:

  • 分析问题场景是关键步骤,从结果开始倒推
  • 没提第0项所以我用了None占个位置,写别的也可以
  • for loop 边界
  1. a,b = b,a+b解法,同上
# -*- coding:utf-8 -*-
class Solution:
    def jumpFloor(self, number):
        # write code here
        a = 1
        b = 1
        for i in range(number):
            a,b = b,a+b
        return a

tips】:

  • 理解 a,b = b,a+b
  • return a / return b ?

变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

从最初状态尝试推算

        f(1) = 1
        f(2) = f(2-1) + f(2-2)  //f(2-2)表示第一次跳2阶之后有f(2-2)种跳法。
        f(3) = f(3-1) + f(3-2) + f(3-3) 
        ...
        f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n)

简化

        f(n-1) = f(0) + f(1) + f(2) + f(3) + ... + f((n-1)-1) 
               = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
        f(n) = 2*f(n-1)

总结

                | 0       ,(n=0 ) 没台阶
        f(n) =  | 1       ,(n=1 ) 一个台阶为1种
                | 2*f(n-1) = 2^(n-1),(n>=2)

# -*- coding:utf-8 -*-
class Solution:
    def jumpFloorII(self, number):
        # write code here
        if number <= 0:
            return 0
        else:
            return pow(2, number - 1)

tips】:

  • 因为每次可跳台阶数不定,从头开始推公式
  • 简化过程是关键

矩形覆盖

我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,总共有多少种方法?
比如 n=3 时,2
3的矩形块有3种覆盖方法:

n=3

和跳台阶类似,把n分成不同排列组合的2和1

f1 -- 1
f2 -- 2
f3 -- 3
f4 -- 5 -- f3 + f2

fibonacci!

class Solution:
    def rectCover(self, number):
        # write code here
        if number==0:
            return 0
        elif number==1:
            return 1
        a,b=1,1
        while number>1:
            a,b=b,a+b
            number-=1
        return b

tips】:

  • return b

斐波那契字符数

Tom发现了一种神奇的字符串-斐波那契字符串,
定义 f[1]=0 , f[2]=1 ,

对于所有的i>2都有f[i]=f[i-2]+f[i-1],其中“+”代表拼接,
比如01+10=0110,
现在对于字符串f[n],请判断f[n]的第k项是0,还是1。
输入两个数字n和k(n<=300,k<=1000000000)。
输出f[n]的第k位,如果k大于f[n]的位数,则输出-1。

  1. 直接,超时超内存

自底向上地构建出从第一个字符串f[1]直到最后的f[n],然后查看f[n]的第k项是0还是1。
由于斐波那契数递增速度非常快,当n较大时,这种暴力拼接法无法完成。

  1. 充分利用斐波那契数列的性质,自顶向下对问题逐步剪枝,定位需要判断的数字位置。

比如,对输入n=19, k=98:
先算出n=19对应的字符串长度len,也就是斐波那契数列的第19项。考虑到k是int类型,故可以先算出n=1~47对应的斐波那契数组成的数组(n=47时,斐波那契数大于2^31-1,无需再提前计算更多的斐波那契数),然后直接在计算好的斐波那契数组中取出第19项。
找到n-2=17,n-1=18对应的斐波那契字符串长度(也就是第17/18个斐波那契数)len1,len2。同样也是在提前计算好的斐波那契数组中找到。
对于k=98,如果k>len1,说明要找的数字在n=18对应的斐波那契字符串中,也就是n=19对应的斐波那契字符串的后半部分;如果k<=len1,说明要找的数字在n=17对应的斐波那契字符串中,也就是n=19对应的斐波那契字符串的前半部分。
根据判断结果,将n赋值为n-2或n-1,同时将k赋值为k或(k-len1),完成剪枝。回到第1步,递归向下搜索。直到n=1或者2,这时k也变为1,定位完成,结束递归,直接返回结果。
进一步优化:
当输入的n > 47时,斐波那契字符串的长度超出了int型变量k的表示范围。需要提前进行推断剪枝。你能根据k的int表示范围(小于等于2^31-1)这一特性,对n较大的那些用例进行优化么?

  1. 递归

这个问题可以使用递归的方法来求解。
题目中给出了字符串拼接的规律:f[i]=f[i-2]+f[i-1]。可以看出对于第i个字符串,它的前半部分属于第i-2个字符串,后半部分属于第i-1个字符串。

这样,当给定具体的n和k时,我们可以首先判断k位于第n个字符串的前半部分还是后半部分,然后递归的把k传递给第n-2或n-1个字符串。

计算过程:

为了判断属于前半部分还是后半部分,需要计算每一个字符串的长度。
一个len数组。每个位置为对应字符串的长度。
len[1] = len[2] = 1;
len[i] = len[i-2]+len[i-1];

如果k > len[n],则输出-1。
设置递归函数 int find(int i, int k);这个函数返回第i个字符串中k个位置的字符
判断k在前半部分还是后半部分。
a) K <= len[i-2] 前半部分,调用find(i-2, k)
b) K > len[i-2] 后半部分,调用find(i-1, k-len[i-2])

上面的计算在第一步时会遇到问题,当n大于几十的时候,len就会超出int的范围。
对这个问题的解决基于对递归过程的观察,第4步a)中k始终是不变的,i的奇偶性也保持不变。所以在计算第1步len时可以提前停止,当发现len[i]>=k且i与n的奇偶性相同时。第4步中的递归起始也可以使用第i个字符串开始,而不是第n个。
时间复杂度O(2*n)
空间复杂度O(n)


Reference

[1] 牛客网在线编程 - 剑指Offer

你可能感兴趣的:(循环和递归-Python刷题笔记)