【算法设计与分析】动态规划-练习题

这篇具有很好参考价值的文章主要介绍了【算法设计与分析】动态规划-练习题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最长递增子序列

输入一个整数数组S[n],计算其最长递增子序列的长度,及其最长递增子序列。

算法一:L[k]

定义 k ( 1 ≤ k ≤ n ) k (1 ≤ k ≤ n) k(1kn),L[k]表示以 S[k] 结尾的递增子序列的最大长度。子问题即为 L[k]。
对于每一个k,我们都遍历前面0~k-1的所有的数,找出最大的L[i],且 S [ k ] > L [ i ] S[k]>L[i] S[k]>L[i],此时 L [ k ] = L [ j ] + 1 L[k]=L[j]+1 L[k]=L[j]+1。不断地维护当前的最大的递增子序列的长度。

转移方程:
【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
时间复杂度: 填表过程从1~n遍历一次 O ( n ) O(n) O(n),在填L[k]时遍历了一次L[1…k-1] O ( n ) O(n) O(n)。因此时间复杂度: T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)
空间复杂度: 创建了备忘录L[n],因此空间复杂度: S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)

代码:文章来源地址https://www.toymoban.com/news/detail-772283.html

int lis_dp1(const vector<int>& s, int n) {
    vector<int> Lis(s.size(),-1);
    int ans=1;
    for(int i=1;i<=n;i++){
        int cnt=1;
        for(int j=i-1;j>0;j--){
            if(s[i]>s[j]) cnt=max(cnt, Lis[j]+1);
        }
        Lis[i]=cnt;
        ans=max(ans, Lis[i]);
    }
    return ans;
}

算法一:L[k]的改进,求最长递增子序列

思路: 基于算法一得到最终的L,同时在填表过程中不断维护出现最长递增子序列的元素的下标index。i从下标index-1开始,不断往前遍历,当 s [ i ] < s [ i n d e x ] & & L [ i ] + 1 = = L [ i n d e x ] s[i]<s[index] \&\& L[i]+1==L[index] s[i]<s[index]&&L[i]+1==L[index]时,S[i]即为最长递增子序列的元素。

时间复杂度: 基于算法一进行改进,原时间复杂度 O ( n 2 ) O(n^2) O(n2)。但没有进行循环的再嵌套,只是在外层增加了一个while循环用于找到最长递增子序列,时间复杂度为 O ( n ) O(n) O(n)。因此总的时间复杂度为: T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

空间复杂度: 基于算法一进行改进,原空间复杂度 O ( n ) O(n) O(n)。只新创建了一个一维数组,空间复杂度为 O ( n ) O(n) O(n)。因此总的空间复杂度度为: S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)

代码:

vector<int> find_lis_dp1(const vector<int>& s, int n) {
    vector<int> Lis(s.size(),-1), L;
    int ans=1,index;
    for(int i=1;i<=n;i++){
        int cnt=1;
        for(int j=i-1;j>0;j--){
            if(s[i]>s[j]) cnt=max(cnt, Lis[j]+1);
        }
        Lis[i]=cnt;
        if(ans<Lis[i]){
            ans=Lis[i];
            index=i;
        }
    }
    L.push_back(s[index]);ans--;
    for(int i=index-1;ans>0;i--){
        if(s[i]<s[index]&&Lis[i]+1==Lis[index]){
            L.push_back(s[i]);
            ans--;
            index=i;
        }
    }
    reverse(L.begin(), L.end());
    return L;
}

算法二:L[i][j]

定义i和j ( 1 ≤ i ≤ j ≤ n 1 \leq i \leq j \leq n 1ijn),L(i, j)表示 S [ j . . . n ] S[j...n] S[j...n]中所有元素均大于 S [ i ] S[i] S[i]的最长公共子序列的长度。子问题即为L[i][j]。

分三种情况:
1、当 j > n j>n j>n 返回0。
2、当 S [ i ] > = S [ j ] S[i]>=S[j] S[i]>=S[j]时,此时 j   n j~n j n中均大于 S [ i ] S[i] S[i]的最长递增子序列一定不包含 S [ j ] S[j] S[j],因此返回 L ( i , j + 1 ) L(i, j+1) L(i,j+1)
3、 S [ i ] < S [ j ] S[i]<S[j] S[i]<S[j],此时要选择更长的递增子序列,返回 m a x ( L ( i , j + 1 ) , L ( j , j + 1 ) + 1 ) max(L(i, j+1), L(j,j+1)+1) max(L(i,j+1),L(j,j+1)+1)

