【华为机试】70. 爬楼梯

发布于:2025-07-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

70. 爬楼梯

描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

提示

  • 1 <= n <= 45

解题思路

核心分析

这是一道经典的动态规划入门题目,本质上是斐波那契数列的变形。

问题建模

要到达第n阶楼梯,可以从两个位置到达:

  1. 从第(n-1)阶爬1步
  2. 从第(n-2)阶爬2步

因此:f(n) = f(n-1) + f(n-2)

这正是斐波那契数列的递推关系!

算法实现

方法1:动态规划(标准解法)

状态定义

  • dp[i] 表示到达第i阶楼梯的方法数
  • dp[i] = dp[i-1] + dp[i-2]

边界条件

  • dp[1] = 1(只有一种方法:爬1步)
  • dp[2] = 2(两种方法:1+1 或 2)
func climbStairs(n int) int {
    if n <= 2 {
        return n
    }
    
    dp := make([]int, n+1)
    dp[1] = 1
    dp[2] = 2
    
    for i := 3; i <= n; i++ {
        dp[i] = dp[i-1] + dp[i-2]
    }
    
    return dp[n]
}

时间复杂度:O(n)
空间复杂度:O(n)

方法2:空间优化动态规划(最优解)

由于状态转移只依赖前两个状态,可以用两个变量代替数组。

func climbStairsOptimized(n int) int {
    if n <= 2 {
        return n
    }
    
    prev2 := 1  // f(1)
    prev1 := 2  // f(2)
    
    for i := 3; i <= n; i++ {
        curr := prev1 + prev2
        prev2 = prev1
        prev1 = curr
    }
    
    return prev1
}

时间复杂度:O(n)
空间复杂度:O(1)

方法3:递归 + 记忆化

使用递归思路,配合记忆化避免重复计算。

func climbStairsMemo(n int) int {
    memo := make(map[int]int)
    return climbStairsMemoHelper(n, memo)
}

func climbStairsMemoHelper(n int, memo map[int]int) int {
    if n <= 2 {
        return n
    }
    
    if val, exists := memo[n]; exists {
        return val
    }
    
    result := climbStairsMemoHelper(n-1, memo) + climbStairsMemoHelper(n-2, memo)
    memo[n] = result
    return result
}

时间复杂度:O(n)
空间复杂度:O(n)

方法4:数学公式(斐波那契通项公式)

使用贝尔纳公式直接计算斐波那契数列第n项。

func climbStairsFormula(n int) int {
    if n <= 2 {
        return n
    }
    
    sqrt5 := math.Sqrt(5)
    phi := (1 + sqrt5) / 2        // 黄金比例
    psi := (1 - sqrt5) / 2        // 共轭黄金比例
    
    // 斐波那契通项公式:F(n) = (φ^n - ψ^n) / √5
    // 但这里是 F(n+1),因为我们的序列是 f(1)=1, f(2)=2
    result := (math.Pow(phi, float64(n+1)) - math.Pow(psi, float64(n+1))) / sqrt5
    
    return int(math.Round(result))
}

时间复杂度:O(1)
空间复杂度:O(1)

方法5:矩阵快速幂

使用矩阵快速幂计算斐波那契数列,适合处理大数。

func climbStairsMatrix(n int) int {
    if n <= 2 {
        return n
    }
    
    // 转换矩阵: [[1,1],[1,0]]
    base := [][]int{{1, 1}, {1, 0}}
    result := matrixPower(base, n-1)
    
    // result * [F(2), F(1)] = [F(n+1), F(n)]
    return result[0][0]*2 + result[0][1]*1
}

func matrixPower(matrix [][]int, n int) [][]int {
    size := len(matrix)
    result := make([][]int, size)
    for i := range result {
        result[i] = make([]int, size)
        result[i][i] = 1  // 单位矩阵
    }
    
    base := make([][]int, size)
    for i := range base {
        base[i] = make([]int, size)
        copy(base[i], matrix[i])
    }
    
    for n > 0 {
        if n&1 == 1 {
            result = matrixMultiply(result, base)
        }
        base = matrixMultiply(base, base)
        n >>= 1
    }
    
    return result
}

func matrixMultiply(a, b [][]int) [][]int {
    size := len(a)
    result := make([][]int, size)
    for i := range result {
        result[i] = make([]int, size)
        for j := 0; j < size; j++ {
            for k := 0; k < size; k++ {
                result[i][j] += a[i][k] * b[k][j]
            }
        }
    }
    return result
}

时间复杂度:O(log n)
空间复杂度:O(1)

复杂度分析

