动态规划习题详解

目录

预备知识

1.Fibonacci

2.最大连续子数组和

3.单词拆分

4. 三角矩阵

5.路径总数

6.最小路径和 

7.背包问题

8. 回文串分割

9. 编辑距离

10.不同子序列

总结


预备知识

1.Fibonacci

 

class Solution {
public:
    int Fibonacci(int n) 
    {   
        vector<int> F(n+1,0);
        //初始化
           F[0] = 0;
           F[1] = 1;
        //解决子问题
        for(int i = 2 ;i <= n; ++i)
        {
            F[i] = F[i-1] +F[i-2];
        }
        
        //返回值
        return F[n];
    }
};


上述解法的空间复杂度为O(n)
其实F(n)只与它相邻的前两项有关,所以没有必要保存所有子问题的解
只需要保存两个子问题的解就可以
下面方法的空间复杂度将为O(1)

class Solution {
public:
    int Fibonacci(int n) 
    {   
        if(n == 1)
            return 1;
        //初始化
        int f1 = 0,f2 = 1;
        int ret = 0;
        //解决子问题
        for(int i = 2 ;i <= n; ++i)
        {
           ret = f1 + f2 ; 
            f1 = f2;
            f2 = ret;
        }
        
        //返回值
        return ret;
    }
};

 

2.最大连续子数组和

 

class Solution {
public:
    int maxSubArray(vector<int>& nums) 
    {
       //直接利用原数组进行操作
       int ret = nums[0];
       for(int i = 1 ; i < nums.size() ; ++i)
       {
           nums[i] = max(nums[i-1] + nums[i] , nums[i]);

           if(nums[i] > ret)
           ret = nums[i];
       }

       return ret;
    }
};

3.单词拆分

 

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) 
    {

        unordered_set<string> wordDictSet; //使用unordered_set便于进行查找单词
        for (auto word: wordDict) 
        {
            wordDictSet.insert(word);
        } 

        vector<bool> dp(s.size()+1,false);
        dp[0] = true; //初始化

        // j < i && f(j) && [j,i]
        for(int i = 1 ; i<=s.size() ; ++i)
        {
            for(int j = 0; j < i ;++j)
            {
                if(dp[j] && wordDictSet.find(s.substr(j, i - j)) != wordDictSet.end())
                {
                  dp[i] = true;
                  break;
                }

            }
        }

        return dp[s.size()];
    }
};

 

4. 三角矩阵

 

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) 
    {
       vector<vector<int>> ret = triangle;  //开辟二维数组+初始化
       
       int row = triangle.size();
       for(int i = 1 ;i < row ; ++i)
       {
           for(int j = 0 ; j < triangle[i].size() ;++j)
           {
               if(j == 0)//处理左边界
               {
                  ret[i][j] += ret[i-1][j];
                  continue;
               }

               if(j == triangle[i].size()-1)//处理右边界
               {
                  ret[i][j] += ret[i-1][j-1];
                  continue;
               }

               ret[i][j] += min(ret[i-1][j-1] , ret[i-1][j]);
           }
       }

       int minRoute = ret[row-1][0];
       for(int i = 1; i < triangle[row-1].size() ;++i)
       {
           if(minRoute > ret[row-1][i])
           {
               minRoute = ret[row-1][i];
           }
       }

       return minRoute;
    }
};


这种逆向思维不需要考虑边界,也不需要最后寻找最小值,直接返回F(0,0)即可

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) 
    {  //从底部到顶部
      int row = triangle.size();
      for(int i = row - 2 ; i >= 0 ;--i)
      {
         for(int j = 0 ; j < triangle[i].size() ; ++j )
         {
             triangle[i][j] += min(triangle[i+1][j] , triangle[i+1][j+1]);
         }
      }

      return triangle[0][0];

    }
};

5.路径总数

 

class Solution {
public:
    int uniquePaths(int m, int n) 
    {
        //申请空间 + 初始化
        vector<vector<int>>  route(m, vector<int>(n , 1));
        for(int i = 1 ; i < m ; ++i )
        {
            for(int j = 1 ; j < n ; ++j)
            {
                route[i][j] = route[i-1][j] + route[i][j-1] ;
            }
        }

        return route[m-1][n-1];
    }
};

