算法学习笔记1-动态规划问题(找钱问题)

问题一:换钱问题

 

 

以下各方法的示例为wallet=[5,3,2],aim=10,0<=pos<wallet.length

(1)   暴力递归法

Int F(wallet,pos,aim)

返回值:从pos位置的钱币开始,共有X种方式可以找aim元。

 

 

思路:

暴力递归法的思路主要是对于当前的aim和可使用的钱数wallet[pos],轮流调用其所有的可能方式。每一种方式,返回的是它后续可以找aim元的组合类型。

示例:

例如:最开始时pos=0,aim=10,当前货币为wallet[pos=0]=5元。所以共有0张,1张,2张5元的组合,进行遍历。

选择0张5元后,对应货币为3元。可以有0张,1张,2张,3张3元的组合方式。

选择1张5元后,对应货币为3元。可以有0张,1张3元的组合方式。

选择2张5元后,直接返回1,表示成功。

然后像这样层层递归下去。

复杂度分析:

    这里的复杂度分析比较麻烦,设wallet[N],aim=A。需要思考一下,以后再写。

(2)   优化一:记忆化搜索

思路:

主要是看到不同路径中可能有相同的子路径,例如[3,0][3,1][3,2]等,这些每次都要反复的调用递归函数,如果可以将这些相同的子路径返回值记录下来,就会节省一定的时间。

    因此建立一个Map,调用子路径时先看Map里面有没有存在的,有则取出,没有则继续调用递归函数。

(3)   优化二:动态规划-二维表方法

思路:

解空间是一张二维表dp,横坐标为0àaim_max,纵坐标为wallet[0]àwallet[length-1]。其中dp[0,aim]是最终的返回值,dp[wallet.length+1][0]=1,这都是根据递归中的截止条件可以确定的。

而dp中每一个位置:

dp[pos][aim]=dp[pos+1][aim-wallet[pos]*i],其中(aim-wallet[pos]*i>=0)

示例:

    初始解空间表

Pos

0

1

2

3

4

5

6

7

8

9

10

0

 

 

 

 

 

 

 

 

 

 

Res

1

 

 

 

 

 

 

 

 

 

 

 

2

 

 

 

 

 

 

 

 

 

 

 

3

1

0

0

0

0

0

0

0

0

0

0

解空间表1,pos=2时,dp[2][k]=dp[3][k]+dp[3][k-2]+dp[3][k-4]+…+dp[3][k-2*i](k-2i>=0)。

Pos

0

1

2

3

4

5

6

7

8

9

10

0

 

 

 

 

 

 

 

 

 

 

Res

1

 

 

 

 

 

 

 

 

 

 

 

2

1

0

1

0

1

0

1

0

1

0

1

3

1

0

0

0

0

0

0

0

0

0

0

解空间表2,pos=1时,dp[1][k]=dp[2][k]+dp[2][k-3]+dp[2][k-6]+…+dp[2][k-3*i](k-3i>=0)。

Pos

0

1

2

3

4

5

6

7

8

9

10

0

 

 

 

 

 

 

 

 

 

 

Res

1

1

0

1

1

1

1

2

1

2

2

2

2

1

0

1

0

1

0

1

0

1

0

1

3

1

0

0

0

0

0

0

0

0

0

0

解空间表3,pos=0时,dp[0][k]=dp[1][k]+dp[1][k-5]+dp[1][k-10]+…+dp[1][k-5*i](k-5i>=0)。

Pos

0

1

2

3

4

5

6

7

8

9

10

0

1

0

1

1

1

2

2

2

3

3

4

1

1

0

1

1

1

1

2

1

2

2

2

2

1

0

1

0

1

0

1

0

1

0

1

3

1

0

0

0

0

0

0

0

0

0

0

 

 

(4)   优化三:动态规划-一维表方法

思路:

可以看到,任意一个位置的返回值为

∑dp[pos+1][aim-wallet[pos]*i]=dp[pos+1][aim]+dp[pos][aim-wallet[pos]]

也就是说,每一个位置的返回值=正下方的那个位置的返回值+同一行它之前wallet[pos]位置处的值。

这也就是说,如果用一维列表来存储的话,每个位置的值等于它原来的值加上它前面某个位置的值,并不依赖于其他的东西。因而这类问题可以通过一维数组推导。

示例:

Int[] dp=new int[10+1]

初始时

Pos=3

1

0

0

0

0

0

0

0

0

0

0

一次迭代,dp[k]=dp[k]+k-2>=0?dp[k-2]:0;

Pos=2

1

0

1

0

1

0

1

0

1

0

1

二次迭代,dp[k]=dp[k]+k-3>=0?dp[k-3]:0;

Index

0

1

2

3

4

5

6

7

8

9

10

Pos=2

1

0

1

1

1

1

2

1

2

2

2

三次迭代,dp[k]=dp[k]+k-5>=0?dp[k-5]:0;

Index

0

1

2

3

4

5

6

7

8

9

10

Pos=2

1

0

1

1

1

2

2

2

3

3

4

 

代码如下