方法 时间复杂度 空间复杂度 优缺点
标准DP O(n) O(n) 思路清晰,易理解
空间优化DP O(n) O(1) 最实用的解法 ⭐
记忆化递归 O(n) O(n) 自顶向下,递归栈开销
数学公式 O(1) O(1) 最快,但有精度问题
矩阵快速幂 O(log n) O(1) 适合大数,复杂度低

核心要点

  1. 斐波那契本质:问题等价于求斐波那契数列第(n+1)项
  2. 状态转移:每个状态只依赖前两个状态
  3. 空间优化:可以用O(1)空间代替O(n)空间
  4. 边界处理:n=1和n=2的特殊情况

数学推导

递推关系证明

f(n) 表示到达第n阶楼梯的方法数:

递推关系

f(n) = f(n-1) + f(n-2)  (n ≥ 3)

初始条件

f(1) = 1
f(2) = 2

证明
要到达第n阶,只能从两个位置到达:

  • 从第(n-1)阶爬1步:有 f(n-1) 种方法
  • 从第(n-2)阶爬2步:有 f(n-2) 种方法
  • 总计:f(n-1) + f(n-2) 种方法

斐波那契数列对应关系

爬楼梯序列:1, 2, 3, 5, 8, 13, 21, 34, …
斐波那契序列:1, 1, 2, 3, 5, 8, 13, 21, …

关系climbStairs(n) = fibonacci(n+1)

通项公式推导

斐波那契数列通项公式:

F(n) = (φ^n - ψ^n) / √5

其中:

  • φ = (1 + √5) / 2 ≈ 1.618(黄金比例)
  • ψ = (1 - √5) / 2 ≈ -0.618

因此:

climbStairs(n) = F(n+1) = (φ^(n+1) - ψ^(n+1)) / √5

执行流程图

graph TD
    A[开始: 输入n] --> B{边界判断}
    B -->|n ≤ 2| C[返回n]
    B -->|n > 2| D[选择算法]
    
    D --> E[标准DP]
    D --> F[空间优化DP]
    D --> G[记忆化递归]
    D --> H[数学公式]
    D --> I[矩阵快速幂]
    
    E --> J[创建dp数组]
    F --> K[使用两个变量]
    G --> L[递归+缓存]
    H --> M[黄金比例公式]
    I --> N[矩阵乘法]
    
    J --> O[循环计算f1-fn]
    K --> P[循环更新prev1,prev2]
    L --> Q[递归计算子问题]
    M --> R[直接计算结果]
    N --> S[快速幂计算]
    
    O --> T[返回dp[n]]
    P --> T
    Q --> T
    R --> T
    S --> T
    C --> U[结束]
    T --> U

实际应用

  1. 组合计数:计算特定约束下的方案数
  2. 路径规划:网格中的路径计数问题
  3. 动态规划优化:状态压缩的经典例子
  4. 算法面试:考察DP基础的经典题目

扩展变形

  1. 步数扩展:如果可以爬1、2、3步怎么办?
  2. 限制条件:某些台阶不能踩怎么处理?
  3. 成本问题:每步有不同成本,求最小成本
  4. 二维扩展:在网格中从左上到右下的路径数

测试用例设计

// 基础测试
n=11
n=22
n=33
n=45
n=58

// 边界测试
n=11 (最小值)
n=451836311903 (题目限制最大值)

// 斐波那契验证
n=1089
n=2010946
n=301346269

// 性能测试
大数值测试各算法效率对比

数学性质

黄金比例的美

斐波那契数列中相邻两项的比值趋近于黄金比例φ:

lim(n→∞) F(n+1)/F(n) = φ = (1+√5)/2 ≈ 1.618

奇偶性质

F(n) 为偶数 ⟺ n ≡ 0 (mod 3)

整除性质

gcd(F(m), F(n)) = F(gcd(m, n))

平方和性质

F(1)² + F(2)² + ... + F(n)² = F(n) × F(n+1)

完整题解代码

package main

import (
	"fmt"
	"math"
	"time"
)

// 方法1:动态规划(标准解法)
func climbStairs(n int) int {
	if n <= 2 {
		return n
	}

	dp := make([]int, n+1)
	dp[1] = 1
	dp[2] = 2

	for i := 3; i <= n; i++ {
		dp[i] = dp[i-1] + dp[i-2]
	}

	return dp[n]
}

// 方法2:空间优化动态规划(最优解)
func climbStairsOptimized(n int) int {
	if n <= 2 {
		return n
	}

	prev2 := 1 // f(1)
	prev1 := 2 // f(2)

	for i := 3; i <= n; i++ {
		curr := prev1 + prev2
		prev2 = prev1
		prev1 = curr
	}

	return prev1
}

// 方法3:递归 + 记忆化
func climbStairsMemo(n int) int {
	memo := make(map[int]int)
	return climbStairsMemoHelper(n, memo)
}

