问题描述
有50层台阶,一个人每步可以上一层或者两层,问一共有多少种上楼的方式。
问题分析
50层可以由 49 层跨一步,也可以由48层跨两步,49层则可以由48层跨一步或者47层跨两步,…只有第1层只能由第0层跨一步到达,由此可见,当n>1 时, 第 n 层可由 n-1 层跨一步或者 n-2 层跨两步到达。
code
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("一段楼梯有n个台阶,每次只能上一层台阶或者两层台阶,输出有多少种上楼的可能。")
fmt.Println("输入 n")
var n int8
fmt.Scanln(&n)
fmt.Println("计算开始", time.Now())
result := deal(n, 0)
fmt.Println("计算结束", time.Now())
fmt.Println(result)
}
func deal(m int8, s int) int {
if m >= 2 {
return deal(m-1, s) + deal(m-2, s)
}
s++
return s
}
输出结果
一段楼梯有n个台阶,每次只能上一层台阶或者两层台阶,输出总所有多少种上楼的可能。
输入 n
50
计算开始 2020-09-14 15:30:20.253355305 +0800 CST m=+1.608072579
计算结束 2020-09-14 15:32:34.116474064 +0800 CST m=+135.471191265
20365011074
可以发现,虽然结果已经算出来了,但是通过输入 n 的变化可以发现,随着 n 的变大,计算的时间会增加的夸张,50层的计算已经需要 14 s的时间了。
优化分析
50 层可以由49层跨一步,也可以由48层跨两步,这里我们程序里会分为两种情况去计算,一种计算49层如何到达,一种计算48层怎么到达,然后49层就可以由48层跨一步或47层跨两步,这里又会计算48层怎么到达了,但是之前在计算50层如何到达的一种可能中就是48层如何到达,这里明细可以发现有重复的计算,所以我们可以使用一个字典去存已经计算过的数据,比如我们要计算48层如何到达,首先先看这个字典里有无48层的结果,如果有我们就直接用,不在去计算了,如果没有,则计算后插入字典。
优化code
package main
import (
"fmt"
"time"
)
var gmap = make(map[int8]int)
func main() {
fmt.Println("一段楼梯有n个台阶,每次只能上一层台阶或者两层台阶,输出总所有多少种上楼的可能。")
fmt.Println("输入 n")
var n int8
fmt.Scanln(&n)
fmt.Println("计算开始", time.Now())
//result := deal(n, 0)
result := dealPlus(n, 0)
fmt.Println("计算结束", time.Now())
fmt.Println(result)
}
//优化前
func deal(m int8, s int) int {
if m >= 2 {
return deal(m-1, s) + deal(m-2, s)
}
s++
return s
}
// 优化后
func dealPlus(m int8, s int) int {
if m >= 2 {
var onesetp int
var twosetp int
r1, ok1 := gmap[m-1]
r2, ok2 := gmap[m-2]
if ok1 {
onesetp = r1
} else {
onesetp = dealPlus(m-1, s)
gmap[m-1] = onesetp
}
if ok2 {
twosetp = r2
gmap[m-2] = twosetp
} else {
twosetp = dealPlus(m-2, s)
}
return onesetp + twosetp
}
s++
return s
}
优化后输出
一段楼梯有n个台阶,每次只能上一层台阶或者两层台阶,输出总所有多少种上楼的可能。
输入 n
50
计算开始 2020-09-14 15:30:05.80893695 +0800 CST m=+3.284548177
计算结束 2020-09-14 15:30:05.809395276 +0800 CST m=+3.285006492
20365011074
可以看出优化后的计算时间耗时为毫秒级
问题扩展
一段楼梯有n个台阶,每次至少跨一层,至多跨m层,输出总共有多少种上楼的可能。
扩展解析
首先考虑 楼梯高度只有 m 层,每次至多跨 m 层,有多少种可能。当 m = 1 时,只有一种可能,当 m = 2 时,有两种可能,当 m = 3 时,有 4 种可能,m = 4 时,8种可能, m = 5 时,有 16 种可能…大概可以推测出 当 m = n 时, 有 2 的 (n-1 ) 次方种可能。
推测只是让我们找到思路去验证,并不代表推测的就一定是对的,没有验证的推测都是耍流氓。
验证: m 层 可由 m-1 ,m-2 ,… ,1 ,0层跨对应的1,2,…,m-1,m 层到达。入股以 S(m)作为到达 m 层的可能。 当 m >=1 时,则 S( m ) = S( m -1 ) + S( m-2 ) +…+S(1) +S(0) 注: S(0) = 1 。
熟悉又陌生的感觉,然后开始梦回高中
因为 S(m) = S( m-1 ) + S(m-2) +.....+ S(1) +S(0)
S ( m-1) = S(m-2) +S(m-3)+....+S(1)+S(0)
所以 S(m) - S(m-1) = S( m-1 )
即 S(m) = 2 x S(m-1)
又因为 S(1) = S(1-1) = S(0) = 0
所以 S(m)= 2 的 m -1 次方
所以可得,当楼层 n <= m 时,上楼的可能为 2 的 n-1 次方。
当楼层 n > m 时,和前面 50 层楼,每次 1-2 层台阶的问题几乎一致。
S(n) = S( n-1) + S( n-2 ) +....+ S(n-m)
func dealPlusPlus(m int, s int, max int) int {
if m > max {
var tmp int
for i := 1; i <= max; i++ {
r, ok := gmap[m-i]
if ok {
tmp += r
} else {
tmp += dealPlusPlus(m-i, s, max)
gmap[m-i] = tmp
}
}
return tmp
}
s = int(math.Pow(2, float64(m-1)))
return s
}