转移方程:
【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
时间复杂度: 填表过程中,有两个参数,因此有两轮循环,因此时间复杂度:
T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

空间复杂度: 备忘录为一个二维的数组,因此空间复杂度:
S ( n ) = O ( n 2 ) S(n)=O(n^2) S(n)=O(n2)

代码:

int lis_dp2(const vector<int>& s, int n) {
    vector<vector<int>> Lis(s.size()+1,vector<int>(s.size()+1,0));
    for(int i=n;i>=0;i--){
        for(int j=n;j>=i;j--){
            if(s[i]>=s[j]) Lis[i][j]=Lis[i][j+1];
            else Lis[i][j]=max(1+Lis[j][j+1], Lis[i][j+1]);
        }
    }
    return Lis[0][1];
}

算法二:L[i][j]改进,求最长递增子序列

思路: 基于 算法二 得到最终的L。观察动态规划的转移方程可以知道,只有在 L [ i ] [ j ] = L [ j ] [ j + 1 ] + 1 L[i][j]=L[j][j+1]+1 L[i][j]=L[j][j+1]+1时,才会有递增子序列的序列长度的增加。因此利用该条件,定义i=0、j=1、len=L[0][1],从L[0][1]开始,如果 L i s [ j ] [ j + 1 ] = = l e n − 1 Lis[j][j+1]==len-1 Lis[j][j+1]==len1,那么该元素即为最长递增子序列中的元素,len自减,i更新为j,j更新为j+1,直至len变为0。

时间复杂度: 原时间复杂度为 O ( n 2 ) O(n^2) O(n2),修改之后只在最外层增加了一个循环,所以总的时间复杂度: T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

空间复杂度: 原空间复杂度为 O ( n ) O(n) O(n),只增加了一维的数组,因此总的空间复杂度度为: S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)

代码:

vector<int> find_lis_dp2(const vector<int>& s, int n) {
    vector<vector<int>> Lis(s.size()+1,vector<int>(s.size()+1,0));
    for(int i=n;i>=0;i--){
        for(int j=n;j>=i;j--){
            if(s[i]>=s[j]) Lis[i][j]=Lis[i][j+1];
            else Lis[i][j]=max(1+Lis[j][j+1], Lis[i][j+1]);
        }
    }
    int len=Lis[0][1],i=0,j=1;
    vector<int> L;
    while(len){
        if(Lis[j][j+1]==len-1){
            L.push_back(s[j]);
            i=j;j=j+1;
            len--;
        }else{
            j=j+1;
        }
    }
    return L;
}

算法三:L[k],存储长度为k的最小的数字

定义k, L [ k ] L[k] L[k]代表着长度为k的递增子序列中,最小元素的值。备忘录即为L。
L [ 1 ] < L [ 2 ] < L [ 3 ] . . . L[1] < L[2] < L[3]... L[1]<L[2]<L[3]...,当遍历到k时,此时我们只需要对比S[k]与L中各个值的大小。S[k]一定能构成一个递增子序列,我们要给S[k]在L中寻找一个位置,让L一直保持递增状态,直到S遍历完成。

时间复杂度: 时间花销主要在两个步骤:遍历S( O ( n ) O(n) O(n)),同时给S[k]在L中寻找合适的位置( O ( l o g n ) O(logn) O(logn))。二者是嵌套的,因此整体的时间复杂度: T ( n ) = O ( n l o g n ) T(n)=O(nlogn) T(n)=O(nlogn)

空间复杂度: 创建了一个备忘录用于存储元素值,因此空间复杂度为: S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)

代码:

int lis_dp3(const vector<int>& s, int n) {
    vector<int> Lis;
    for(int i=1;i<=n;i++){
        auto cnt = lower_bound(Lis.begin(),Lis.end(),s[i]);
        if(cnt==Lis.end()) Lis.push_back(s[i]);
        else Lis[cnt-Lis.begin()]=s[i];
    }
    return Lis.size();
}