func climbStairsMemoHelper(n int, memo map[int]int) int {
	if n <= 2 {
		return n
	}

	if val, exists := memo[n]; exists {
		return val
	}

	result := climbStairsMemoHelper(n-1, memo) + climbStairsMemoHelper(n-2, memo)
	memo[n] = result
	return result
}

// 方法4:数学公式(斐波那契通项公式)
func climbStairsFormula(n int) int {
	if n <= 2 {
		return n
	}

	sqrt5 := math.Sqrt(5)
	phi := (1 + sqrt5) / 2 // 黄金比例
	psi := (1 - sqrt5) / 2 // 共轭黄金比例

	// 斐波那契通项公式:F(n) = (φ^n - ψ^n) / √5
	// 这里是 F(n+1),因为我们的序列是 f(1)=1, f(2)=2
	result := (math.Pow(phi, float64(n+1)) - math.Pow(psi, float64(n+1))) / sqrt5

	return int(math.Round(result))
}

// 方法5:矩阵快速幂
func climbStairsMatrix(n int) int {
	if n <= 2 {
		return n
	}

	// 标准斐波那契矩阵:[[1,1],[1,0]]
	// F(n) = [[1,1],[1,0]]^(n-1) 的第一行第一列
	// 爬楼梯问题:climbStairs(n) = F(n+1)
	
	base := [][]int{{1, 1}, {1, 0}}
	result := matrixPower(base, n)
	
	// result[0][0] = F(n+1), result[0][1] = F(n)
	return result[0][0]
}

func matrixPower(matrix [][]int, n int) [][]int {
	size := len(matrix)
	result := make([][]int, size)
	for i := range result {
		result[i] = make([]int, size)
		result[i][i] = 1 // 单位矩阵
	}

	base := make([][]int, size)
	for i := range base {
		base[i] = make([]int, size)
		copy(base[i], matrix[i])
	}

	for n > 0 {
		if n&1 == 1 {
			result = matrixMultiply(result, base)
		}
		base = matrixMultiply(base, base)
		n >>= 1
	}

	return result
}

func matrixMultiply(a, b [][]int) [][]int {
	size := len(a)
	result := make([][]int, size)
	for i := range result {
		result[i] = make([]int, size)
		for j := 0; j < size; j++ {
			for k := 0; k < size; k++ {
				result[i][j] += a[i][k] * b[k][j]
			}
		}
	}
	return result
}

// 测试函数
func testClimbingStairs() {
	fmt.Println("=== 70. 爬楼梯测试 ===")

	testCases := []struct {
		name     string
		n        int
		expected int
	}{
		// 基础测试用例
		{"示例1", 2, 2},
		{"示例2", 3, 3},
		{"示例3", 4, 5},
		{"示例4", 5, 8},

		// 边界测试用例
		{"最小值", 1, 1},
		{"小值测试", 6, 13},

		// 斐波那契验证
		{"斐波那契-10", 10, 89},
		{"斐波那契-15", 15, 987},
		{"斐波那契-20", 20, 10946},

		// 中等规模测试
		{"中等规模-25", 25, 121393},
		{"中等规模-30", 30, 1346269},

		// 大规模测试(接近题目限制)
		{"大规模-40", 40, 165580141},
		{"最大值-45", 45, 1836311903},
	}

	methods := []struct {
		name string
		fn   func(int) int
	}{
		{"标准DP", climbStairs},
		{"空间优化DP", climbStairsOptimized},
		{"记忆化递归", climbStairsMemo},
		{"数学公式", climbStairsFormula},
		{"矩阵快速幂", climbStairsMatrix},
	}

	for _, tc := range testCases {
		fmt.Printf("\n测试用例: %s (n=%d)\n", tc.name, tc.n)
		fmt.Printf("期望输出: %d\n", tc.expected)

		for _, method := range methods {
			start := time.Now()
			result := method.fn(tc.n)
			duration := time.Since(start)

			status := "✓"
			if result != tc.expected {
				status = "✗"
			}

			fmt.Printf("%s %s: %d (耗时: %v)\n",
				status, method.name, result, duration)
		}
	}
}

// 性能测试
func performanceTest() {
	fmt.Println("\n=== 性能测试 ===")

	testSizes := []int{10, 20, 30, 40, 45}

	methods := []struct {
		name string
		fn   func(int) int
	}{
		{"标准DP", climbStairs},
		{"空间优化DP", climbStairsOptimized},
		{"记忆化递归", climbStairsMemo},
		{"数学公式", climbStairsFormula},
		{"矩阵快速幂", climbStairsMatrix},
	}

	for _, size := range testSizes {
		fmt.Printf("\n测试规模 n=%d:\n", size)

		for _, method := range methods {
			start := time.Now()
			result := method.fn(size)
			duration := time.Since(start)

			fmt.Printf("%s: 结果=%d, 耗时=%v\n",
				method.name, result, duration)
		}
	}
}