6.最小路径和 

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) 
    {
       int row = grid.size();
       int col = grid[0].size();
       
      //第一行初始化
       for(int i = 1 ; i < col ; ++i)
       {
           grid[0][i] += grid[0][i-1] ;
       }

       //第一列初始化
        for(int i = 1 ; i < row ; ++i)
       {
           grid[i][0] += grid[i-1][0] ;
       }

       for(int i = 1 ; i < row ; ++i)
       {
           for(int j = 1; j < col ; ++j)
           {
               grid[i][j] += min(grid[i-1][j] , grid[i][j-1]);
           }
       }

       return grid[row-1][col-1];
    }
};

 

7.背包问题

    int backPackII(int m, vector<int> &A, vector<int> &V) 
    {

        int row = A.size();
        vector<vector<int>> dp(row + 1 , vector<int>(m + 1 , 0));

        for(int i = 1 ; i < (row+1) ; ++i)
        {
            for(int j = 1 ; j < (m+1) ; ++j)
            {
               //第i个商品在A中对应的索引为i-1: i从1开始
               //如果第i个商品大于j,说明放不下, 所以(i,j)的最大价值和(i-1,j)相同
               if(j < A[i-1]) //放不下
               {
                   dp[i][j] = dp[i-1][j];
               }
               else
               {
                   dp[i][j] = max(dp[i-1][j] , dp[i-1][j - A[i-1]] + V[i-1] );
               }
               //如果可以装下,分两种情况,装或者不装
               //如果不装,则即为(i-1, j)
               //如果装,需要腾出放第i个物品大小的空间: j - A[i-1],装入之后的最大价值即为
               //(i - 1,j - A[i-1]) + 第i个商品的价值V[i - 1]
               //最后在装与不装中选出最大的价值
              
            }
        }

        return dp[row][m];
    }
};


优化算法:
上面的算法在计算第i行元素时,只用到第i-1行的元素,所以二维的空间可以优化为一维空间
但是如果是一维向量,需要从后向前计算,因为后面的元素更新需要依靠前面的元素未更新(模拟二维矩阵的上一行的值)的值

int backPackII(int m, vector<int> &A, vector<int> &V) 
    {

        int row = A.size();
        vector<int> dp(m+1 , 0); //一维数组,从后向前更新 , 不需要列
        for(int i = 1 ; i < (row+1) ; ++i)
        {
            for(int j = m ; j > 0 ; --j)
            {
               if(j >= A[i-1]) //从后向前计算,利用 i-1列的值
               {
                   dp[j] = max(dp[j] , dp[j - A[i-1]] + V[i-1] );
               }
               
            }
        }

        return dp[m];
    }

 

8. 回文串分割

 

class Solution {
public:

bool ispal(const string& s, int left,int right)
{
    while(left < right)
    {
        if(s[left++] != s[right--])
        return false;
    }

    return true;
}

    int minCut(string s) 
    {
        int sz = s.size(); 
       vector<int> dp(sz+1 , 0);

       for(int i = 1 ; i <= sz ; ++i) //初始化
       {
             dp[i]  = i-1;
       }

       for(int i = 2 ; i <= sz ; ++i )
       {

           if( ispal(s,0,i-1) ) //如果整体是回文直接返回
            {
                 dp[i] = 0;
                 continue;
            }

           for(int j = i-1 ; j >= 1 ; --j)//整体不是回文,则需要一次根据前面的值进行判断
           {
               if( ispal(s, j , i-1) )
               {
                   dp[i] = min(dp[i] , dp[j]+1);
               }
           }
       }

       return dp[sz];
    }
};


判断是否为回文串也需要进行动态规划进行优化


