动态规划——机器人走路

题目一
假设有排成一行的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))
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值