经典算法之动态规划

不知道大家有没有一种感觉,一旦提到动态规划头就会隐隐作痛,因为动态规划这个东西一旦想清楚就十分简单,想不清楚就巨难,所以一些面试的时候,一旦被问到这样的问题就会惊出一身冷汗。今天咱们就介绍几个动态规划的问题。

括号匹配

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 

当 n=3的时候,输出是这个样子的
out=["((()))","(()())","(())()","()(())","()()()"]

其实提议还是不比较好理解的,就是如果给你n对括号,那么有多少中组合的方式呢?

如果没有思路,我们就将所有的可能结果都写出来看看,看看之前的关系,从而能够帮助我们写出转移方程。

dq[0] = [""]
dq[1] = [()]
dq[2] = [()(),(())]
dq[3] = [((())),(()()),(())(),()(()),()()()]

我们观察一下看看有怎么样的规律呢?

dq[0]没什么可说的就是初始定义,dq[1]如何用dq[0]表示呢? 似乎应该是如下表示方法
(dq[0]),这个新的最外层的括号就是我们新加入的一个括号。是不是最终的转移方程呢? 先看看在说

比较麻烦的是dq[2]怎么表示呢? 我们看看如下的表达方式吧

dq[2]=(dq[1])+dq[0] =(())
dq[2]=(dq[0])+dq[1] = ()()

看起来是不是有点意思啦,不要高兴的太早咱们还是要看下dq[3]的。

dq[3]=(dq[2])+dq[0]=(()()) / ((()))
dq[3]=(dq[1])+dq[1]=(())() / ()(())
dq[3]=(dq[0])+dq[2]=()()()

这个时候是不是已经清晰了呢?

$$ dq[n]=dq[a]+dq[b] \\ a+b=n-1 $$

上面的公式就是转移方程,对于动态规划的方式一定是一个小问题逐渐合并成一个大问题的解,所以以后遇到类似的问题不妨写出来各个解真实值,然后使用小规模解表示大规模解找到规律似乎就能够解答这类问题,下面就直接上代码啦。

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        if n == 0:
            return []
        dq = list()
        dq.append(" ")
        for i in range(1, n + 1, 1):
            tmp = list()
            for j in range(0, i):
                part_0 = dq[j]
                part_1 = dq[i-j-1]
                for item in part_0:
                    for itm in part_1:
                        val = "(" + item + ")" + itm
                        val = val.replace(" ", "")
                        tmp.append(val)
            dq.append(tmp)
        return dq[n]
s = Solution()
print(s.generateParenthesis(3))

找零钱问题

有面值分别是1元,3元和5元的钱币,需要凑齐n元最少需要多少钱币呢?这就是经典的破零钱问题。
下面我就按照动态规划的思想来思考这个问题。

动态规划三要素

最优子结构

所谓最优子结构就是如何描述当前当前的最优解,一般我们通过枚举归纳来获取最优子结构。

如果想凑成0元钱币,使用$d(0)=0$
如果想凑成1元钱币,使用$d(1)=d(0)+0$
如果想凑成2元钱币,使用$d(2)=d(1)+1$
如果想凑成3元钱币,使用$d(3)=min\{d(2)+1, d(3-3=0)+1\}$
...

通过上面的归纳总结,我们发现最优子结构$d(i)=min\{d(i-1)+1, d(i-v_{j}+1)\}$就是$d(i)$, 这里的$d(i-v_{j})$表示枚举所有可能的情况,当然需要满足$i-v_{j}>=0$

边界条件

动态规划的边界条件一般就如递归的算法出口,从上面的枚举看,当时就是$d(0)=0$

状态转移公式

状态转移就是如上面枚举归纳总结的过程,在什么状态下需要引入更多的判断,和最优子结构一样,状态转移公式就是

$$ d(i)=min\{d(i-1)+1, d(i-v_{j})+1\} $$

实操练习

def dp(n):
    scheme = [0 for x in range(n)]
    money = [1, 3, 5]
    for i in range(1, n):
        scheme[i] = scheme[i - 1] + 1
        for idx, num in enumerate(money):
            if num > i:
                break
            if scheme[i - num] < scheme[i - 1]:  # 状态转移
                scheme[i] = scheme[i - num] + 1
    return scheme[n - 1]
n = 10
print(dp(n + 1))

对应上面的结构看我们的实现过程,把我们的归纳过程记录到scheme中,然后每次计算新的最优结构的时候,都参照之前的最优结构,就得到了最优的解。

# 面试 
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×