暴力遞迴轉動態規劃
題
機器人到達指定位置
假設有排成一行的 N 個位置,記為 1~N,N 一定大於或等於 2。開始時機器人在其中的 M 位 置上(M 一定是 1~N 中的一個),機器人可以往左走或者往右走,如果機器人來到 1 位置, 那 麼下一步只能往右來到 2 位置;如果機器人來到 N 位置,那麼下一步只能往左來到 N-1 位置。 規定機器人必須走 K 步,最終能來到 P 位置(P 也一定是 1~N 中的一個)的方法有多少種。給 定四個引數 N、M、K、P,返回方法數。
轉換為動態規劃的思路
- 想出暴力遞迴的方法
- 根據暴力遞迴中變數的數量,設計表,表的範圍參考變數的範圍
- 在第二部的基礎上,尋找規律,比如那些列,那些行一開始就是可以確定的,然後找出正確方向順序來遍歷剛才第二部設計的表,將基本位置補全。
/**
* @Author: 郜宇博
* @Date: 2021/11/12 14:41
*/
public class RobotCounts {
public static void main(String[] args) {
long s1 = System.currentTimeMillis();
//System.out.println(getRobotCounts1(7, 3, 30, 5));
long s2 = System.currentTimeMillis();
System.out.println(getRobotCounts2(7,3,5000,5));
long s3 = System.currentTimeMillis();
System.out.println(fuc3(7,3,1000000,5));
long s4 = System.currentTimeMillis();
System.out.println("s1="+(s2-s1));
System.out.println("s2="+(s3-s2));
System.out.println("s3="+(s4-s3));
}
//開始在M位置,走K步,最後到P,一共有1-N個位置
public static int getRobotCounts1(int N,int M,int K,int P){
return fuc(N,M,K,P);
}
public static int getRobotCounts2(int N,int M,int K,int P){
//使用表儲存
int [][]dp = new int[N+1][K+1];
//初始化表,初始化時看可變引數的變化範圍
for (int i = 0; i < N+1; i++) {
for(int j = 0; j < K+1; j++){
dp[i][j] = -1;
}
}
return fuc1(N,M,K,P,dp);
}
public static int getRobotCounts3(int N,int M,int K,int P){
//使用表儲存
int [][]dp = new int[N+1][K+1];
//初始化表,初始化時看可變引數的變化範圍
for (int i = 0; i < N+1; i++) {
for(int j = 0; j < K+1; j++){
dp[i][j] = -1;
}
}
return fuc1(N,M,K,P,dp);
}
/**
* 方法一:暴力遞迴
* @param N 一共有1-N個位置
* @param cur 當前位置 M
* @param rest 剩餘步 K
* @param P 目標點
* @return 當前情況下到P的可能數
*/
public static int fuc(int N,int cur,int rest,int P){
//base case
if (rest == 0){
//沒步數,看是否到終點
return cur == P? 1:0;
}
//到兩邊了
if (cur == 1){
return fuc(N,2,rest-1,P);
}
if (cur == N){
return fuc(N,N-1,rest-1,P);
}
//在中間
return fuc(N,cur-1,rest-1,P) + fuc(N,cur+1,rest-1,P);
}
/**
* 方法二:記憶儲存
* @param N 一共有1-N個位置
* @param cur 當前位置
* @param rest 剩餘步
* @param P 目標點
* @return 當前情況下到P的可能數
*/
public static int fuc1(int N,int cur,int rest,int P,int[][]dp){
if (dp[cur][rest] != -1){
return dp[cur][rest];
}
if (rest == 0){
dp[cur][rest]= cur == P? 1:0;
return dp[cur][rest];
}
else {
if (cur == 1){
dp[cur][rest]= fuc1(N,2,rest-1,P,dp);
}
else if (cur == N){
dp[cur][rest]= fuc1(N,N-1,rest-1,P,dp);
}
else {
dp[cur][rest]=fuc1(N,cur-1,rest-1,P,dp) + fuc1(N,cur+1,rest-1,P,dp);
}
}
return dp[cur][rest];
}
/**
* 方法三:動態規劃
* @param N 一共有1-N個位置
* @param M 當前位置
* @param K 剩餘步
* @param P 目標點
* @return 當前情況下到P的可能數
* dp[剩餘步數][當前位置]
*/
public static int fuc3(int N,int M,int K,int P){
int[][] dp = new int[K+1][N+1];
dp[0][P] = 1;
for (int i = 1; i <= K; i++){
for (int j = 1; j <= N; j++){
if (j == 1){
dp[i][j] = dp[i-1][j+1];
}
else if (j == N){
dp[i][j] = dp[i-1][j-1];
}
//中間
else {
dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1];
}
}
}
return dp[K][M];
}
}
棋盤問題
馬從0,0位置出發到x,y,只能走step步,有多少種可能
相當於從x,y出發到0,0
/**
* @Author: 郜宇博
* @Date: 2021/11/12 17:56
*/
public class HorseCounts {
public static void main(String[] args) {
long s1 = System.currentTimeMillis();
System.out.println(getHorseCounts1(7, 7, 8));
long s2 = System.currentTimeMillis();
System.out.println(getHorseCounts2(7, 7, 15));
long s3 = System.currentTimeMillis();
System.out.println("S1="+(s2-s1));
System.out.println("S2="+(s3-s2));
}
public static int getHorseCounts1(int x,int y, int step){
return f1(x,y,step);
}
public static int getHorseCounts2(int x,int y, int step){
if (x < 0 || x > 8 || y < 0 || y > 9){
return 0;
}
//儲存的為x,y到0,0可以走step不得所有可能數
int[][][] dp = new int[9][10][step+1];
dp[0][0][0] = 1;
for (int k = 1; k <= step; k++){
for (int i = 0; i < 9; i++){
for (int j = 0; j < 10; j++){
//只和上一層有關,第0層初始狀態,第一層能由第0層推出。。。。
dp[i][j][k] += getValue(dp,i-1,j-2,k-1);
dp[i][j][k] += getValue(dp,i-2,j-1,k-1);
dp[i][j][k] += getValue(dp,i-2,j+1,k-1);
dp[i][j][k] += getValue(dp,i-1,j+2,k-1);
dp[i][j][k] += getValue(dp,i+1,j+2,k-1);
dp[i][j][k] += getValue(dp,i+2,j+1,k-1);
dp[i][j][k] += getValue(dp,i+2,j-1,k-1);
dp[i][j][k] += getValue(dp,i+1,j-2,k-1);
}
}
}
return dp[x][y][step];
}
private static int getValue(int[][][] dp, int x, int y, int step) {
if (x < 0 || x > 8 || y < 0 || y > 9 || step < 0){
return 0;
}
return dp[x][y][step];
}
/**
* 從x,y出發,到0,0點的可能數
*/
private static int f1(int x, int y, int step) {
//走越界了
if (x < 0 || x > 8 || y < 0 || y > 9){
return 0;
}
if (step == 0){
return (x == 0 && y == 0)?1:0;
}else {
return f1(x-1,y-2,step-1)+
f1(x-2,y-1,step-1)+
f1(x-2,y+1,step-1)+
f1(x-1,y+2,step-1)+
f1(x+1,y+2,step-1)+
f1(x+2,y+1,step-1)+
f1(x+2,y-1,step-1)+
f1(x+1,y-2,step-1);
}
}
}
湊錢可能數
[x,y,z,c]其中x,yz,c是人民幣面值,有任意張,aim為目標
問:湊齊aim的可能數
/**
* @Author: 郜宇博
* @Date: 2021/11/12 19:39
*/
public class MoneyCounts {
public static void main(String[] args) {
System.out.println(getMoneyCount(new int[]{2, 3, 4, 10},50));
System.out.println(getMoneyCount1(new int[]{2, 3, 4, 10},50));
System.out.println(getMoneyCount2(new int[]{2, 3, 4, 10},50));
}
public static int getMoneyCount(int[] arr, int aim){
return f(0,aim,arr);
}
public static int getMoneyCount1(int[] arr, int aim){
if (arr == null || arr.length == 0){
return 0;
}
//dp[index,rest]
int[][] dp = new int[arr.length+1][aim+1];
dp[arr.length][0] = 1;
//從下向上
for (int index = arr.length-1 ; index >= 0; index--){
for (int rest = 0; rest <= aim; rest++){
for (int i = 0; arr[index]*i <= rest; i++){
dp[index][rest] += dp[index+1][rest-arr[index]*i];
}
}
}
return dp[0][aim];
}
public static int getMoneyCount2(int[] arr, int aim){
if (arr == null || arr.length == 0){
return 0;
}
//dp[index,rest]
int[][] dp = new int[arr.length+1][aim+1];
dp[arr.length][0] = 1;
//從下向上
for (int index = arr.length-1 ; index >= 0; index--){
for (int rest = 0; rest <= aim; rest++){
dp[index][rest] = dp[index+1][rest];
if (rest-arr[index] >= 0){
dp[index][rest] += dp[index][rest-arr[index]];
}
}
}
return dp[0][aim];
}
public static int f(int index,int rest,int []arr){
if (index == arr.length){
return rest==0?1:0;
}
//有可用的錢,並且還沒湊齊aim
int res = 0;
//i代表張數,嘗試arr[index]位置的任意張數
for (int i = 0; i*arr[index]<=rest ;i++){
res += f(index+1,rest-arr[index]*i,arr);
}
return res;
}
}