案例一:有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。

给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。
测试样例:
[1,2,4],3,3
返回:2

思路:
1.暴力搜索法:
用零张1元,让[2,4]组成剩下的3,最终方法记为------------res1
用一张1元,让[2,4]组成剩下的2,最终方法数目记为---------res2
用两张1元,让[2,4]组成剩下的1,最终方法数目记为---------res3
用三张1元,让[2,4]组成剩下的1,最终方法数目记为---------res4
最后方法总数为:res1+res2+res3+res4

code

# -*- coding:utf-8 -*-

class Exchange:
    def countWays(self, penny, n, aim):
        # write code here
        return self.process(penny,0,aim)
    def process(self,penny,index,aim):
        res = 0
        # 当索引到达列表终点,判断aim是否为零
        if index == len(penny):  # 注意条件为整个列表的长度
            if aim==0:
                return 1
            else:
                return 0
       # 当索引没有到达列表终点时,需要继续向下计算
        else:
                i = 0
                while penny[index]*i <= aim:
                    res += self.process(penny,index+1,aim-penny[index]*i) 
                    i+=1
        return res

暴力搜索的缺点:会进行相当多重复的计算,
重复计算一个状态举例:
用两张一元,零张两元后,1,[4]
用零张一元和一张两元后,1,[4]
状态发生重复,但是我们仍会计算两次。


记忆搜索法:

我们可以试把已经计算了的值加入到哈希表中,在每次计算前在表中进行查询,如果表中存在则直接使用,不存在则需要暴力计算,计算完成后,再将所得到的值以及对应的状态放入到hash表中。

class Exchange:
    def countWays(self, penny, n, aim):
        # write code here
        dic = [[0 for clo in range(aim+1)]for row in range(n+1)]
        return self.process(penny,0,aim,dic)
    def process(self,penny,index,aim,dic):
        res = 0
        if index == len(penny):
            if aim==0:
                return 1
            else:
                return 0
        else:
            if dic[index][aim]!=0:
                if dic[index][aim]==-1:
                    return 0
                else:
                    return dic[index][aim]
            else:
                i = 0
                while penny[index]*i <= aim:
                    res += self.process(penny,index+1,aim-penny[index]*i,dic) 
                    i+=1
        if res==0:
            dic[index][aim]=-1
        else:
            dic[index][aim]=res
        return res

tips:我们用初始化为零的二维列表来代替没有计算过的状态,用-1来代替计算过但是数值为零的状态。注意初始化二维列表的方法:

a = [[0]*4]*3
print(a)
a[0][0]=1
print(a)

D:\Anaconda3\envs\pytorch\python.exe C:/Users/xiong/Desktop/jobshop.py
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
[[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]

动态规划法

1.记忆搜索方法就是某种形态的动态规划算法。
2.记忆搜索方法不关心到达某一个递归过程的路径,只是单纯的对计算递归过程进行记录,避免重复的递归过程。
3.动态规划的方法则是规定好每一个递归过程的计算顺序,一次惊醒计算,后面的计算过程严格依赖前面的计算。
4.两者都是空间换时间的方法,也都有枚举的过程

思路,我们需要从记忆搜索方法中找到某种可以不使用递归的方法,通过恰当的状态设置可以按顺序将所有的值写出来,动态规划就是有顺序的记忆搜索法

class Exchange:
    def countWays(self, penny, n, aim):
        # write code here
        dp = [[0 for clo in range(aim+1)] for row in range(n)]
        for j in range(aim+1):
            if j%penny[0]==0:
                dp[0][j]=1
        for i in range(n):
            dp[i][0]=1
        for j in range(1,aim+1):
            for i in range(1,n):
                k = 0             # 注意编程中尽量不要使用重复变量,产生新的变量前需要检查是否已经被使用。
                while k*penny[i]<=j:
                    dp[i][j]+=dp[i-1][j-k*penny[i]]
                    k+=1
        return dp[n-1][aim]