class Solution {
public:

vector<vector<bool>> getMat(string s) 
{
 int len = s.size();
 vector<vector<bool>> mat = vector<vector<bool>>(len, vector<bool>(len, false));

 for (int i = len - 1; i >= 0; --i)//i为什么要从右向左, 要利用 F(i+1, j-1)的值进行判断
 {                                 //
     for (int j = i; j < len; ++j) 
     {
        if (j == i) 
        {
           // 单字符为回文字符串
          mat[i][j] = true;
        }
        else if (j == i + 1) 
        {
           // 相邻字符如果相同,则为回文字符串
           mat[i][j] = (s[i] == s[j]);
        }
        else 
        {
            // F(i,j) = {s[i]==s[j] && F(i+1,j-1)
            // j > i+1
           mat[i][j] = ((s[i] == s[j]) && mat[i + 1][j - 1]);
        }
     }
 }
   return mat;
}

    int minCut(string s) 
    {
        int sz = s.size(); 
       vector<int> dp(sz+1 , 0);
       vector<vector<bool>> mat = getMat(s);

       for(int i = 1 ; i <= sz ; ++i) //初始化
       {
             dp[i]  = i-1;
       }

       for(int i = 2 ; i <= sz ; ++i )
       {
           if(mat[0][i-1])
           {
               dp[i] = 0;
               continue;
           }
           for(int j = 1 ; j <= i-1 ; ++j)//整体不是回文,则需要一次根据前面的值进行判断
           {
               if( mat[j][i-1])
               {
                   dp[i] = min(dp[i] , dp[j]+1);
               }
           }
       }

       return dp[sz];
    }
};

9. 编辑距离

 

class Solution {
public:
    int minDistance(string word1, string word2) 
    {
       int row = word1.size();
       int col = word2.size();

       if(row == 0)
       return col;

       if(col == 0)
       return row;

       vector<vector<int>> dp(row+1 , vector<int>(col+1,0));

       for(int i = 0 ; i <= row ; ++i)
       dp[i][0] = i;

        for(int j = 0 ; j <= col ; ++j)
        dp[0][j] = j;

        for(int i = 1 ; i <= row ; ++i)
        {
            for(int j = 1 ; j <= col ; ++j)
            {
                //插入  删除
                dp[i][j] = min(dp[i-1][j]+1 , dp[i][j-1]+1 );
                
                //替换操作
                if(word1[i-1] == word2[j-1])
                {
                    dp[i][j] = min(dp[i][j] , dp[i-1][j-1]);
                }
                else
                {
                    dp[i][j] = min(dp[i][j] , dp[i-1][j-1]+1);
                }
            }
        }

        return dp[row][col];
       
    }
};

 

10.不同子序列

 

class Solution {
public:
    int numDistinct(string s, string t) 
    {
      int row = s.size();
      int col = t.size();

      if(row == 0  || col == 0)
      return 0;

      vector<vector<size_t>> dp(row+1 , vector<size_t>(col+1 , 0));

      for(int i = 0 ;  i <= row ; ++i) //初始化
      dp[i][0] = 1;

      for(int i =1 ; i <= row ; ++i )
      {
          for(int j = 1 ; j <= col ;++j)
          {
              if(i < j)
              {
                 dp[i][j] = 0;
              }
              else
              {
                  if( s[i-1] == t[j-1] )
                  {
                      dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
                  }
                  else
                  {
                      dp[i][j] = dp[i-1][j];
                  }
              }
              
          }
      }

      return dp[row][col];
    }
};


此题也可优化空间复杂度为O(n)
f[i][j] 只和 f[i - 1][j], f[i - 1][j - 1]有关
类似于背包问题,可以用一维数组保存上一行的结果,每次从最后一列更新元素值


class Solution {
public:
    int numDistinct(string s, string t) 
    { //因为我们只使用了上一行的数据,所以我们只需要一行即可
      int row = s.size();
      int col = t.size();

      if(row == 0  || col == 0)
      return 0;

      vector<unsigned int> dp(col+1, 0);
      dp[0] = 1;

      for(int i = 1 ; i <= row ; ++i)
      {
          for(int j = col ; j >= 1 ; --j) //一般使用一行就需要从后向前更新,防止值被覆盖
          {
             if(i < j)
              {
                 dp[j] = 0;
              }
              else
              {
                  if( s[i-1] == t[j-1] )
                  {
                      dp[j] = dp[j-1] + dp[j];
                  }
              }
          }
      }
 
      return dp[col];
     
    }
};

 

总结

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值