// 算法分析
func algorithmAnalysis() {
	fmt.Println("\n=== 算法分析 ===")

	fmt.Println("时间复杂度:")
	fmt.Println("  • 标准DP: O(n)")
	fmt.Println("  • 空间优化DP: O(n)")
	fmt.Println("  • 记忆化递归: O(n)")
	fmt.Println("  • 数学公式: O(1) ⭐ 最快")
	fmt.Println("  • 矩阵快速幂: O(log n)")

	fmt.Println("\n空间复杂度:")
	fmt.Println("  • 标准DP: O(n)")
	fmt.Println("  • 空间优化DP: O(1) ⭐ 最优实用")
	fmt.Println("  • 记忆化递归: O(n)")
	fmt.Println("  • 数学公式: O(1)")
	fmt.Println("  • 矩阵快速幂: O(1)")

	fmt.Println("\n核心思想:")
	fmt.Println("  1. 斐波那契数列本质:f(n) = f(n-1) + f(n-2)")
	fmt.Println("  2. 状态转移:到达第n阶只能从n-1或n-2阶到达")
	fmt.Println("  3. 空间优化:只需要保存前两个状态")
	fmt.Println("  4. 数学加速:利用黄金比例直接计算")

	fmt.Println("\n推荐使用:")
	fmt.Println("  • 面试/教学: 空间优化DP(平衡了效率和理解难度)")
	fmt.Println("  • 高性能场景: 数学公式(需要注意浮点精度)")
	fmt.Println("  • 大数场景: 矩阵快速幂(避免精度问题)")
}

// 斐波那契数列分析
func fibonacciAnalysis() {
	fmt.Println("\n=== 斐波那契数列分析 ===")

	fmt.Println("爬楼梯与斐波那契的关系:")
	fmt.Printf("%-10s %-15s %-15s\n", "n", "climbStairs(n)", "fibonacci(n+1)")
	fmt.Println(repeatString("-", 45))

	for i := 1; i <= 10; i++ {
		climb := climbStairsOptimized(i)
		fib := fibonacci(i + 1)
		fmt.Printf("%-10d %-15d %-15d\n", i, climb, fib)
	}

	fmt.Println("\n黄金比例验证:")
	for i := 5; i <= 15; i++ {
		fn := float64(climbStairsOptimized(i))
		fn1 := float64(climbStairsOptimized(i + 1))
		ratio := fn1 / fn
		phi := (1 + math.Sqrt(5)) / 2
		fmt.Printf("F(%d)/F(%d) = %.10f, φ = %.10f, 差值 = %.2e\n",
			i+1, i, ratio, phi, math.Abs(ratio-phi))
	}
}

// 辅助函数:计算斐波那契数列
func fibonacci(n int) int {
	if n <= 2 {
		return 1
	}
	a, b := 1, 1
	for i := 3; i <= n; i++ {
		a, b = b, a+b
	}
	return b
}

// 可视化演示
func visualDemo() {
	fmt.Println("\n=== 可视化演示 ===")

	n := 5
	fmt.Printf("示例: n = %d\n", n)

	fmt.Println("\n楼梯示意图:")
	for i := n; i >= 1; i-- {
		spaces := repeatString(" ", (n-i)*2)
		fmt.Printf("%s[%d]\n", spaces, i)
	}
	fmt.Println(repeatString(" ", n*2) + "[0] 起点")

	fmt.Println("\n状态转移过程:")
	fmt.Println("f(1) = 1 (一种方法: 爬1步)")
	fmt.Println("f(2) = 2 (两种方法: 1+1 或 2)")

	for i := 3; i <= n; i++ {
		prev1 := climbStairsOptimized(i - 1)
		prev2 := climbStairsOptimized(i - 2)
		curr := prev1 + prev2
		fmt.Printf("f(%d) = f(%d) + f(%d) = %d + %d = %d\n",
			i, i-1, i-2, prev1, prev2, curr)
	}

	fmt.Printf("\n最终答案: %d种方法\n", climbStairsOptimized(n))
}

// 辅助函数:重复字符串
func repeatString(s string, count int) string {
	if count <= 0 {
		return ""
	}
	result := ""
	for i := 0; i < count; i++ {
		result += s
	}
	return result
}

func main() {
	// 执行所有测试
	testClimbingStairs()
	performanceTest()
	algorithmAnalysis()
	fibonacciAnalysis()
	visualDemo()
}

网站公告

今日签到

点亮在社区的每一天
去签到