动态规划

6 篇文章 0 订阅

解动态规划最重要的步骤个人觉得是找出描述子问题的数据结构,记住里面存储的是子问题的最终结果,而不是一个中间值。

问题一:硬币找零

假设有几种硬币,如1、3、5,并且数量无限。请找出能够组成某个数目的找零所使用最少的硬币数。

解:找子结构,设sum[N]为找N元所需的最小硬币数,则若最后一个是找的3元,则sum[N - 3]一定是找N-3元钱所需硬币数的最优解,即:
           sum[N] = min(sum[N-3],sum[N-1],sum[N-5]) + 1

//为了保证sum下标非负
sum[0] = 0
sum[1] = 1
sum[2] = 2
sum[3] = 1
sum[4] = 2
sum[5] = 1 
for i 6 to N:
    sum[i] = min(sum[i - 1],sum[i - 3],sum[i - 5]) + 1

上面并没有记录结果如何拿,若要记录,每次记下min(sum[i - 1],sum[i - 3],sum[i - 5])中最小的是1还是3还是5即可。

问题二:矩阵链乘

给定有n个要相乘的矩阵构成的序列(链)A1,A2,A3,…….,An>,要计算乘积A1A2…..An,给其画括号已减少计算乘法的次数。

解:找最优子结构,由于括号是可以任意划分的,并不一定从头开始,所以对于任意一段长度的Ai……..Aj,若有括号(Ai……Ak),(Ak……Aj),那么这2个子序列一定是计算乘法次数最少的(当然Ai…….Ak之间也能继续画括号)。即:
           sum[i][j] = min(sum[i][k] + sum[k][j] + Pi-1 * Pk *Pj) (k从i到j)

for i  0 to N:
    for j  i to N:
        for k  i to j:
            if(min < sum[i][k] + sum[k][j] + Pi-1 * Pk *Pj):
                    min = sum[i][k] + sum[k][j] + Pi-1 * Pk *Pj
                    index = k
        res[i][j] = min
        base[i][j] = index

最后再将res[0][N]打出来即可,并且通过k = base[0][N]将序列分成0 - k 和k - N然后不断逆归得到括号的位置。

问题三:捡苹果

平面上有N*M个格子,每个格子中放着一定数量的苹果。从左上角的格子开始, 每一步只能向下走或是向右走,每次走到一个格子就把格子里的苹果收集起来, 这样一直走到右下角,问最多能收集到多少个苹果。

解:找到最优子结构,由于只能下或者右,那么如果走到位置(i,j),那么其一定是要么从上面走来要么从左边走来,而且是走捡到苹果最大的,即
        res[i][j] = max(res[i - 1][j],res[i][j - 1]) + P[i][j]

for i 0 to N:
    for j to M:
        res[i][j] = max(res[i - 1][j],res[i][j - 1]) + P[i][j]

最终将res表中最大的打出即可,若要回溯得到路径,则每次记录max(res[i - 1][j],res[i][j - 1])中最大的那个,比如1:向上 2:向左

问题四:LCS

对于序列S和T,求它们的最长公共子序列。例如X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A}则它们的lcs是{B,C,B,A}和{B,D,A,B}。求出一个即可

解:最优子结构,证明在前缀中也有最长公共子串,设LCS[i][j]为最长公共子串的长度:
1、当stringa[i] = stringb[j]时,则LCS[i][j] = LCS[i - 1][j - 1] + 1,
2、当stringa[i] != stringb[j]时,则LCS[i][j] = max(LCS[i - 1][j],LCS[i][j - 1])

再用LCSpath记录每次选的方向是0、1、2即可回溯找到最长的子串

#这里的i和j都是前缀长度,故可以取到len
for i in range(0,len(stringa) + 1):
    for j in range(0,len(stringb) + 1):
        #初始化,有0的时候长度全为0
        if (i == 0) | (j == 0):
            LSCN[i][j] = 0
            LCS[i][j] = -1
            continue
        #这里的index才是数组下标
        IndexA = i - 1
        IndexB = j - 1
        #当相等的时候即+1
        if stringa[IndexA] == stringb[IndexB]:
            LSCN[i][j] = LSCN[i - 1][j - 1] + 1
            LCS[i][j] = 1
        #当不等的时候取目前公共子串较长的
        elif (LSCN[i - 1][j] > LSCN[i][j - 1]):
            LSCN[i][j] = LSCN[i - 1][j]
            LCS[i][j] = 0
        else:
            LSCN[i][j] = LSCN[i][j - 1]
            LCS[i][j] = 2

问题五:0-1背包

有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和

解:子结构为:
1、如果当前拿上这个物品是最优的GetiPath_Capacity[i][Cap],则在Cap = Cap - w[i]这个时候GetiPath_Capacity[i - 1][Cap - w[i]]是最优的。
2、如果最优解不包括第i个物品,那么GetiPath_Capacity[i - 1][Cap]就是最优的

