(說明:本博客中的題目、題目詳細說明及參考代碼均摘自 “何海濤《劍指Offer:名企面試官精講典型編程題》2012年”)
題目
1. 寫一個函數,輸入 n, 求斐波那契(Fibonacci)數列的第 n 項。斐波那契數列的定義如下:
2. 一只青蛙一次可以跳上 1 級台階,也可以跳上 2 級。求該青蛙跳上一個n級的台階總共有多少種跳法?
3. 一只青蛙一次可以跳上 1 級台階,也可以跳上 2 級,...... ,也可以跳上n級,此時該青蛙跳上一個 n 級的台階共有多少種跳法?
4. 用 2x1 (圖 2.13 的左邊)的小矩形橫着或者豎着去覆蓋更大的矩形。請問用 8 個 2x1 小矩形無重疊地覆蓋一個 2x8 的大矩形(圖 2.13 的右邊),總共有多少種方法?
題目解析
本博客提及到 4 個題目:題目 1 直接給出斐波那契數列的定義,可采用多種算法實現,這些算法思想將在 “算法設計思想“ 部分介紹;題目 2 和題目 4 的本質上解決的還是斐波那契數列第 n 項的計算問題,即題目 1;題目 3 可以說是數學問題,只要意識到其計算的實質上是 2 的 n 次冪即可,剩下的工作采用程序就很容易實現了。
下面具體說如何理解題目 2、題目 3 和 題目4:
- 對於題目 2,青蛙每次只能跳上 1 級或 2 級台階。假定青蛙需要跳上 n 級台階,其可能的組合數為 g(n) 。青蛙第 1 次跳台階有 2 種可能:跳上 1 級台階,剩余 n-1 級台階;跳上 2 級台階,剩余 n-2 級台階。所以 g(n) = g(n-1) + g(n-2),即青蛙跳上 n 級台階的可能的組合數等於第 1 次跳上 1 級台階的可能組合數加上第 1 次跳上 2 級台階的可能組合數。也可理解為以何種方式跳上第 n 級台階(跳上 1 級台階,還是跳上 2 級台階)。至此,就轉化為求解斐波那契數列的第 n 項問題。
- 對於題目 3,與題目 2 相比,區別在於青蛙每次可以跳上任意級台階,不僅僅是 1 級或 2 級台階。如果此時青蛙需要跳上 n 級台階,可采取的跳法有 f(n) = 2n-1 種。可以采用數學歸納法證明,具體證明思路如下:
當 n = 1或 n = 2 時, 顯然成立;
令 n = k 時, f(k) = 2k-1 成立,當 n = k+1 時,f(k+1) = f(k) + f(k-1) + f(k-2) + ... + f(1) + 1. 在增加 1 級台階后,可以理解為,設青蛙在跳上最后一級台階(新增加的台階)時,所跳上的台階數為 x,若 x = 1,則此時可采跳法是 f(k) 種跳法;若 x = 2,則此時可采取的跳法為 f(k-1); 如此下去,一直到 x = k-1 時,則此時可采取的跳法為 f(1);除此之外,還需要加上一種 x = k+1 可能,即只需一次直接跳上 k+1 級台階。又因為
- 對於題目 4,使用圖 2.13 左圖 (2x1的矩形,也可變換為1x2矩形,設為形狀A) 填充圖 2.13 的右圖 (2x8的矩形,設為形狀B(8),其中8為列數) 時,如果先放置一塊,有兩種放法,一種橫着放,一種是豎着放。如果第一次橫着放,則下一個也必須是橫着放,此時問題變為使用形狀 A 填充形狀 B(6);如果第一次是豎着放,則問題變為使用形狀 A 填充形狀 B(7)。為了表示方便,則依舊用相同的符號 B 表示為用 A 填充 B 的方法數,則有 B(8) = B(6) + B(7),從遞推公式可以看出,這是一個斐波那契數列的問題。
算法設計思想
1. 遞歸方法(Recursive Method)。循環調用自身。缺點:有大量的重復計算,不實用。優點:實現非常簡單,代碼短小。對於斐波那契數列的實現,其時間復雜度為 O(2n)。
2. 迭代方法 (Iterative Method)。通過循環,替代遞歸方法,從理論上說,任何遞歸算法都可用迭代算法實現。優點:節省棧空間,有可能降低時間復雜度。缺點是相對於遞歸方法,實現較難,代碼往往會復雜一些。對斐波那契數列,其時間復雜度為 O(n),是比較實用的算法。
3. 公式法。通過不常用的計算斐波那契數列的第 n 項的數學公式,如果采用合適的實現方式,可將時間復雜度降為 O(logn),具體數學公式和相關說明如下(摘自參考資料):
C++ 實現
#include <iostream>
// Method 1: recursive method and its time complexity is O(2^n).
int fibonacciRecursively(int n) { int result; if (n <= 0) result = 0; else if (1 == n) result = 1; else result = fibonacciRecursively(n-1) + fibonacciRecursively(n-2); return result; } // Method 2: iterative method and its time complexity is O(n).
int fibonacciIteratively(int n) { int result = 0; int nextItem = 1; for (int i = 1; i <= n; ++i) { int tmp = nextItem; nextItem += result; result = tmp; } return result; } // Method 3: by means of the specified matrix power
long int* matrixPower(long int *mat, int n); // compute the power of the matrix
int fibonacciMatrixPower(int n) { long int matrix[] = {1, 1, 1, 0}; int result = 0; if (n <= 0) result = 0; else { matrixPower(matrix, n-1); result = matrix[0]; } return result; } // 2 x 2 matrix power, n >= 0
long int* matrixPower(long int *mat, int n) { const int rows = 2; const int cols = 2; if (n <= 0) return NULL; else if (0 == n) { // identity matrix when the power of a matrix is 0.
for (int i = 0; i < rows; ++i) for (int j = 0; i < cols; ++j) { if (i == j) *(mat + i * cols + j) = 1; else
*(mat + i * cols + j) = 0; } } else if (1 == n) { } else if (2 == n) { // Create two temporary arrays for matrix multiplication
long int tmpMat1[4], tmpMat2[4]; for (int i = 0; i < rows; ++i) for (int j = 0; j < cols; ++j) { tmpMat1[i*cols+j] = *(mat + i * cols + j); tmpMat2[i*cols+j] = *(mat + i * cols + j); } // matrix multiplication
*(mat + 0 * cols + 0) = tmpMat1[0*cols+0] * tmpMat2[0*cols+0] + tmpMat1[0*cols+1] * tmpMat2[1*cols+0]; // matrix{0,0}
*(mat + 0 * cols + 1) = tmpMat1[0*cols+0] * tmpMat2[0*cols+1] + tmpMat1[0*cols+1] * tmpMat2[1*cols+1]; // matrix{0,1}
*(mat + 1 * cols + 0) = tmpMat1[1*cols+0] * tmpMat2[0*cols+0] + tmpMat1[1*cols+1] * tmpMat2[1*cols+0]; // matrix{1,0}
*(mat + 1 * cols + 1) = tmpMat1[1*cols+0] * tmpMat2[0*cols+1] + tmpMat1[1*cols+1] * tmpMat2[1*cols+1]; // matrix{1,1}
} else if (n % 2 == 0) // when n is even and n is greater than 2
{ matrixPower(mat, n/2); matrixPower(mat, 2); } else // n is odd and n is greater than 2
{ long int tmpMat1[4]; for (int k = 0; k < 4; ++k) tmpMat1[k] = *(mat + k); // Compute matrix power in even case
matrixPower(mat, n-1); // Temporarily save the matrix
long int tmpMat2[4]; for (int k = 0; k < 4; ++k) tmpMat2[k] = *(mat + k); // matrix multiplication with additional element.
*(mat + 0 * cols + 0) = tmpMat1[0*cols+0] * tmpMat2[0*cols+0] + tmpMat1[0*cols+1] * tmpMat2[1*cols+0]; *(mat + 0 * cols + 1) = tmpMat1[0*cols+0] * tmpMat2[0*cols+1] + tmpMat1[0*cols+1] * tmpMat2[1*cols+1]; *(mat + 1 * cols + 0) = tmpMat1[1*cols+0] * tmpMat2[0*cols+0] + tmpMat1[1*cols+1] * tmpMat2[1*cols+0]; *(mat + 1 * cols + 1) = tmpMat1[1*cols+0] * tmpMat2[0*cols+1] + tmpMat1[1*cols+1] * tmpMat2[1*cols+1]; } return mat; } void unitest() { int n = 5; std::cout << "The " << n << "-th item in the fibonacci sequence: \n"
<< " Recursive method result: " << fibonacciRecursively(n) << std::endl << " Iterative method result: " << fibonacciIteratively(n) << std::endl << " Matrix power method result: " << fibonacciMatrixPower(n) << std::endl ; } int main() { unitest(); return 0; }
Python 實現
#!/usr/bin/python # -*- coding: utf8 -*-
# Method 1: recursive method
def fib_recursively(n): result = 0 if n >= 1: if 1 == n: result = 1
else: result = fib_recursively(n-1) + fib_recursively(n-2) return result # Method 2: iterative method
def fib_iteratively(n): result, next_item = 0, 1 i = 1
while i <= n: result, next_item = next_item, result + next_item i += 1
return result # Method 3: matrix power
def fib_matrix_power(n): matrix = [1, 1, 1, 0] result = 0 if n > 0: matrix_power(matrix, n-1) result = matrix[0] return result # 2 x 2 matrix power
def matrix_power(mat, n): rows, cols = 2, 2 # 2 x 2 matrix
if n <= 0: return None elif 0 == n: mat[:] = [1, 0, 0, 1] # identity matrix
elif 1 == n: pass
elif 2 == n: tmp_mat1, tmp_mat2 = [], [] tmp_mat1.extend(mat) tmp_mat2.extend(mat) # matrix multiplication
for i in range(rows): for j in range(cols): mat[i*cols+j] = inner_product(tmp_mat1[i::cols], tmp_mat2[j::cols]) elif n % 2 == 0: # even case
matrix_power(mat, n/2) matrix_power(mat, 2) else: # temporarily save mat
tmp_mat1 = [] tmp_mat1.extend(mat) # recursive call
matrix_power(mat, n-1) # multiply with former temporary value
tmp_mat2 = [] tmp_mat2.extend(mat) for i in range(rows): for j in range(cols): mat[i*cols+j] = inner_product(tmp_mat1[i::cols], tmp_mat2[j::cols]) return mat def inner_product(vec1, vec2): product = 0 if (vec1 and vec2 and len(vec1) == len(vec2)): for i in range(len(vec1)): product += vec1[i] * vec2[i] return product if __name__ == '__main__': n = 5
print("The %d-th item in the fibonacci sequence:" % n) print(" Recursive method result: %d" % fib_recursively(n)) print(" Iterative method result: %d" % fib_iteratively(n)) print(" Matrix power method result: %d" % fib_matrix_power(n))
參考代碼
1. targetver.h
#pragma once
// The following macros define the minimum required platform. The minimum required platform // is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run // your application. The macros work by enabling all features available on platform versions up to and // including the version specified. // Modify the following defines if you have to target a platform prior to the ones specified below. // Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista.
#define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows.
#endif
2. stdafx.h
// stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently //
#pragma once #include "targetver.h" #include <stdio.h> #include <tchar.h>
// TODO: reference additional headers your program requires here
3. stdafx.cpp
// stdafx.cpp : source file that includes just the standard includes // Fibonacci.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H // and not in this file
4. Fibonacci.cpp
// Fibonacci.cpp : Defines the entry point for the console application. //
// 《劍指Offer——名企面試官精講典型編程題》代碼 // 著作權所有者:何海濤
#include "stdafx.h"
// ====================方法1:遞歸====================
long long Fibonacci_Solution1(unsigned int n) { if(n <= 0) return 0; if(n == 1) return 1; return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2); } // ====================方法2:循環====================
long long Fibonacci_Solution2(unsigned n) { int result[2] = {0, 1}; if(n < 2) return result[n]; long long fibNMinusOne = 1; long long fibNMinusTwo = 0; long long fibN = 0; for(unsigned int i = 2; i <= n; ++ i) { fibN = fibNMinusOne + fibNMinusTwo; fibNMinusTwo = fibNMinusOne; fibNMinusOne = fibN; } return fibN; } // ====================方法3:基於矩陣乘法====================
#include <cassert>
struct Matrix2By2 { Matrix2By2 ( long long m00 = 0, long long m01 = 0, long long m10 = 0, long long m11 = 0 ) :m_00(m00), m_01(m01), m_10(m10), m_11(m11) { } long long m_00; long long m_01; long long m_10; long long m_11; }; Matrix2By2 MatrixMultiply ( const Matrix2By2& matrix1, const Matrix2By2& matrix2 ) { return Matrix2By2( matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10, matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11, matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10, matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11); } Matrix2By2 MatrixPower(unsigned int n) { assert(n > 0); Matrix2By2 matrix; if(n == 1) { matrix = Matrix2By2(1, 1, 1, 0); } else if(n % 2 == 0) { matrix = MatrixPower(n / 2); matrix = MatrixMultiply(matrix, matrix); } else if(n % 2 == 1) { matrix = MatrixPower((n - 1) / 2); matrix = MatrixMultiply(matrix, matrix); matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0)); } return matrix; } long long Fibonacci_Solution3(unsigned int n) { int result[2] = {0, 1}; if(n < 2) return result[n]; Matrix2By2 PowerNMinus2 = MatrixPower(n - 1); return PowerNMinus2.m_00; } // ====================測試代碼====================
void Test(int n, int expected) { if(Fibonacci_Solution1(n) == expected) printf("Test for %d in solution1 passed.\n", n); else printf("Test for %d in solution1 failed.\n", n); if(Fibonacci_Solution2(n) == expected) printf("Test for %d in solution2 passed.\n", n); else printf("Test for %d in solution2 failed.\n", n); if(Fibonacci_Solution3(n) == expected) printf("Test for %d in solution3 passed.\n", n); else printf("Test for %d in solution3 failed.\n", n); } int _tmain(int argc, _TCHAR* argv[]) { Test(0, 0); Test(1, 1); Test(2, 1); Test(3, 2); Test(4, 3); Test(5, 5); Test(6, 8); Test(7, 13); Test(8, 21); Test(9, 34); Test(10, 55); Test(40, 102334155); return 0; }
5. 參考代碼下載
項目 09_Fibonacci 下載: 百度網盤
何海濤《劍指Offer:名企面試官精講典型編程題》 所有參考代碼下載:百度網盤
參考資料
[1] 何海濤. 劍指 Offer:名企面試官精講典型編程題 [M]. 北京:電子工業出版社,2012. 71-77.