以两个题目来说明,并带你掌握这种转化,不用什么动态规划的方程什么的,你只需要根据题意写出暴力递归就可以了。
第一题:
一排有N个位置,一个机器人在最开始停留在P位置上,如果P==0,下一分钟机器人一定向右移动到1位置,如果P=N-1,下一分钟机器人一定向左移动到N-2位置。如果P在0到N-1之间,下一分钟机器人一定会向左或向右移动,求K分钟时候,机器人到达T位置有多少种走法。
思路:这里我们先用递归求解,然后再阐释暴力递归转动态规划的做法。
递归:在K分钟是T位置上只能从K-1分钟的T-1位置和K-1分钟的T+1位置上来,那么K分钟T位置上走法也就是他们二者之和了。然后在设置相应的边界就可以了。
public static int f1(int N,int P,int K,int T){
if (N < 2 || P < 0 || K < 1 || T < 0 || P >= N || T >= N) {
return 0;
}
if (K == 1) {
return T==P?1:0;
}
if (T == 0) {
return f1(N,P,K-1,1);
}
if (T == N - 1) {
return f1(N,P,K-1,T-1);
}
return f1(N,P,K-1,T-1)+f1(N,P,K-1,T+1);
}
暴力递归转动态规划:由递归式可知N、P传进来就没有变过,所以这个dp由K、T控制,但是T的取值范围是N,所有组成一个K*N的二维数组。然后根据递归式转换数组。
public static int f2(int N, int P, int K, int T) {
if (N < 2 || P < 0 || K < 1 || T < 0 || P >= N || T >= N) {
return 0;
}
int[][] dp = new int[K][N];
dp[0][P] = 1;
for (int i = 1; i < K; i++) {
dp[i][0] = dp[i - 1][1];
dp[i][N - 1] = dp[i - 1][N - 2];
for (int j = 1; j < N - 1; j++) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
}
}
return dp[K - 1][T];
}
我们还可以对此进行优化,可以看出我们生成二维数组的过程是一行一行生成的而且某一行的生成只依赖它的上一行,并且结果只存在在最后一行。那么我么就可以用两个长度为N的一维数组交替保存数据来代替二维数组。
那么还可以不可以优化了。答案是可以的,只用一个长度为N的二维数组。数组的生成是一个一个往后覆盖的,我们将计算结构覆盖在上面就可以了,但是中间元素要依赖上行两边的元素。上方后面元素不用担心,因为在计算的时候还没有被覆盖,前面的元素在被覆盖之前,我们用一个pre变量保存一下就可以了。
public static int f3(int N, int P, int K, int T) {
if (N < 2 || P < 0 || K < 1 || T < 0 || P >= N || T >= N) {
return 0;
}
int[] dp = new int[N];
dp[P] = 1;
int pre = 0;
int tmp = 0;
for (int i = 1; i < K; i++) {
pre = dp[0];
dp[0] = dp[1];
for (int j = 1; j < N - 1; j++) {
tmp = dp[j];
dp[j] = pre + dp[j + 1];
pre = tmp;
}
dp[N - 1] = pre;
}
return dp[T];
}
第二题:
给定一个整形数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,但每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都是绝顶聪明。请返回最后获胜者的分数。
举例:arr=[1,2,100,4],开始时,玩家A只能拿1或4,如果玩家A拿走1,则玩家B可以拿2或4,然后继续A拿,如果开始时A拿走4,则B可以拿100。因为A绝顶聪明,不会让B拿100,因为B拿了100,不论B在拿什么都是胜者,所以A会拿1。
思路:每个玩家分别选择自己对自己最优的决定。然后在设置边界。
public static int win1(int[] arr){
if (arr==null||arr.length==0)
return 0;
return Math.max(first(arr,0,arr.length-1),seconde(arr,0,arr.length-1));
}
//在数组arr的i到j上先拿
public static int first(int[] arr,int i,int j){
//如果只有一个元素,先拿就别无选择
if (i==j){
return arr[i];
}
return Math.max(arr[i]+seconde(arr,i+1,j),arr[j]+seconde(arr,i,j-1));
}
//在数组arr的i到j上后拿
public static int seconde(int[] arr,int i,int j){
//如果只剩一个元素,后那肯定什么都拿不到了
if (i == j) {
return 0;
}
return Math.min(first(arr,i+1,j),first(arr,i,j-1));
}
暴力递归改动态规划:两个递归函数要涉及到两个二维表,且根据递归只给出的ij关系确定都是上右三角,如图:
public static int win2(int[] arr){
if (arr == null || arr.length == 0) {
return 0;
}
int[][] first=new int[arr.length][arr.length];
int[][] second=new int[arr.length][arr.length];
for (int j = 0; j < arr.length; j++) {
first[j][j] = arr[j];
for (int i = j - 1; i >= 0; i--) {
first[i][j] = Math.max(arr[i] + second[i + 1][j], arr[j] + second[i][j - 1]);
second[i][j] = Math.min(first[i + 1][j], first[i][j - 1]);
}
}
return Math.max(first[0][arr.length - 1], second[0][arr.length - 1]);
}
有人会说,这都两个递归改成两张表,也不好啊, 你不就爱优化吗,这下怎么不优化了,来,那就优化优化,改成一个递归一张表的。
其实递归的该法就是不用两个递归来回跳,但是拿牌的思路还是一样的:
public static int win3(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
int scores = p(arr, 0, arr.length - 1);
return Math.max(sum - scores, scores);
}
public static int p(int[] arr, int i, int j) {
if (i == j) {
return arr[i];
}
if (i + 1 == j) {
return Math.max(arr[i], arr[j]);
}
return Math.max(arr[i] + Math.min(p(arr, i + 2, j), p(arr, i + 1, j - 1)),
arr[j] + Math.min(p(arr, i + 1, j - 1), p(arr, i, j - 2)));
}
转动态规划:
public static int win4(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0];
}
if (arr.length == 2) {
return Math.max(arr[0], arr[1]);
}
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
int[][] dp = new int[arr.length][arr.length];
for (int i = 0; i < arr.length - 1; i++) {
dp[i][i] = arr[i];
dp[i][i + 1] = Math.max(arr[i], arr[i + 1]);
}
dp[arr.length - 1][arr.length - 1] = arr[arr.length - 1];
for (int k = 2; k < arr.length; k++) {
for (int j = k; j < arr.length; j++) {
int i = j - k;
dp[i][j] = Math.max(arr[i] + Math.min(dp[i + 2][j], dp[i + 1][j - 1]),
arr[j] + Math.min(dp[i + 1][j - 1], dp[i][j - 2]));
}
}
return Math.max(dp[0][arr.length - 1], sum - dp[0][arr.length - 1]);
}