状态转移方程即为
GetiPath_Capacity[i][Cap] = Max(GetiPath_Capacity[i - 1][Cap],GetiPath_Capacity[i][Cap - w[i]] + v[i])
GetiPath_Capacity[i - 1][Cap]:不拿第i个物体在Cap容量下的更好
GetiPath_Capacity[i - 1][Cap - w[i]] + v[i]:拿了第i个物体在Cap容量下最好,可以看到这里利用了前面的子结构.

#Capacity + 1是因为Cap是容量 不仅仅是数组下标,数组下标不含10
for Cap in range(0,Capacity + 1):
    for i in range(0,N):
        #保证Cap - w[i]大于0,这样才能装下东西
        if Cap - w[i] < 0:
            GetiPath_Capacity[i][Cap] = 0
            continue;
        if i == 0:
            GetiPath_Capacity[i][Cap] = v[i]
            isGetiPath[i][Cap] = 1
        if GetiPath_Capacity[i - 1][Cap] > GetiPath_Capacity[i - 1][Cap - w[i]] + v[i]:
            GetiPath_Capacity[i][Cap] = GetiPath_Capacity[i - 1][Cap]
        else:
            GetiPath_Capacity[i][Cap] = GetiPath_Capacity[i - 1][Cap - w[i]] + v[i]
            isGetiPath[i][Cap] = 1

问题六:字符串距离

对于不同的字符串,判断其相似度。
定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为:
  1.修改一个字符(如把“a”替换为“b”)
  2.增加一个字符(如把“abdd”变为“aebdd”)
  3.删除一个字符(如把“travelling”变为“traveling”)
定义:把这个操作所需要的最少次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数

解:LCS为其特殊情况,对于string a的i前缀和string b的j前缀:
1、stringa[i] == stringb[j],则Dis[i][j] = Dis[i - 1][j - 1]
2、stringa[i] != stringb[j],则Dis[i][j] = min(Dis[i - 1][j],Dis[i][j - 1]) + 1,因为不论是添加、删除还是改变操作,操作的值都是1,如果定义操作不同距离不同,则方程应该为:
Dis[i][j] = min(Dis[i - 1][j] + ActionCost[k]) k = 0………M

#1:a string 进行变更 2:b string进行变更 3: 都不变
for i in range(0,la + 1):
    for j in range(0,lb + 1):
        if i == 0:
            Dis[i][j] = j
            continue
        if j == 0:
            Dis[i][j] = i
            continue
        IndexA = i - 1
        IndexB = j - 1
        if stringa[IndexA] == stringb[IndexB]:
            Dis[i][j] = Dis[i - 1][j - 1]
            Action[i][j] = 3
        elif Dis[i - 1][j] > Dis[i][j - 1]:
            Dis[i][j] = Dis[i][j - 1] + 1
            Action[i][j] = 2
        else:
            Dis[i][j] = Dis[i - 1][j] + 1
            Action[i][j] = 1

Action记录如何改变的,最后可以打出变化序列,1:a string 进行变更 2:b string进行变更 3: 都不变

i = lengtha
j = lengthb
while (i != 0) & (j != 0):
    c = Action[i][j]
    print(c)
    if c == 1:
        i -= 1
    elif c == 2:
        j -= 1
    elif c == 3:
        i -= 1
        j -= 1
    else:
        i = -1

问题七:二插搜索树个数问题

给一个数n,问能建多少种不同的二插搜索树,eg:
这里写图片描述

解:假设sum[n]保存数字为n时的数目,那么可以看到当n = n+1时,加上了n+1这个最大的数,这个数可以:
1、当树根 = sum[n - 1] (直接放root,下面都不变)
2、当树叶 = sum[n - 1] (直接插入n-1时刻的每一个树,由于n最大,肯定是成叶子节点)
3、当中间节点 = sum[i] * sum[n - i - 1](由于是搜索树,所以放中间的时候其上面和下面部分都一定是连续的,即上面是1 to i,下面是i + 1 to n - 1)
公式: sum[n] = sum[n - 1] * 2 + sum[i] * sum[n - i - 1] (i 1 to n - 2)
注意i不能为n-1,否则n就成叶子了

        for(int j = 1; j <= n; j++)
        {
            if(j == 1)
            {
                sum[j] = 1;
                continue;
            }
            int midSum = 0;
            for(int i = 1; i < j - 1; i++)
                midSum += sum[i] * sum[j - i - 1];
            sum[j] = midSum + sum[j - 1] * 2;
        }

Ps:这里还有一个打印上面所有BST的题目,代码如下,主要复习下树的遍历以及指针和链表的使用:

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

void TreeTraversalCopy(TreeNode *p, TreeNode **nowp)
{
    if (p == NULL)
    {
        *nowp = NULL;
        return;
    }
    *nowp = (TreeNode*)malloc(sizeof(TreeNode));
    (*nowp)->val = p->val;
    TreeTraversalCopy(p->left,&((*nowp)->left));
    TreeTraversalCopy(p->right, &((*nowp)->right));
}

