1.机器人到达指定位置方法数
(1)暴力递归
public static int ways1(int N, int M, int K, int P) {
return walk(N,M,K,P);
}
// N : 位置为1 ~ N,固定参数
// cur : 当前在cur位置,可变参数
// rest : 还剩res步没有走,可变参数
// P : 最终目标位置是P,固定参数
// 该函数的含义:只能在1~N这些位置上移动,当前在cur位置,走完rest步之后,停在P位置的方法数作为返回值返回
public static int walk(int N, int cur, int rest, int P) {
// 如果没有剩余步数了,当前的cur位置就是最后的位置
// 如果最后的位置停在P上,那么之前做的移动是有效的
// 如果最后的位置没在P上,那么之前做的移动是无效的
if(rest==0){
return cur==P?1:0;
}
// 如果还有rest步要走,而当前的cur位置在1位置上,那么当前这步只能从1走向2
// 后续的过程就是,来到2位置上,还剩rest-1步要走
if(cur==1){
return walk(N,cur+1,rest-1,P);
}
// 如果还有rest步要走,而当前的cur位置在N位置上,那么当前这步只能从N走向N-1
// 后续的过程就是,来到N-1位置上,还剩rest-1步要走
if(cur==N){
return walk(N,cur-1,rest-1,P);
}
// 如果还有rest步要走,而当前的cur位置在中间位置上,那么当前这步可以走向左,也可以走向右
// 走向左之后,后续的过程就是,来到cur-1位置上,还剩rest-1步要走
// 走向右之后,后续的过程就是,来到cur+1位置上,还剩rest-1步要走
// 走向左、走向右是截然不同的方法,所以总方法数要都算上
return walk(N,cur-1,rest-1,P)+walk(N,cur+1,rest-1,P);
}
(2)记忆化搜索:使用dp数组记录走过的位置
public static int ways2(int N, int M, int K, int P) {
int dp[][]=new int[M+1][K+1];
for (int i = 0; i < M+1; i++) {
for (int j=0;j<K+1;j++){
dp[i][j]=-1;
}
}
return walk2(N, M, K, P,dp);
}
private static int walk2(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;
}else if(cur==1){
dp[cur][rest]=walk(N,cur+1,rest-1,P);
}else if(cur==N){
dp[cur][rest]=walk(N,cur-1,rest-1,P);
}else{
dp[cur][rest]=walk(N,cur-1,rest-1,P)+walk(N,cur+1,rest-1,P);
}
return dp[cur][rest];
}
(3)严格表结构
public static int ways3(int N, int M, int K, int P) {
int dp[][]=new int[K+1][N+1];
for(int i=0;i<K+1;i++){
for (int j=0;j<N+1;j++){
dp[i][j]=-1;
}
}
return walk3(N,M,K,P,dp);
}
private static int walk3(int N, int cur, int rest, int P, int[][] dp){
//第一行赋值
for(int j=0;j<N+1;j++){
if(j==P){
dp[0][j]=1;
}else{
dp[0][j]=0;
}
}
//其他行赋值
for(int i=1;i<rest+1;i++){
for (int j=1;j<N+1;j++){
if(j==1){
dp[i][j]=dp[i-1][2];
}else if(j==N){
dp[i][j]=dp[i-1][N-1];
}else{
dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
}
}
}
return dp[rest][cur];
}
2.换钱的最少货币数(左神版:每一种面值的货币只能拿一个)
(1)暴力递归
public static int minCoins3(int[] arr,int aim){
return process3(arr,0,aim);
}
public static int process3(int[] arr,int i,int rest){
if(rest<0){
return -1;
}
if(rest==0){
return 0;
}
if(i==arr.length){
return -1;
}
int p1=process3(arr,i+1,rest);
int p2Next=process3(arr,i+1,rest-arr[i]);
if(p1==-1&&p2Next==-1){
return -1;
}else{
if(p1==-1){
return p2Next+1;
}
if(p2Next==-1){
return p1;
}
return Math.min(p1,p2Next);
}
}
(2)记忆搜索
public static int minCoins4(int[] arr,int aim){
int dp[][]=new int[arr.length+1][aim+1];
for (int i=0;i<arr.length;i++){
for (int j = 0; j < aim; j++) {
dp[i][j]=-2;
}
}
return process4(arr,0,aim,dp);
}
public static int process4(int[] arr,int i,int rest,int[][] dp){
if(rest<0){
return -1;
}
if(dp[i][rest]!=-2){
return dp[i][rest];
}
if(rest==0){
dp[i][rest]=0;
return dp[i][rest];
}
if(i==arr.length){
dp[i][rest]=-1;
return dp[i][rest];
}
int p1=process3(arr,i+1,rest);
int p2Next=process3(arr,i+1,rest-arr[i]);
if(p1==-1&&p2Next==-1){
dp[i][rest]=-1;
}else{
if(p1==-1){
dp[i][rest]=p2Next+1;
}else if(p2Next==-1){
dp[i][rest]=p1;
}else{
dp[i][rest]=Math.min(p1,p2Next);
}
}
return dp[i][rest];
}
(3)严格表
public static int minCoin6(int [] arr,int aim){
int dp[][]=new int[arr.length+1][aim+1];
for(int index=0;index<=arr.length;index++){
dp[index][0]=0;
}
for (int rest = 1; rest <=aim; rest++) {
dp[arr.length][rest]=-1;
}
for(int index=arr.length-1;index>=0;index--){
for (int rest=1;rest<=aim;rest++){
int p1=dp[index+1][rest];
int p2Next=-1;
if(rest-arr[index]>=0){
p2Next=dp[index+1][rest-arr[index]];
}
if(p1==-1&&p2Next==-1){
dp[index][rest]=-1;
}else{
if(p1==-1){
dp[index][rest]=p2Next+1;
}else if(p2Next==-1){
dp[index][rest]=p1;
}else{
dp[index][rest]=Math.min(p1,p2Next+1);
}
}
}
}
return dp[0][aim];
}
3.换钱的最少货币数(同一种面值可拿多个)
(1)暴力递归
public static int minCoins1(int[] arr, int aim) {
if(arr.length==0||arr==null||aim<0){
return -1;
}
return process1(arr,0,aim);
}
// 当前考虑的面值是arr[i],还剩rest的钱需要找零
// 如果返回-1说明自由使用arr[i..N-1]面值的情况下,无论如何也无法找零rest
// 如果返回不是-1,代表自由使用arr[i..N-1]面值的情况下,找零rest需要的最少张数
public static int process1(int[] arr, int i, int rest) {
// base case:
// 已经没有面值能够考虑了
// 如果此时剩余的钱为0,返回0张
// 如果此时剩余的钱不是0,返回-1
if(i==arr.length){
return rest==0?0:-1;
}
// 最少张数,初始时为-1,因为还没找到有效解
int res=-1;
// 依次尝试使用当前面值(arr[i])0张、1张、k张,但不能超过rest
// 使用了k张arr[i],剩下的钱为rest - k * arr[i]
// 交给剩下的面值去搞定(arr[i+1..N-1])
// 说明这个后续过程有效
for(int k=0;k*arr[i]<=rest;k++){
int next=process1(arr,i+1,rest-k*arr[i]);
if(next!=-1){
res=res==-1?next+k:Math.min(res,next+k);
}
}
return res;
}
(2)记忆化搜索
class Solution {
public int coinChange(int[] coins, int amount) {
if(coins.length == 0 || coins == null){
return -1;
}
int dp[][] = new int[coins.length+1][amount+1];
for(int i = 0; i <= coins.length; i++){
for(int j = 0; j <= amount; j++){
dp[i][j] = -2;
}
}
return dfs(coins, 0, amount, dp);
}
public int dfs(int[] coins, int index, int rest, int [][] dp){
if(dp[index][rest] != -2){ //如果当前位置之前计算过了,直接返回
return dp[index][rest];
}
if(index == coins.length){ //在返回之前先记一下当前位置的值,再返回
dp[index][rest] = rest == 0 ? 0 : -1;
return dp[index][rest];
}
int res = -1;
for(int i = 0; (i * coins[index]) <= rest; i++){
int next = dfs(coins, index + 1, rest - (i * coins[index]), dp);
if(next == -1){
continue;
}else{
res = res == -1 ? next + i : Math.min(res, next + i);
}
}
dp[index][rest] = res;//在返回之前先记一下当前位置的值,再返回
return dp[index][rest];
}
}
(3)严格表(未掌握)
public static int minCoins3(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return -1;
}
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
// 设置最后一排的值,除了dp[N][0]为0之外,其他都是-1
for (int col = 1; col <= aim; col++) {
dp[N][col] = -1;
}
for (int i = N - 1; i >= 0; i--) { // 从底往上计算每一行
for (int rest = 0; rest <= aim; rest++) { // 每一行都从左往右
dp[i][rest] = -1; // 初始时先设置dp[i][rest]的值无效
if (dp[i + 1][rest] != -1) { // 下面的值如果有效
dp[i][rest] = dp[i + 1][rest]; // dp[i][rest]的值先设置成下面的值
}
// 左边的位置不越界并且有效
if (rest - arr[i] >= 0 && dp[i][rest - arr[i]] != -1) {
if (dp[i][rest] == -1) { // 如果之前下面的值无效
dp[i][rest] = dp[i][rest - arr[i]] + 1;
} else { // 说明下面和左边的值都有效,取最小的
dp[i][rest] = Math.min(dp[i][rest],
dp[i][rest - arr[i]] + 1);
}
}
}
}
return dp[0][aim];
}
3.换钱的最少货币数(同一种面值可拿多个,但是这道题并不是返回最少货币数,而是返回方法数)
(1)暴力递归
public static int minCoin7(int [] arr,int aim){
if(arr==null||arr.length==0||aim==0){
return 0;
}
return process7(arr,0,aim);
}
private static int process7(int[] arr, int i, int rest) {
if(i==arr.length){
return rest==0?1:0;
}
int num=0;
for(int zhang=0;zhang*arr[i]<=rest;zhang--){
num+=process7(arr,i+1,rest-zhang*arr[i]);
}
return num;
}
(2)记忆化搜索
public static int minCoin8(int [] arr,int aim){
if(arr==null||arr.length==0||aim==0){
return 0;
}
int dp[][]=new int[arr.length+1][aim+1];
return process8(arr,0,aim,dp);
}
private static int process8(int[] arr, int i, int rest,int [][] dp) {
if(i==arr.length){
dp[i][rest]=rest==0?1:0;
return dp[i][rest];
}
int ways=0;
for(int zhang=0;zhang*arr[i]<=rest;zhang--){
ways+=process7(arr,i+1,rest-zhang*arr[i]);
}
dp[i][rest]=ways;
return dp[i][rest];
}
(3)严格表dp
public static int minCoin9(int [] arr,int aim) {
if (arr == null || arr.length == 0 || aim == 0) {
return 0;
}
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++) {
int ways = 0;
for (int zhang = 0; arr[index] * zhang <= rest; zhang++) {
if(rest - zhang * arr[index]>=0){
ways += dp[index + 1][rest - zhang * arr[index]];
}
}
dp[index][rest]=ways;
}
}
return dp[0][aim];
}
(4)严格表dp(进一步优化)
public static int minCoin10(int [] arr,int aim) {
if (arr == null || arr.length == 0 || aim == 0) {
return 0;
}
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];
}