算法三:L[k],改进

算法三中,L在更新的过程中会出现最长公共子序列的完整序列,因此只需要判断什么时候出现即可。当S[k]大于所有的L中的元素时,此时L中的所有元素构成的是从1~k的一个最长公共子序列。随着,不断地在L末尾添加元素,最后那一次在L末尾添加元素时,该序列即为我们要求的最长公共子序列。

时间复杂度: 原时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),为添加嵌套循环,所以总的时间复杂度: T ( n ) = O ( n l o g n ) T(n)=O(nlogn) T(n)=O(nlogn)

空间复杂度: 原空间复杂度为 O ( n ) O(n) O(n),只增加了一维的数组,因此总的空间复杂度度为: S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)

代码:

vector<int> find_lis_dp3(const vector<int>& s, int n) {
    vector<int> Lis, L;
    int index=0;
    for(int i=1;i<=n;i++){
        auto cnt = lower_bound(Lis.begin(),Lis.end(),s[i]);
        if(cnt==Lis.end()){
            Lis.push_back(s[i]);
            index=i;
        }
        else Lis[cnt-Lis.begin()]=s[i];
    }
    cout<<index<<endl;
    for(int i=1;i<=index;i++){
        auto cnt = lower_bound(L.begin(),L.end(),s[i]);
        if(cnt==L.end()) L.push_back(s[i]);
        else L[cnt-L.begin()]=s[i];
    }
    return L;
}

铺地毯

题目:
给定⼀个3n的棋盘, 设计⼀个动态规划算法计算有多少种使⽤12的⻣牌进⾏完美覆盖的⽅案。完美覆盖是指没有未覆盖的⽅格,也没有堆叠或者悬挂在棋盘外的⻣牌.

思路
首先如果棋盘的列数是奇数,那总的棋盘格子也是奇数,无法满足要求。因此只有列数是偶数时,才能求解方案数。

n = 2 n=2 n=2时, d p [ n ] = 3 dp[n]=3 dp[n]=3

n = 4 n=4 n=4时,我们也能很快的计算得到 d p [ n ] = 11 dp[n]=11 dp[n]=11,下面是一些例子:【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
在例子中,我们可以观察到一定的现象,示例图主要有两个类别:第一种:存在2*3的棋盘可以与剩下棋盘的骨牌区域分割开来、第二种:整个棋盘是一起的,不能分割成k*3的小块。

n = 6 n=6 n=6,绘制出部分实例。可以看到示例图主要分三个类别:1、分为3个23的子棋盘。2、分为一个23的子棋盘和一个43的整体的子棋盘。3、只有一个大的63的子棋盘。

将上述三种列别分别计算求和: 3 ∗ 3 ∗ 3 + 2 ∗ 3 ∗ 2 + 2 ∗ 1 = 41 3*3*3+2*3*2+2*1=41 333+232+21=41
【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划

n = 8 n=8 n=8时:根据示例观察,我们可以得到四个类别。1、最后的一个整体为23。2、最后的一个整体为43。3、最后的一个整体为63。4、最后的一个整体为83。

易证得,该四个类别之间没有交集,且其数量之和为解:
无交集:最后一个整体为23,与最后一个整体为43,此时都确定了一个小块区域与对方不一致,不管剩下的一块怎么放置,整体都不会完全一样。剩下的类别也是类似。
数量之和为解:8*3的放置方式可以看做是6*3的基础上多了一个2*3+4*3的基础上多了一个4*3+2*3的基础上多了一个6*3+8*3为一个整体的。这些类别其实就与上述示例图的类别一致。

【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
最终观察n=6、n=4都能不断地从后往前,确定整体小块棋盘的大小(从2开始,不断的增加,直至n)。

转移方程:
【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
时间复杂度: 需要两层遍历: T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

空间复杂度: 创建了一个一维的数组,因此空间复杂度为 S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)

代码:

#include <iostream>
#include <vector>
#include <algorithm>
#include <math.h>
using namespace std;


int countWays(int n) {
    if(n%2!=0) return 0;
    vector<int> v(n+1,0);
    v[0]=1;
    for(int i=2;i<=n;i+=2){
        int cnt = 3*v[i-2];
        for(int j=i-4;j>=0;j-=2){
            cnt+=v[j]*2;
        }
        v[i]=cnt;
    }
    return v[n];
}