void TreeTraversalChange(TreeNode **p, int i)
{
    if (*p == NULL)
        return;
    (*p)->val = (*p)->val + i - 1;
    TreeTraversalChange(&((*p)->left), i);
    TreeTraversalChange(&((*p)->right), i);
}

TreeNode * FindMostRight(TreeNode * p, int val)
{
    TreeNode * temp = p;
    while (p->val != val)
        p = p->right;
    return p;
}
class Solution {
public:
    vector<TreeNode*> generateTrees(int n) {
        int sum[1000];
        vector<vector<TreeNode*>> pTreeSet;
        vector<TreeNode*> temp;
        pTreeSet.push_back(temp);
        for (int j = 1; j <= n; j++)
        {
            vector<TreeNode*> temp;
            pTreeSet.push_back(temp);
            if (j == 1)
            {
                TreeNode *p = (TreeNode*)malloc(sizeof(TreeNode));
                sum[j] = 1;
                p->val = j;
                p->left = NULL;
                p->right = NULL;
                pTreeSet[j].push_back(p);
                continue;
            }
            int midSum = 0;
            //n在中间
            for (int i = 1; i < j - 1; i++)
            {

                for (int k = 0; k < sum[i]; k++)
                    for (int m = 0; m < sum[j - i - 1]; m++)
                    {
                        TreeNode *p = (TreeNode*)malloc(sizeof(TreeNode));
                        p->val = j;
                        p->left = NULL;
                        p->right = NULL;
                        TreeNode *pUpRoot = NULL, *pDownRoot = NULL;
                        TreeTraversalCopy(pTreeSet[i][k], &pUpRoot);
                        TreeTraversalCopy(pTreeSet[j - i - 1][m], &pDownRoot);
                        TreeTraversalChange(&pDownRoot, i + 1);
                        TreeNode *iter = FindMostRight(pUpRoot, i);
                        iter->right = p;
                        p->left = pDownRoot;
                        pTreeSet[j].push_back(pUpRoot);
                    }
                midSum += sum[i] * sum[j - i - 1];
            }
            for (int i = 0; i < sum[j - 1]; i++)
            {
                //n在根节点,子树不用复制
                TreeNode *pRoot = (TreeNode*)malloc(sizeof(TreeNode));
                pRoot->val = j;
                pRoot->left = NULL;
                pRoot->right = NULL;
                (*pRoot).left = pTreeSet[j - 1][i];
                pTreeSet[j].push_back(pRoot);
                //n在叶子,需要将树重新复制一遍
                TreeNode *pleafRoot = NULL;
                TreeNode *pLeaf = (TreeNode*)malloc(sizeof(TreeNode));
                pLeaf->val = j;
                pLeaf->left = NULL;
                pLeaf->right = NULL;
                TreeTraversalCopy(pTreeSet[j - 1][i], &pleafRoot);
                TreeNode *iter = FindMostRight(pleafRoot, j - 1);
                iter->right = pLeaf;
                pTreeSet[j].push_back(pleafRoot);
            }
            sum[j] = midSum + sum[j - 1] * 2;
        }
        return pTreeSet[n];
    }
};

问题八:丑数字问题

丑数字:即质因数只有2、3、5的数,1也算,输入n,请给出第n个丑数字。
eg:n = 10 丑数字为:1, 2, 3, 4, 5, 6, 8, 9, 10, 12

解:直接一个个判断太慢,由于这样的数字为少数,所以我们找出他们再排序,可以发现这些数字都是由2,3,5乘起来的,于是我们可以建立3个list L2、L3、L5,分别表示由2,3,5所乘起来的数。注意:这里的子问题是:
         Dp[i] = min(Dp[L2] * 2,Dp[L3] * 3,Dp[L5] * 5)

        UglyNumSet = [1]
        count = 1
        L2 = L3 = L5 = 0
        while(count < n):
            PredictL2 = UglyNumSet[L2] * 2
            PredictL3 = UglyNumSet[L3] * 3
            PredictL5 = UglyNumSet[L5] * 5
            if PredictL2 < PredictL3:
                if PredictL2 < PredictL5:
                    if PredictL2 == UglyNumSet[count - 1]:
                        L2 += 1
                        continue
                    L2 += 1
                    UglyNumSet.append(PredictL2)
                    count += 1
                    continue
                else:
                    if PredictL5 == UglyNumSet[count - 1]:
                        L5 += 1
                        continue
                    L5 += 1
                    UglyNumSet.append(PredictL5)
                    count += 1
                    continue
            if PredictL3 < PredictL5:
                if PredictL3 == UglyNumSet[count - 1]:
                    L3 += 1
                    continue
                L3 += 1
                UglyNumSet.append(PredictL3)
                count += 1
            else:
                if PredictL5 == UglyNumSet[count - 1]:
                    L5 += 1
                    continue
                L5 += 1
                UglyNumSet.append(PredictL5)
                count += 1
        return UglyNumSet[n - 1]  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值