import java.util.HashMap;
public class DP_MoneyTypesProblem {
    /*
       
这个方法是正确的递归方法
     */
   
public static int combination2(int[] wallet, int pos,int aim)
    {
        System.out.println("pos:"+pos+",aim:"+aim);
        int sum=0;
        if(pos==wallet.length)
        {
            sum=aim==0?1:0;
        }
        else
       
{
            for(int i=0;wallet[pos]*i<=aim;i++) {
                sum += combination2(wallet, pos + 1, aim - i * wallet[pos]);
            }
        }
        System.out.println("sum:"+sum);
        return sum;
    }
    /*
       
记忆化搜索,带缓存的方法
     */
    // KEY: pos_aim
    //VALUE:返回值
   
public static HashMap<String,Integer> record;//Integer与int的区别
   
public static int combination3(int[] wallet, int pos,int aim)
    {
        System.out.println("pos:"+pos+",aim:"+aim);
        int sum=0;
        if(pos==wallet.length)
        {
            sum=aim==0?1:0;
        }
        else
       
{
            for(int i=0;wallet[pos]*i<=aim;i++) {
                String name=Integer.toString(pos+1)+"_"+Integer.toString(aim-i*wallet[pos]);
                if(record.containsKey(name))
                    sum+=record.get(name);
                else {
                    int temp = combination3(wallet, pos + 1, aim - i * wallet[pos]);
                    sum += temp;
                    record.put(name, temp);
                }
            }
        }
        System.out.println("sum:"+sum);
        return sum;
    }
    /*
    *
下面是动态规划-二维矩阵方法
     */
   
public static int combination4(int[] wallet,int aim)
    {
        if(wallet==null||aim<0)
            return -1;
        //初始化二维矩阵
       
int[][] dp=new int[wallet.length+1][aim+1];
        //设置矩阵的初始值,将所有可直接确定取值的位置赋值
       
for(int i=0;i<wallet.length+1;i++)
            dp[i][0]=1;
        for(int i=1;i<aim+1;i++)
            dp[wallet.length][i]=0;
        for(int i=wallet.length-1;i>=0;i--)
        {
            for(int j=0;j<aim+1;j++)
            {
                if(j-wallet[i]>=0)
                    dp[i][j]=dp[i+1][j]+dp[i][j-wallet[i]];
                else
                   
dp[i][j]=dp[i+1][j];
            }
        }
        for(int i=0;i<wallet.length+1;i++)
        {
            for(int j=0;j<aim+1;j++)
                System.out.print(dp[i][j]+" ");
            System.out.println("");
        }
        return dp[0][aim];
    }
    /*
    *
    *
下面是动态规划-一维表方法
     */
   
public static int combination5(int[] wallet,int aim)
    {
        if(wallet==null||aim<0)
            return -1;
        //初始化一维矩阵
       
int[] dp=new int[aim+1];
        //设置矩阵的初始值,将所有可直接确定取值的位置赋值
       
dp[0]=1;
        for(int i=1;i<aim+1;i++)
            dp[i]=0;
        for(int i=wallet.length-1;i>=0;i--)
        {
            for(int j=0;j<aim+1;j++)
            {
                if(j-wallet[i]>=0)
                    dp[j]=dp[j]+dp[j-wallet[i]];
            }
        }
        for(int j=0;j<aim+1;j++)
                System.out.print(dp[j]+" ");
        System.out.println("");
        return dp[aim];
    }
    public static void main(String[] args)
    {
        /*
        *
此类问题首先是递归法
        * 优化一是增加缓存,使用前提是无后效性问题(面试常见)/有后效性问题(N皇后问题)
        *
        * */
       
int wallet[]=new int[3];
        //以下是示例一:wallet=[200,100,50],aim=1000
        /*
        wallet[1]=100;
        wallet[0]=200;
        wallet[2]=50;
        int res=0;
        //下面是正确的递归方法
        res=combination2(wallet,0,1000);
        System.out.println(res);
        System.out.println("====================================");
        //下面是带缓存的方法
        record=new HashMap<String, Integer>();
        int res2=combination3(wallet,0,1000);
        System.out.println(res2);
        //下面是动态规划-二维表方法
        int res3=combination4(wallet,1000);
        System.out.println(res3);
        //下面是动态规划-一维表方法
        int res4=combination5(wallet,1000);
        System.out.println(res4);
        */
       
System.out.println("Example 2 ====================================");
        //以下是示例二:wallet=[5,3,2],aim=10
       
wallet[0]=5;
        wallet[1]=3;
        wallet[2]=2;
        //下面是正确的递归方法
       
int res=combination2(wallet,0,10);
        System.out.println(res);
        System.out.println("====================================");
        //下面是带缓存的方法
       
record=new HashMap<String, Integer>();
        int res2=combination3(wallet,0,10);
        System.out.println(res2);
        //下面是动态规划-二维表方法
       
int res3=combination4(wallet,10);
        System.out.println(res3);
        //下面是动态规划-一维表方法
       
int res4=combination5(wallet,10);
        System.out.println(res4);
    }
}

 

posted @ 2020-06-11 14:33  右耳朵猫  阅读(307)  评论(0)    收藏  举报