int main() {
    int n = 6;
    cout << countWays(n) << endl;

    return 0;
}

棋盘放石子

题目:
一个4行n列的棋盘,每个正方形上都写着一个整数。有一组2n个鹅卵石,可以将所有或期中一部分鹅卵石放置在棋盘的正方形中(每个鹅卵石可以正好放在一个正方形上),以便最大化鹅卵石覆盖的正方形中的整数之和。
放置鹅卵石的方式有一个约束:为了使鹅卵石的放置合法,它们中的两个不能在水平或垂直相邻的正方形上(对角线相邻是可以的)。

思路:
对于4*n的棋盘,如果整体考虑,会很复杂。我们将每一列单独考虑,每一列只有四个值,要求每行每列的数都不相邻。最终每一列的情况如下:
【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
对上述八种情况进行表示,可以将这四个位置看做二进制的一维,组成一个四位二进制数,从上往下分别代表第一位、第二位以此类推。若放置石子(即为方块填蓝色),则对应二进制位为1。因此最终八种状态可以用如下数字表示:

0、1、2、4、5、7、8、9

对于不同列,如果这两列不相邻,则他们之间的选取并不会互相影响,因此只需要考虑相邻的情况:
【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
可以看到,满足要求相邻两列同一行是不会同时填蓝色的(基于列满足的情况)。将其转换成二进制运算,如果同一行均放置石子(图中均为蓝色),那么按位于的结果一定不为0,而如果满足题意,则按位与的情况是一定为0的,如第1和3张图。

转移方程:
【算法设计与分析】动态规划-练习题,算法设计与分析,算法,动态规划
时间复杂度: 需要有三层循环,但最里面两层循环均是有限次的,8*8=64次,因此时间复杂度为: T ( n ) = O ( 64 n ) T ( n ) = O ( n ) T(n)=O(64n)\\T(n)=O(n) T(n)=O(64n)T(n)=O(n)

空间复杂度: 创建了一个二维的数组,但第二维仅有8个数,因此空间复杂度为 S ( n ) = O ( 8 n ) S ( n ) = O ( n ) S(n)=O(8n)\\S(n)=O(n) S(n)=O(8n)S(n)=O(n)

代码:

#include<iostream>
#include<vector>
using namespace std;

int countNumber(vector<vector<int>>& checkBoard, int col,int n){
    int cnt=0,i=0;
    while(n){
        if(n&1) cnt+=checkBoard[i][col];
        n>>=1;
        i++;
    }
    return cnt;
}

int placePebbles(vector<vector<int>>& checkBoard){
    int n=checkBoard[0].size();
    vector<vector<int>> dp(n,vector<int>(7,0));
    vector<int> v={1,2,4,5,8,9,10};
    for(int i=0;i<n;i++){
        for(int j=0;j<7;j++){
            dp[i][j]=countNumber(checkBoard, i,v[j]);
        }
    }
    cout<<endl;
    for(int i=1;i<n;i++){
        for(int j=0;j<7;j++){
            int cnt=0;
            for(int k=0;k<7;k++){
                if((v[j]&v[k])!=0) continue;
                cnt=max(dp[i][j]+dp[i-1][k], cnt);
            }
            dp[i][j]=max(cnt, dp[i][j]);
        }
        cout<<endl;
    }
    int ans=0;
    for(int i=0;i<7;i++){
        ans=max(ans, dp[n-1][i]);
    }
    return ans;
}

int main(){
    vector<vector<int>> checkBoard={
        {1,2,3,-4},
        {1,2,3,4},
        {1,2,3,4},
        {1,2,3,-4}
    };
    cout<<placePebbles(checkBoard)<<endl;
    return 0;
}

