【劍指Offer】跳臺階

題目描述

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。

解法1

首先對這道題,我們可以通過找規律來解
一隻青蛙可以跳上1級臺階,也可以跳上2兩級臺階
當n = 1時,有1種跳法
當n = 2時,有2種跳法
當n = 3時,有3種跳法
當n = 4時,有5種跳法
當n = 5時,有8種跳法

等等,1,2,3,5,8…,多麼熟悉的數列,斐波那契?
仔細想想當有n(n >= 2)級臺階時,求F(n)
青蛙第一步可以選擇跳上1級臺階,則還剩n - 1級臺階需要跳,即F(n - 1)
青蛙第一步也可以選擇跳上2級臺階,則還剩n - 2級臺階需要跳,即F(n - 2)
則總的跳法F(n) = F(n - 1) + F(n - 2),毫無疑問這就是斐波那契數列的定義了。
關於斐波那契的求解方法,讀者可以參考【劍指Offer】斐波那契數列,包括了遞歸,動態規範,矩陣快速冪多種解法,這裏就不再贅述了。
下面放上以求解斐波那契數列的方式解題的其中一種寫法。

實現代碼

public int jumpFloor(int number)
{
    int f1 = 0;
    int f2 = 1;
    while (number-- > 0)
    {
        f2 = f1 + f2;
        f1 = f2 - f1;
    }
    return f2;
}

排列組合

網上大多數的解法都是以求解斐波那契數列的思路來解題,但其實使用排列組合的思想也可以求解(真相是一開始沒發現是斐波那契,纔想到排列組合的-_-||)。
解題前先簡單介紹下排列組合,摘自百度百科

  • 排列的定義:從n個不同元素中,任取m(m≤n,m與n均爲自然數,下同)個元素按照一定的順序排成一列,叫做從n個不同元素中取出m個元素的一個排列。從n個不同元素中取出m個元素的所有排列的個數,叫做從n個不同元素中取出m個元素的排列數,用符號A(n,m)表示。
    Anm=n(n1)(n2)...(nm+1)=n(n1)(n2)(nm+1)(nm)...21(nm)(nm1)(nm2)...21A{^m_n} = n * (n - 1) * (n-2) * ... * (n-m+1) = \frac{n * (n - 1) * (n - 2) * (n - m + 1) * (n - m) * ... * 2 * 1}{(n - m) * (n - m - 1) * (n - m - 2) * ... * 2 * 1}
    Anm=n!(nm)!A{^m_n} = \frac{n!}{(n - m)!}
    其中n!表示n的階乘,比如4! = 4 * 3 * 2 * 1
    對於Anm=n(n1)(n2)...(nm+1)A{^m_n} = n * (n - 1) * (n-2) * ... * (n-m+1)可以這樣理解,當從n個元素中取第一個元素時,有n種取法。當再取第2個元素時,此時還剩n - 1個元素,有n - 1種取法。當再取第3個元素時,此時還剩n - 2個元素,有n - 2種取法。以此類推,當取到第m個元素時,此時還剩n - m + 1個元素,有n - m + 1種取法。則總取法爲n(n1)(n2)...(nm+1)n * (n - 1) * (n-2) * ... * (n-m+1)
  • 組合的定義:從n個不同元素中,任取m(m≤n)個元素併成一組,叫做從n個不同元素中取出m個元素的一個組合。從n個不同元素中取出m個元素的所有組合的個數,叫做從n個不同元素中取出m個元素的組合數。用符號C(n,m) 表示。
    Cnm=Anmm!=n!(nm)!m!C{^m_n} = \frac{A{^m_n}}{m!} = \frac{n!}{(n - m)!m!}
    排列和組合的區別可以先簡單理解爲,從n個不同元素中,任取m個元素,有多少種取法,如果這m個元素的先後順序不同,就認爲是不同的取法的話,那麼總取法數就是排列數A(n,m)。如果取出來的m個元素無論先後順序如何,都認爲是同一種取法,那麼總取法數就是組合數C(n,m)。舉個栗子,在{1,2,3,4,5}中任取三個數{2, 4, 5}或者{5,2,4},對於排列數來說,它們屬於2種取法,而對於組合數來說它們只算1種取法。
    可以發現,排列和組合的區別在於取出來的這m個元素是否有順序性。當有順序性時,這m個元素有A(m,m)種排列順序,所以排列數A(n,m)除以A(m,m),去除m個元素順序性的影響,得到的就是組合數C(n,m),即
    Cnm=AnmAmm=Anmm!C{^m_n} =\frac{A{^m_n}}{A{^m_m}} =\frac{A{^m_n}}{m!}

解法2

回到本題,青蛙可以跳上1級臺階,也可以跳上2級臺階,
對於n級臺階來說,它最多可以跳 n/2 次 2 級臺階,也就是說總的跳法數是跳0次2級臺階跳法數,1次2級臺階跳法數,2次2級臺階跳法數 … n/2 次2級臺階跳法數的總和
現在問題就變成了求跳指定次數2級臺階的跳法數
假設有n級臺階,指定要跳m次2級臺階,還剩下n - 2m個1級臺階,則青蛙一共要跳n - 2m + m = n - m次才能跳完。即在n-m次跳躍中選擇m次跳2級臺階,有多少種跳法數呢,C(n - m,m)種。(注意這裏不是An - m,m),因爲選出的m次2級臺階沒有順序性)
所以我們求出C(n - 1,1),C(n - 2,2),C(n - 3,3) … C(n - n/2, n/2),然後將其相加,記得再加上1(選0個2級臺階,1種跳法),就是總的跳法數
實現代碼時注意求組合數C(n,m)的算法,需要做一些優化,主要有兩點
先化簡公式
Cnm=Anmm!=n(n1)(n2)...(nm+1)m!C{^m_n} =\frac{A{^m_n}}{m!} =\frac{n * (n - 1) * (n-2) * ... * (n-m+1)}{m!}
1,如果C(n,m)的結果用c#的int類型存儲,則最大值爲231-1,如果按照公式依次計算分子和分母,可能分子或分母會超過231-1,從而造成異常。所以優化爲分子和分母的計算同步進行,當判斷分子除以分母可以除整的時候,就先相除,避免累計數過大。後面的代碼會有對應註釋。
2,可以省略的計算就省略
比如求解C(5,4),其中的4 * 3 * 2,分子分母可以直接相約,本來分子分母的計算都需要連乘4次,現在只需要連乘1次
C54=54324321C{^4_5} = \frac{5 * 4 * 3 * 2}{4 * 3 * 2 * 1}

實現代碼

// 求解cnm
public int c(int n, int m)
{
    int count = m;
    int nf = 1, mf = 1;
    if (m >= n - m + 1)
    {
    	// 對應優化2,只需要連乘n-m次
        count = n - m;
    }
    for (int i =0; i < count; i++)
    {
        nf *= (n - i);
        mf *= (i + 1);
        // 對應優化1,可以除整的時候就先除
        if (nf % mf == 0)
        {
            nf = nf / mf;
            mf = 1;
        }
    }
    return nf / mf;
}

public int jumpFloor(int number)
{
    int ret = 1;
    int count = number / 2;
    for (int i = 1; i <= count; i++)
    {
        ret += c(number - i, i);
    }
    return ret;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章