• C++【算法】【动态规划问题】


    目录

    一、斐波那契数列

    二、字符串分割

    三、三角矩阵​​​​​​​

    四、路径总数​​​​​​​

    五、最小路径和

    六、背包问题

    七、回文串分隔

    八、编辑距离

    九、不同子序列


    动态规划是在将大问题转化为小问题的分治过程中,保存对这些小问题已经处理好的结果,并提供给后面处理更大的问题来使用这些结果

    动态规划的三个特点

    1.把原来的问题分解成了几个相似的子问题

    2.所有的子问题都只需要解决一次

    3.存储子问题的解

    动态规划的本质,是对问题状态的定义和状态转移方程的定义(状态以及状态之间的递推关系)

    动态规划问题一般从以下四个角度考虑:

    1.状态定义

    2.状态间的转移方程定义(从题目中找线索)

    3.状态的初始化

    4.返回结果

    状态定义的要求:定义的状态一定要形成递推关系

    难点:状态比较难以定义,转移方程不好找。

    适用场景:最大值/最小值,可不可行,是不是,方案个数。

    一、斐波那契数列

    牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决_牛客网

    描述

    大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。 

    斐波那契数列是一个满足 fib(x)={1x=1,2fib(x−1)+fib(x−2)x>2fib(x)={1fib(x−1)+fib(x−2)​x=1,2x>2​ 的数列 

    数据范围:1≤n≤401≤n≤40

    要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n) ,本题也有时间复杂度 O(logn)O(logn) 的解法 

    输入描述:

    一个正整数n

    返回值描述:

    输出一个正整数。

    示例1

    输入:

    4

    复制返回值:

    3

    复制说明:

    根据斐波那契数列的定义可知,fib(1)=1,fib(2)=1,fib(3)=fib(3-1)+fib(3-2)=2,fib(4)=fib(4-1)+fib(4-2)=3,所以答案为3。   

    示例2

    输入:

    1

    复制返回值:

    1

    复制

    示例3

    输入:

    2

    复制返回值:

    1

    递归的解法 

    1. class Solution {
    2. public:
    3. int Fibonacci(int n) {
    4. if(n==0)
    5. return 0;
    6. if(n==1||n==2)
    7. return 1;
    8. return Fibonacci(n-1)+Fibonacci(n-2);
    9. }
    10. };

    如果这一个n非常大的话,上面递归的算法的时间复杂度会非常大,一次递归分解成两个,两次分解成四个,复杂度呈指数级增长。

    状态F(i):第i项的值

    状态转移方程(从已知的结果推导未知的结果):F(i)=F(i-1)+F(i-2)

    初始状态:F(0)=0,F(1)=1

    求解:第n项的结果

    返回结果F(n):F(n)

    1. class Solution {
    2. public:
    3. int Fibonacci(int n) {
    4. //创建一个数组,保存中间状态的解
    5. int * F=new int[n+1];
    6. //初始化F(0)和F(1)
    7. F[0]=0;
    8. F[1]=1;
    9. //F[i]=F[i-1]+F[i-2]
    10. for(int i=2;i<=n;++i)
    11. {
    12. F[i]=F[i-1]+F[i-2];
    13. }
    14. //返回结果
    15. return F[n];
    16. }
    17. };

    上面是数组的写法,还可以进一步优化空间复杂度至O(1)

    1. class Solution {
    2. public:
    3. int Fibonacci(int n) {
    4. if(n==0||n==1)
    5. {
    6. return n;
    7. }
    8. int fn;
    9. int fn1=1;
    10. int fn2=0;
    11. for(int i=2;i<=n;++i)
    12. {
    13. fn=fn1+fn2;
    14. //更新中间状态
    15. fn2=fn1;
    16. fn1=fn;
    17. }
    18. return fn;
    19. }
    20. };

    二、字符串分割

    拆分词句_牛客题霸_牛客网

    给定一个字符串s和一组单词dict,判断s是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是dict中的单词(序列可以包含一个或多个单词)。 

    例如:
    给定s=“nowcode”;
    dict=["now", "code"].
    返回true,因为"nowcode"可以被分割成"now code".

    问题:字符串s是否可以被分割  -->抽象状态

    状态:字符串的前i个字符是否可以被分割

    F(3):前三个字符是否可以被分割:true

    F(7):F(3)&&[4,7]是否可以在词典中找到 

    F(1)&&[2,7]

    F(2)&&[3,7]

    F(3)&&[4,7]

    F(4)&&[5,7]

    F(5)&&[6,7]

    F(6)&&[7,7]

    [0,0]&&[1,8] -->整体能不能被匹配到

     状态转移方程:F(i) :j

    初始状态:F(0)

    返回结果:F(字符串长度):f(s.size()) f(s.size())

    1. class Solution {
    2. public:
    3. bool wordBreak(string s, unordered_set& dict) {
    4. if (s.empty()) {
    5. return false;
    6. }
    7. if (dict.empty()) {
    8. return false;
    9. }
    10. vector<bool> can_break(s.size() + 1, false);
    11. // 初始化F(0) = true
    12. can_break[0] = true;
    13. for (int i = 1; i <= s.size(); i++) {
    14. for (int j = i - 1; j >= 0; j--) {
    15. // F(i): true{j
    16. // 第j+1个字符的索引为j
    17. if (can_break[j] && dict.find(s.substr(j, i - j)) != dict.end()) {
    18. can_break[i] = true;
    19. break;
    20. }
    21. }
    22. }
    23. return can_break[s.size()];
    24. }
    25. };

    三、三角矩阵

    三角形_牛客题霸_牛客网

    描述

    给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字, 

    例如,给出的三角形如下: 

    [[20],[30,40],[60,50,70],[40,10,80,30]]

    也就是:

                            [[20],

                          [30,40],

                        [60,50,70],

                     [40,10,80,30]]

    最小的从顶部到底部的路径和是20 + 30 + 50 + 10 = 110。注意: 

    如果你能只用O(N)的额外的空间来完成这项工作的话,就可以得到附加分,其中N是三角形中的行总数。

    从上往下走

    问题:从顶部到底部的最小路径和

    状态F(i,j):从(0,0)到(i,j)的最小路径和

    状态转移方程:

    (0

    F(i,j)=min(F(i-1,j-1),F(i-1,j))+array[i][j]

    (j==0||j==i):F(i,j)

            j==0:F(i-1,0)+array[i][0]

            j==i:F(i-1,j-1)+array[i][j]

    F(1,0):F(0,0)+array[1][0]      F(1,1):F(0,0)+array[1][1]

    F(2,1):min(F(1,0),F(1,1))+array[2][1]

     (i,j)-->(i+1,j)和(i+1,j+1)

    (i-1,j-1)和(i-1,j) --->(i,j)

    初始状态:F(0,0)=array[0][0]

    返回结果:min(F(row-1,j))

    1. class Solution {
    2. public:
    3. int minimumTotal(vectorint> > &triangle) {
    4. if(triangle.empty())
    5. return 0;
    6. int row=triangle.size();
    7. int col=triangle[0].size();
    8. //第0行不用考虑,考虑从1开始
    9. for(int i=1;i
    10. {
    11. for(int j=0;j<=i;++j)
    12. {
    13. //处理边界的情况
    14. //因为边界的情况只有一条路可以走
    15. if(j==0)
    16. {
    17. triangle[i][j]=triangle[i-1][j]+triangle[i][j];
    18. }
    19. else if(j==i)
    20. {
    21. triangle[i][j]=triangle[i-1][j-1]+triangle[i][j];
    22. }
    23. //三角形非边界的情况
    24. else
    25. {
    26. triangle[i][j]=min(triangle[i-1][j-1],triangle[i-1][j])+triangle[i][j];
    27. }
    28. }
    29. }
    30. int minSum=triangle[row-1][0];
    31. for(int j=1;j
    32. minSum=min(minSum,triangle[row-1][j]);
    33. return minSum;
    34. }
    35. };

    从下往上走的情况

    状态F(i,j):从(i,j)到达最后一行的最小路径和

    状态转移方程:F(i,j):min(F(i+1,j),F(i+1,j+1))+array[i][j]

    初始状态:F(row-1,j)=array[row-1][j]

    返回结果:F(0,0)

    1. class Solution {
    2. public:
    3. int minimumTotal(vectorint> > &triangle) {
    4. if(triangle.empty())
    5. return 0;
    6. //F(row-1,j)=array[row-1][j]
    7. int row=triangle.size();
    8. for(int i=row-2;i>=0;--i)
    9. {
    10. for(int j=0;j<=i;++j)
    11. {
    12. triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
    13. }
    14. }
    15. return triangle[0][0];
    16. }
    17. };

    四、路径总数

    不同路径的数目(一)_牛客题霸_牛客网

    描述

    一个机器人在m×n大小的地图的左上角(起点)。 

    机器人每次可以向下或向右移动。机器人要到达地图的右下角(终点)。

    可以有多少种不同的路径从起点走到终点? 

    备注:m和n小于等于100,并保证计算结果在int范围内 

    数据范围:0

    要求:空间复杂度 O(nm)O(nm),时间复杂度 O(nm)O(nm)

    进阶:空间复杂度 O(1)O(1),时间复杂度 O(min(n,m))O(min(n,m))

    示例1

    输入:

    2,1

    复制返回值:

    1
    

    复制

    示例2

    输入:

    2,2

    复制返回值:

    2

    状态F(i,j):从(0,0)到达(i,j)的路径个数

    状态转移方程:F(i,j): F(i,j-1)+F(i-1,j)

    初始状态:F(i,0)=F(0,j)=1

    返回:F(m-1,n-1)

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param m int整型
    6. * @param n int整型
    7. * @return int整型
    8. */
    9. int uniquePaths(int m, int n) {
    10. if (m < 1 || n < 1) {
    11. return 0;
    12. }
    13. // 申请F(i, j)空间,初始化
    14. vectorint> > ret(m, vector<int>(n, 1));
    15. for (int i = 1; i < m; ++i) {
    16. for (int j = 1; j < n; ++j) {
    17. // F(i,j) = F(i-1,j) + F(i,j-1)
    18. ret[i][j] = ret[i - 1][j] + ret[i][j - 1];
    19. }
    20. }
    21. return ret[m - 1][n - 1];
    22. }
    23. };

    五、最小路径和

    带权值的最小路径和_牛客题霸_牛客网

    描述

    给定一个由非负整数填充的m x n的二维数组,现在要从二维数组的左上角走到右下角,请找出路径上的所有数字之和最小的路径。
    注意:你每次只能向下或向右移动。

    示例1

    输入:

    [[1,2],[5,6],[1,1]]

    复制

    返回值:

    状态F(i,j):从(0,0)到达(i,j)的最短路径和

    转移方程:

            F(i,j):min(F(i,j-1),F(i-1,j))+array[i][j]

            第一行:

            F(0,j):F(0,j-1)+array[0][j]

            第一列

             F(i,0):F(i-1,0)+array[i][0]

    初始状态:

            F(0,0)=array[0][0]

    返回:F(row-1,col-1)

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param grid int整型vector>
    6. * @return int整型
    7. */
    8. int minPathSum(vectorint> >& grid) {
    9. // write code here
    10. if(grid.size()==0)
    11. {
    12. return 0;
    13. }
    14. int row=grid.size();
    15. int col=grid[0].size();
    16. //第一行
    17. for(int i=0;i
    18. {
    19. grid[0][i]=grid[0][i-1]+grid[0][i];
    20. }
    21. //第一列
    22. for(int i=1;i
    23. {
    24. grid[i][0]=grid[i-1][0]+grid[i][0];
    25. }
    26. for(int i=1;i
    27. {
    28. for(int j=1;j
    29. {
    30. grid[i][j]=min(grid[i][j-1],grid[i-1][j])+grid[i][j];
    31. }
    32. }
    33. return grid[row-1][col-1];
    34. }
    35. };

    六、背包问题

     LintCode 炼码

    描述

    有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.

    问最多能装入背包的总价值是多大?

    1. A[i], V[i], n, m 均为整数
    2. 你不能将物品进行切分
    3. 你所挑选的要装入背包的物品的总大小不能超过 m
    4. 每个物品只能取一次
    5. m<=1000m<=1000\

    len(A),len(V)<=100len(A),len(V)<=100

    样例 1:

    输入:

     
     
    1. m = 10
    2. A = [2, 3, 5, 7]
    3. V = [1, 5, 2, 4]

    输出:

     
     
    9

    解释:

    装入 A[1] 和 A[3] 可以得到最大价值, V[1] + V[3] = 9

    样例 2:

    输入:

     
     
    1. m = 10
    2. A = [2, 3, 8]
    3. V = [2, 5, 8]

    输出:

     
     
    10

    价值大,体积大

    价值大,体积小

    价值小,体积大

    价值小,体积小

    状态F(i,j):从前i个商品中选择,包的大小为j时的最大值 

    状态索引范围:从1开始,价值数组,大小数组索引范围:从0开始

    状态转移方程:

    如果第i个商品的大小A[i-1]<=j,就可以将商品放进去(j是我们当前包剩余的大小)

            F(i,j):F(i-1)+V[i-1]

    第i个商品可以放入大小为j的包中:

            F(i, j) = max{F(i-1,j), F(i-1, j - A[i]) + V[i]}
            F(i-1,j): 表示不把第i个物品放入背包中, 所以它的价值就是前i-1个物品放入大小为j的背包的最大价值
            F(i-1, j - A[i]) + V[i]:表示把第i个物品放入背包中,价值增加V[i],但是需要腾出j - A[i]的大小放第i个商品。

    第i个商品太大,(A[i-1]>j)大小为j的包放不下第i个商品

            F(i,j)=F(i-1,j)

    初始状态:

            第零行表示我现在没有放置任何的商品,第零列表示我现在的包里面放不下任何的商品

            F(i,0)=0

            F(0,j)=0

    采用二维数组的写法 

    1. int backPackII(int m, vector<int> A, vector<int> V) {
    2. if (A.empty() || V.empty() || m < 1) {
    3. return 0;
    4. }
    5. //多加一行一列,用于设置初始条件
    6. const int N = A.size() + 1;
    7. const int M = m + 1;
    8. vectorint> > result;
    9. result.resize(N);
    10. //初始化所有位置为0,第一行和第一列都为0,初始条件
    11. for (int i = 0; i != N; ++i) {
    12. result[i].resize(M, 0);
    13. }
    14. for (int i = 1; i < N; ++i) {
    15. for (int j = 1; j != M; ++j) {
    16. //第i个商品在A中对应的索引为i-1: i从1开始
    17. //如果第i个商品大于j,说明放不下, 所以(i,j)的最大价值和(i-1,j)相同
    18. if (A[i - 1] > j) {
    19. result[i][j] = result[i - 1][j];
    20. }
    21. //如果可以装下,分两种情况,装或者不装
    22. //如果不装,则即为(i-1, j)
    23. //如果装,需要腾出放第i个物品大小的空间: j - A[i-1],装入之后的最大价值即为(i - 1, j- A[i-1]) + 第i个商品的价值V[i - 1]
    24. //最后在装与不装中选出最大的价值
    25. else {
    26. int newValue = result[i - 1][j - A[i - 1]] + V[i - 1];
    27. result[i][j] = max(newValue, result[i - 1][j]);
    28. }
    29. }
    30. }
    31. //返回装入前N个商品,物品大小为m的最大价值
    32. return result[N - 1][m];
    33. }

     采用一维数组的写法

    1. #include
    2. using namespace std;
    3. int backPackII(int m, vector<int> A, vector<int> V) {
    4. if (A.empty() || V.empty() || m < 1) {
    5. return 0;
    6. }
    7. const int N = A.size();
    8. //多加一列,用于设置初始条件,因为第一件商品要用到前面的初始值
    9. const int M = m + 1;
    10. vector<int> result;
    11. //初始化所有位置为0,第一行都为0,初始条件
    12. result.resize(M, 0);
    13. //这里商品的索引位置不需要偏移,要和未优化的方法区分开
    14. //这里的i-1理解为上一行,或者未更新的一维数组值
    15. for (int i = 0; i != N; ++i) {
    16. for (int j = M - 1; j > 0; --j) {
    17. //如果第i个商品大于j,说明放不下, 所以(i,j)的最大价值和(i-1,j)相同
    18. if (A[i] > j) {
    19. result[j] = result[j];
    20. }
    21. //如果可以装下,分两种情况,装或者不装
    22. //如果不装,则即为(i-1, j)
    23. //如果装,需要腾出放第i个物品大小的空间: j - A[i],装入之后的最大价值即为(i - 1, j -
    24. A[i-1]) + 第i个商品的价值V[i]
    25. //最后在装与不装中选出最大的价值
    26. else {
    27. int newValue = result[j - A[i]] + V[i];
    28. result[j] = max(newValue, result[j]);
    29. }
    30. }
    31. }
    32. //返回装入前N个商品,物品大小为m的最大价值
    33. return result[m];
    34. }

    七、回文串分隔

    分割回文串-ii_牛客题霸_牛客网

    描述

    给出一个字符串s,分割s使得分割出的每一个子串都是回文串 

    计算将字符串s分割成回文分割结果的最小切割数 

    例如:给定字符串s="aab",

    返回1,因为回文分割结果["aa","b"]是切割一次生成的。

    示例1

    输入:

    "aab"

    复制返回值:

    1
    

    问题:s的最小分割次数

    状态F(i):s的前i个字符最小的分割次数

    状态转移方程:(移步转移只能切一次)

            F(i):j

            F(i):[1,i]是回文串:0


    “aab”

    F(1):0

    F(2):整体0

    F(3):F(2)(前面两个字符最小分割次数已经决定了,我F(3)不用管,就直接在F(2)后面再切一刀就行)(aa | b)

    初始状态:

            F(i)=i-1(i个字符最多分割i-1次)

            

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param s string字符串
    6. * @return int整型
    7. */
    8. bool isPal(string& s,int start,int end)
    9. {
    10. while(start
    11. {
    12. if(s[start]!=s[end])
    13. {
    14. return false;
    15. }
    16. ++start;
    17. --end;
    18. }
    19. return true;
    20. }
    21. int minCut(string s) {
    22. // write code here
    23. int len=s.size();
    24. if(len==0)
    25. return 0;
    26. //判断整体是不是一个回文串
    27. if(isPal(s,0,len-1))
    28. return 0;
    29. vector<int> minCut(len+1);
    30. //F(i)=i-1
    31. //5个字符中间最多分割4次
    32. for(int i=1;i<=len;++i)
    33. minCut[i]=i-1;
    34. for(int i=2;i<=len;++i)
    35. {
    36. //整体是否为回文串
    37. if(isPal(s,0,i-1))
    38. {
    39. minCut[i]=0;
    40. continue;
    41. }
    42. // j
    43. for(int j=1;j
    44. {
    45. if(isPal(s,j,i-1))
    46. {
    47. minCut[i]=min(minCut[i],minCut[j]+1);
    48. }
    49. }
    50. }
    51. return minCut[len];
    52. }
    53. };

    或者将0的位置也利用起来,写下面这种写法,就将整体是否为回文串和局部是否为回文串的情况归并到一起了

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param s string字符串
    6. * @return int整型
    7. */
    8. bool isPal(string& s,int start,int end)
    9. {
    10. while(start
    11. {
    12. if(s[start]!=s[end])
    13. {
    14. return false;
    15. }
    16. ++start;
    17. --end;
    18. }
    19. return true;
    20. }
    21. int minCut(string s) {
    22. // write code here
    23. int len=s.size();
    24. if(len==0)
    25. return 0;
    26. if(isPal(s,0,len-1))
    27. return 0;
    28. vector<int> minCut(len+1);
    29. //F(i)=i-1
    30. for(int i=0;i<=len;++i)
    31. minCut[i]=i-1;
    32. for(int i=2;i<=len;++i)
    33. {
    34. // j
    35. for(int j=0;j
    36. {
    37. if(isPal(s,j,i-1))
    38. {
    39. minCut[i]=min(minCut[i],minCut[j]+1);
    40. }
    41. }
    42. }
    43. return minCut[len];
    44. }
    45. };

     但是上面的主代码中存在O(n^2),并且其中调用的判断是否为回文串还有一层循环,为O(N^3)

    我们可以保存这个区间是否为回文串

    用一个n*n的方阵去保存下来,用空间换时间

    判断回文串:

            状态F(i,j):区间[i,j]是否为回文串

            状态转移方程:F(i,j):s[i]==s[j] &&F(i+1,j-1)

            i==j:

                    F(i,j):true

            i+1==j:

                    F(i,j):s[i]==s[j]

    初始状态:

            i==j:

            F(i,j):true

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param s string字符串
    6. * @return int整型
    7. */
    8. bool isPal(string& s,int start,int end)
    9. {
    10. while(start
    11. {
    12. if(s[start]!=s[end])
    13. {
    14. return false;
    15. }
    16. ++start;
    17. --end;
    18. }
    19. return true;
    20. }
    21. vectorbool>> getMat(string & s)
    22. {
    23. int n=s.size();
    24. vectorbool>> Mat(n,vector<bool>(n,false));
    25. for(int i=n-1;i>=0;--i)
    26. {
    27. for(int j=i;j
    28. {
    29. if(j==i)
    30. {
    31. Mat[i][j]=true;
    32. }
    33. else if(j==i+1)
    34. {
    35. Mat[i][j]=s[i]==s[j];
    36. }
    37. else
    38. {
    39. Mat[i][j]=(s[i]==s[j])&&(Mat[i+1][j-1]);
    40. }
    41. }
    42. }
    43. return Mat;
    44. }
    45. int minCut(string s) {
    46. // write code here
    47. int len=s.size();
    48. if(len==0)
    49. return 0;
    50. if(isPal(s,0,len-1))
    51. return 0;
    52. vector<int> minCut(len+1);
    53. //F(i)=i-1
    54. vectorbool>> Mat=getMat(s);
    55. for(int i=0;i<=len;++i)
    56. minCut[i]=i-1;
    57. for(int i=2;i<=len;++i)
    58. {
    59. // j
    60. for(int j=0;j
    61. {
    62. if(Mat[j][i-1])
    63. {
    64. minCut[i]=min(minCut[i],minCut[j]+1);
    65. }
    66. }
    67. }
    68. return minCut[len];
    69. }
    70. };

    八、编辑距离

    编辑距离_牛客题霸_牛客网

    描述

    给定两个单词word1和word2,请计算将word1转换为word2至少需要多少步操作。
    你可以对一个单词执行以下3种操作:
    a)在单词中插入一个字符
    b)删除单词中的一个字符
    c)替换单词中的一个字符

    示例1

    输入:

    "b",""

    复制返回值:

    1
    

    复制

    示例2

    输入:

    "ab","bc"

    复制返回值:

     问题:word1到word2编辑距离

    子问题:word1的局部变成word2局部需要的编辑距离

    状态(i,j):word1前i个字符到word2的前j个字符的编辑距离

    状态转移方程:

            F(i,j):min(插入,删除,替换)

                        min(F(i-1,j)+1,F(i,j-1)+1,F(i-1,j-1)+(w1[i]==w2[j]?0:1))

                        F(i,j-1):(w1[1,i]-->w2[1,j-1]+插入-->F(i,j-1)+1-->F(i,j)

                        F(i-1,j):w1[i,i-1]-->w2[1,j]+删除w1的第i个字符-->F(i-1,j)+1-->F(i,j)

                        F(i-1,j-1):w1[1,i-1]->w2[1,j-1]+替换:w1[i]!=w2[j]-->F(i-1,j-1)+(w1[i]==w2[j]?0:1)

    r:替换

    i:插入

    d:删除

    ""keji
    ""

    i:"" ""

    F(0,0):0

    i:""+k-->k

    F(0,1):1

    i:""+ke

    F(0,2):2

    i:""+kej

    F(0,3):3

    i:""+keji

    F(0,4):4

    b

    d:b-b-->""

    F(1,0):1

    d:F(0,1)-->(删除b):b-->k

    i : F(1,0)-->(插入k):b-->k

    r:F(0,0)-->(b替换k):b-->k

    F(1,1):1

    d:F(0,2)-->(删除b):b-->ke

    i :F(1,1)-->(插入e):b-->ke

    r:F(0,1)-->(b替换e):b-->ke

    F(1,2):2

    i

    d:bi-bi-->""

    F(2,0):2

    d:F(1,1)-->(删除i):bi-->k

    i :F(2,0)-->(插入k):bi-->k

    r:F(1,0)-->(i替换k):bi-->k

    F(2,1):2

    d:F(1,2)-->(删除i):bi-->ke

    i :F(2,1)-->(插入e):b-->k

    r:F(1,1)-->(i替换e):bi-->ke

    F(2,2):2(两次替换)

    t

    d:bit-bit-->""

    F(3,0):3

    e

    d:bite-bite-->""

    F(4,0):4

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param word1 string字符串
    6. * @param word2 string字符串
    7. * @return int整型
    8. */
    9. int minDistance(string word1, string word2) {
    10. // write code here
    11. int row=word1.size();
    12. int col=word2.size();
    13. vectorint>> minD(row+1,vector<int>(col+1));
    14. //初始化F(1,0):i F(0,j):j 第一行和第一列
    15. for(int i=0;i<=col;++i)
    16. {
    17. minD[0][i]=i;
    18. }
    19. for(int i=1;i<=row;++i)
    20. {
    21. minD[i][0]=i;
    22. }
    23. for(int i=1;i<=row;++i)
    24. {
    25. for(int j=1;j<=col;++j)
    26. {
    27. //插入、删除
    28. //插入和删除的步数中选择一个步数最小的,然后+1
    29. minD[i][j]=min(minD[i][j-1],minD[i-1][j])+1;
    30. //替换
    31. //如果word1的第i个就等于word2的第j个,就不需要替换
    32. if(word1[i-1]==word2[j-1])
    33. {
    34. //替换是在对角线上
    35. minD[i][j]=min(minD[i][j],minD[i-1][j-1]);
    36. }
    37. else{
    38. //从插入删除和替换中选择一个步数最少的
    39. minD[i][j]=min(minD[i][j],minD[i-1][j-1]+1);
    40. }
    41. }
    42. }
    43. return minD[row][col];
    44. }
    45. };

    九、不同子序列

    不同的子序列_牛客题霸_牛客网

    描述

    给定两个字符串S和T,返回S子序列等于T的不同子序列个数有多少个? 

    字符串的子序列是由原来的字符串删除一些字符(也可以不删除)在不改变相对位置的情况下的剩余字符(例如,"ACE"is a subsequence of"ABCDE"但是"AEC"不是)
    例如:

    S="nowcccoder", T = "nowccoder" 

    返回3 

    示例1

    输入:

    "nowcccoder","nowccoder"

    复制返回值:

    3
    

    问题:S中与T相同的子序列的个数

    子问题:S的子串中与T相同的子序列的个数

    状态F(i):S的前i个字符构成的子串中与T前j个字符相同的子序列个数。

            子串的长度>=T的长度              

                            S:abaaaa            T:aba             i:4        F(i-1):1 -->aba   

                            第四个字符"a"与T的最后一个字符相同,然后取前面的字符找ab

            if(S[i]==T[j])

            :如果使用第i个字符,只能为子序列的最后一个字符,相当于和T的第j个字符匹配:F(i-1,j-1)

            :如果不使用第i个字符:F(i-1,j)

                    F(i,j):

             F(i,j):F(i-1,j-1)+F(i-1,j)

            if(S[i]!=T[j])

                    ​​​​​​​肯定不能使用第i个字符

                    F(i,j)=F(i-1,j)

            

     

    rabbit
    1(空的字符串中可以找到空的字符串)000000
    r1

    1

    F(0,0)+F(0,1)

    0

    a1

    1

    F(1,1)

    1

    F(1,2)+F(1,1)

    b1

    (rab中找ra)

    b!=a

    F(2,2)

    1

    b==b

    F(2,2):ra-->ra:1

    F(2,3):ra rab:0

    b1
    b1
    i1
    t1

    初始状态F(i,0)=1(集合中寻找空集的个数)

                    F(0,j)=0  (j!=0);

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param S string字符串
    6. * @param T string字符串
    7. * @return int整型
    8. */
    9. int numDistinct(string S, string T) {
    10. int s_size = S.size();
    11. int t_size = T.size();
    12. // S的长度小于T长度,不可能含有与T相同的子串
    13. if (S.size() < T.size()) return 0;
    14. // T为空串,只有空串与空串相同,S至少有一个子串,它为空串
    15. if (T.empty()) return 1;
    16. // F(i,j),初始化所有的值为0
    17. vectorint> > f(s_size + 1, vector<int>(t_size + 1, 0));
    18. // 空串与空串相同的个数为1
    19. f[0][0] = 1;
    20. for (int i = 1; i <= s_size; ++i) {
    21. // F(i,0)初始化
    22. f[i][0] = 1;
    23. for (int j = 1; j <= t_size; ++j) {
    24. // S的第i个字符与T的第j个字符相同
    25. if (S[i - 1] == T[j - 1]) {
    26. f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
    27. }
    28. else {
    29. // S的第i个字符与T的第j个字符不相同
    30. // 从S的前i-1个字符中找子串,使子串与T的前j个字符相同
    31. f[i][j] = f[i - 1][j];
    32. }
    33. }
    34. }
    35. return f[s_size][t_size];
    36. }
    37. };

    由于我们这里观察到其实是要用到上一次遍历的结果,所以我们可以将二维矩阵降成一维的数组

    1. class Solution {
    2. public:
    3. /**
    4. *
    5. * @param S string字符串
    6. * @param T string字符串
    7. * @return int整型
    8. */
    9. int numDistinct(string S, string T) {
    10. if (S.empty())
    11. return 0;
    12. if (T.empty())
    13. return 1;
    14. int len1 = S.size();
    15. int len2 = T.size();
    16. vector<int> numDis(len2 + 1, 0);
    17. numDis[0] = 1;
    18. for (int i = 1; i <= len1; ++i) {
    19. //从后向前更新,因为我们所需要使用的是未更新的值,也就是上一行的值
    20. for (int j = len2; j > 0; --j) {
    21. if (S[i - 1] == T[j - 1])
    22. numDis[j] = numDis[j - 1] + numDis[j];
    23. //如果相同的话,本一次更新就等于上一次的数据
    24. }
    25. }
    26. return numDis[len2];
    27. }
    28. };

    动态规划状态定义:

            状态来源:从问题中抽象状态

            抽象状态:每一个状态对应一个子问题

            状态的形式可以定义很多,如何验证状态的合理性:

                    1.某一个状态的解或者多个状态处理之后的解能否对应最终问题的解。

                    2.状态之间可以形成递推关系

            一维状态 VS 二维状态(依据问题和线索)

                    首先尝试一维状态,

                    一维的状态的合理性不满足的时候,再去定义二维状态。

            常见问题的状态:

                    字符串:状态一般对应子串,状态中每一次一般增加一个新的字符

                    矩阵:二维状态-->优化-->一维状态

  • 相关阅读:
    艾美捷彗星检测试剂盒(单细胞凝胶电泳)化验原理及研究
    基于SSH的网上拍卖系统的设计与实现(JavaWeb的拍卖商城系统)
    Docker容器管理
    java开发工具V2022.2即将上线,最新功能抢先查看!
    深度学习与CV教程(4) | 神经网络与反向传播
    【REGEXP】【正则的使用】【正则的使用】
    【JavaScript脚本宇宙】打造完美用户体验:六大模态库全解析
    Intel汇编-查看命令行参数
    <Zero to One> 2.perfect competition and monopoly
    Linux防火墙命令
  • 原文地址:https://blog.csdn.net/weixin_62684026/article/details/127131636