到了这里,关于【算法设计与分析】动态规划-练习题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Matlab:遗传算法,模拟退火算法练习题

    1、遗传算法 (1) 遗传算法 是一种基于自然选择原理和自然遗传机 制的搜索(寻优)算法,它是模拟自然界中的生命进化机制,在人工系统中实现特定目 标的优化。遗传算法的实质是通过群体搜索技术,根据适者生存的原则逐代进化,最终 得到最优解或准最优解。它必须

    2024年01月21日
    浏览(32)
  • 拓扑排序 (算法思想+图解+模板+练习题)

    有向无环图一定是拓扑序列,有向有环图一定不是拓扑序列。 无向图没有拓扑序列。 首先我们先来解释一下什么是有向无环图: 有向就是我们两个结点之间的边是有方向的,无环的意思就是整个序列中没有几个结点通过边形成一个圆环。 下图就是一个有向无环图,它也一定

    2024年02月03日
    浏览(36)
  • C语言:指针【进阶】习题练习及分析讲解

    前言: 前面我们刚刚学完了C语言:指针详解【进阶】的知识,这部分的知识还是要重在理解加实践,今天我这里就分享一些有关C语言指针方面的练习供大家更深入的理解指针的知识。 我们初期的指针学习大部分都是与数组的知识绑定在一起的,所以今天的练习也是大多与数

    2024年02月02日
    浏览(37)
  • 数据结构与算法系列之习题练习

    💗 💗 博客:小怡同学 💗 💗 个人简介:编程小萌新 💗 💗 如果博客对大家有用的话,请点赞关注再收藏 🌞 括号匹配问题。 用队列实现栈。 用栈实现队列。 设计循环队列。 有效的括号 //用栈来实现 //左括号进栈 右括号出栈并销毁如果不匹配则return //设置两个队列,入栈

    2024年02月11日
    浏览(29)
  • 数据结构与算法--图(概念+练习题+解析)

    有向图 在有向图中有以下几点结论: 1.所有顶点的度数之和等于边数的二倍。 2.所有顶点的入度之和等于出度之和。 3.n个顶点的有向完全图有n(n-1)条边。 4.n个顶点的强连通图至少有n条边。 无向图 在无向图中有以下几点结论: 1.所有顶点的度数之和等于边数的二倍。 2.n个顶

    2024年02月04日
    浏览(32)
  • 数学建模算法与应用:预测算法(6)预测习题练习

    目录  一,水塔总水量以及流速预测问题         1.1、题目         1.2、建立模型         1.3、用MATLAB计算,将“-”替换为-1。         1.4、拟合法          二、预测产值问题         2.1、题目         2.2、建立模型  一,水塔总水量以及流速预测问题        

    2024年02月13日
    浏览(31)
  • Photoshop平面设计练习题(附答案)

    1.下列哪个是photoshop图像最基本的组成单元: C A. 节点 B. 色彩空间 C. 像素 D. 路径 2.下面对矢量图和像素图描述正确的是: C A. 矢量图的基本组成单元是像素 B. 像素图的基本组成单元是锚点和路径 C. Adobe Illustrator 9图形软件能够生成矢量图 D. Adobe photos

    2024年02月03日
    浏览(32)
  • 每天一道算法练习题--Day18 && 第一章 --算法专题 --- ----------前缀树

    字典树也叫前缀树、Trie。它本身就是一个树型结构,也就是一颗多叉树,学过树的朋友应该非常容易理解,它的核心操作是插入,查找。删除很少使用,因此这个讲义不包含删除操作。 截止目前(2020-02-04) 前缀树(字典树) 在 LeetCode 一共有 17 道题目。其中 2 道简单,8 个

    2024年02月02日
    浏览(37)
  • 2021级Java程序设计课程练习题

    1-1 抽象类是不能实例化的。  T   1-2 JAVA抽象类中一定含有抽象方法。  F   答题时没有看到一定qaq,抽象类不一定包含抽象方法,但包含抽象方法的类一定是抽象类。 2-2 有如下程序代码, 程序运行的结果是( )。 D.false true 第一个竟然是false!!! 使用“==”比较两个字符

    2023年04月23日
    浏览(32)
  • 每天一道算法练习题--Day22&& 第一章 --算法专题 --- ----------最大公约数

    关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。但是却有一些间接需要你求解最大公约数的题目。 时间复杂度:最好的情况是执行一次循环体,最坏的情况是循环到 smaller 为 1,因此总的时间复杂度为 O ( N ) O(N) O ( N ) ,其中 N 为 a 和

    2024年02月03日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包