题目一
假设有排成一行的N个位置,记为:1~N,N一定大于或等于2
开始时机器人在其中的M位置上(M一定是1~N中的一个)
如果机器人来到1的位置,那么下一步只能往右来到2的位置
如果机器人来到N的位置,那么下一步只能往左来到N-1的位置
如果机器人来到中间位置,那么下一步可以往左走或者往右走
规定机器人开始在M位置,必须走K步,最终能来到P位置(P也是1~N中的一个)的方法有多少种
给定四个参数 N、M、K、P,返回方法数。
N = 7
M = 3
P = 4
K = 3
1 2 3 4 5 6 7
1 --> 2
7 --> 6
1. 3 > 2 > 3 > 2
2. 3 > 2 > 1 > 2
3. 3 > 4 > 3 > 2
*/
func ways1(N, start, aim, K int) int{
//参数无效直接返回0
if N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1 {
return -1
}
//总共N个位置,从M点出发,还剩K步,返回最终能到达P的方法数
return walk(start, K, aim, N)
}
//N:位置为1~N,固定参数
//cur: 当前在cur位置,可变参数
//rest: 还剩rest步没有走,可变参数
//P:最终目标位置是P,固定参数
//该函数的汉字:只能在1 ~ N这些位置上移动,当前在cur位置,走完rest步之后,停在P位置的方法数
func walk(cur, rest, aim, N int) int {
//如果没有剩余步数了,当前的cur位置就是最后的位置
//如果最后的位置停在P上,那么之前做的移动是有效的
//如果最后的位置没在P上,那么最初做的移动是无效的
if rest == 0 {
if cur == aim {
return 1
}
return 0
}
//如果还有rest步要走,而当前的cur位置在1的位置上,那么当前这不只能从1走向2
//后序的过程就是,来到2位置上,还剩rest步要走
if cur == 1 {
return walk(2,rest-1, aim,N)
}
//如果还有rest步要走,而当前的cur位置在N位置上,那么当前这步只能从N走向N-1
//后序的过程就是,来到N-1位置上,还剩rest-1步要走
if cur == N {
return walk(N-1,rest-1, aim,N)
}
//如果还有rest步要走,而当前的cur位置在中间位置上,那么当前这步可以向左走或向右走
//走向左之后,后序的过程就是,来到cur -1 位置上,还剩rest -1 步要走
//走向右之后,后序的过程就是,来到cur +1 位置上,还剩rest -1 步要走
//走向左、走向有是截然不同的方法,所以总方法数要都算上
return walk(cur + 1,rest -1, aim,N) + walk(cur-1 ,rest -1 , aim,N)
}
func TestWays1(t *testing.T) {
t.Log(ways1(7,3,4,3))
}
//记忆化搜索 动态规划中的最糙
func ways2(N, start, aim, K int) int{
//参数无效直接返回0
if N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1 {
return -1
}
dp := make([][]int,N+1)
for k := range dp {
dp[k] = make([]int,K+1)
}
// dp就是缓存表
// dp[cur][rest] == -1 -> process1(cur, rest)之前没算过!
// dp[cur][rest] != -1 -> process1(cur, rest)之前算过!返回值,dp[cur][rest]
// N+1 * K+1
for row := 0; row <= N; row++ {
for col:= 0; col <= K; col++ {
dp[row][col] = -1
}
}
//总共N个位置,从M点出发,还剩K步,返回最终能到达P的方法数
return walkCache( start,K,aim, N,dp)
}
//N:位置为1~N,固定参数
//cur: 当前在cur位置,可变参数
//rest: 还剩rest步没有走,可变参数
//P:最终目标位置是P,固定参数
//该函数的汉字:只能在1 ~ N这些位置上移动,当前在cur位置,走完rest步之后,停在P位置的方法数
func walkCache(cur, rest, aim, N int, dp [][]int) int {
if dp[cur][rest] != -1 {
return dp[cur][rest]
}
if rest == 0 {
//加缓存 再返回
if cur == aim {
dp[cur][rest] = 1
return 1
}
dp[cur][rest] = 0
return 0
}
if cur == 1 {
dp[cur][rest] = walkCache(2,rest-1, aim, N,dp)
return dp[cur][rest]
}
if cur == N {
dp[cur][rest] = walkCache(N-1,rest-1, aim, N, dp)
return dp[cur][rest]
}
dp[cur][rest] = walkCache(cur + 1,rest -1, aim, N,dp) + walkCache(cur-1 ,rest -1 , aim, N,dp)
return dp[cur][rest]
}
func TestWaysCache(t *testing.T) {
t.Log(ways1(7,3,4,3))
}
//把整张dp表,把整个状态列出来,就是经典的动态规划
//两个可变参数是 二维表
/*
rest1~7 = N
2 = M
5 = K
3 = P
0 1 2 3 4 5
0
1
2
3
4
5
6
7
0 1 2 3 4 5
0 X X X X X X
1 0
2 0
3 1
4 0
5 0
6 0
7 0
rest = 0 ,cur == p 时,p = 3, cur = 1
cur = 1时, 依赖左下角的值,copy上去就行
cur = n时, 依赖左上角的值,copy下来就行
任何一个普遍的值,依赖 左下角和左上角的累加和,copy下来
也就是说,给定某个可变参数的递归,就能改出动态规划,不需要依赖原题意
0 1 2 3 4 5
0 X X X X X X
1 0 0 1 0 3 0
2 0 1 0 3 0【9】 //返回 dp[M][K]
3 1 0 2 0 6 0
4 0 1 0 3 0 10
5 0 0 1 0 4 0
6 0 0 0 1 0 5
7 0 0 0 0 1 0
动态规划,都是由暴力尝试的种子改过来的
不是所有的暴力递归都能改成动态规划,应为某些暴力递归没有足够多的重复过程
所有动态规划一定来自于一个暴力递归
把参数组合完成一个结构化的缓存
*/
func ways3(N, start, aim, K int) int {
if N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1 {
return -1
}
dp := make([][]int,N+1)
for k := range dp {
dp[k] = make([]int,K+1)
}
dp[aim][0] = 1
for rest := 1; rest <= K; rest++ {
dp[1][rest] = dp[2][rest-1]
for cur := 2; cur < N; cur++ {
dp[cur][rest] = dp[cur-1][rest-1] + dp[cur+1][rest-1]
}
dp[N][rest] = dp[N-1][rest-1]
}
return dp[start][K]
}
func TestRobotWalk(t *testing.T) {
fmt.Println(ways1(5,2,4,6))
fmt.Println(ways2(5,2,4,6))
fmt.Println(ways3(5,2,4,6))
}
动态规划——机器人走路
最新推荐文章于 2023-05-28 16:23:16 发布