动态规划16题

目录

91. 解码方法

1646. 获取生成数组中的最大值

1043. 分隔数组以得到最大和

139. 单词拆分

1277. 统计全为 1 的正方形子矩阵

剑指 Offer II 091. 粉刷房子

剑指 Offer 42. 连续子数组的最大和

300. 最长递增子序列

方案一

方案二   贪心,二分

1027. 最长等差数列

剑指 Offer II 093. 最长斐波那契数列

剑指 Offer II 095. 最长公共子序列

516. 最长回文子序列

72. 编辑距离

剑指 Offer 47. 礼物的最大价值

剑指 Offer II 101. 分割等和子集

剑指 Offer II 103. 最少的硬币数目


91. 解码方法

难度中等

1406

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1 1 10 6)
  • "KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为  (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6" 和 "06" 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。

示例 2:

输入:s = "226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

示例 3:

输入:s = "06"
输出:0
解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)

提示:

  • 1 <= s.length <= 100
  • s 只包含数字,并且可能包含前导零。

 

class Solution {
    int dp[1000];
public:
    int numDecodings(string s) {
        
        int n = s.size();
        for (int i = 0; i < n; i++)
        {
            if (i == 0)
            {
                if (s[i] == '0') break;
                else dp[i] = 1;
            }
            else
            {
                if(s[i]=='0')

               {
                   if(s[i-1]=='0') break;
                   if(s[i-1]=='1'||s[i-1]=='2') 
                   {    if(i>1)
                       dp[i]=dp[i-2];
                       else
                       dp[i]=1;
                   }
                   else break;
               }
               else 
               {
                   int tep = (s[i - 1] - '0') * 10 + (s[i] - '0');
                   if(s[i-1]=='0') dp[i]=dp[i-1];
                   else
                   {
                        if(tep>10&&tep<=26) 
                        {
                            if(i>1) dp[i]=dp[i-2]+dp[i-1];
                            else dp[i]=2;
                        }
                        else 
                        {
                            if(i>1)
                            dp[i]=dp[i-1];
                            else dp[i]=1;
                        }
                   }
               }
             
            }

        }
        return dp[n-1];
    }
    };

1646. 获取生成数组中的最大值

难度简单

83

给你一个整数 n 。按下述规则生成一个长度为 n + 1 的数组 nums :

  • nums[0] = 0
  • nums[1] = 1
  • 当 2 <= 2 * i <= n 时,nums[2 * i] = nums[i]
  • 当 2 <= 2 * i + 1 <= n 时,nums[2 * i + 1] = nums[i] + nums[i + 1]

返回生成数组 nums 中的 最大 值。

题解:题述相当于状态转移方程

class Solution {

    int dp[105];
    int maxn=0;
public:
    int getMaximumGenerated(int n) {
        dp[0]=0;
        dp[1]=1;
        for(int i=0;i<=n;i++)
        {
          if(2<=2*i&&2*i<=n) dp[2*i]=dp[i];
          if(2<=2*i+1&&2*i+1<=n) dp[2*i+1]=dp[i]+dp[i+1];
          maxn=max(dp[i],maxn);
        }
    
    
    return maxn;
    }
};

1043. 分隔数组以得到最大和

难度中等

267

给你一个整数数组 arr,请你将该数组分隔为长度 最多 为 k 的一些(连续)子数组。分隔完成后,每个子数组的中的所有值都会变为该子数组中的最大值。

返回将数组分隔变换后能够得到的元素最大和。本题所用到的测试用例会确保答案是一个 32 位整数。

示例 1:

输入:arr = [1,15,7,9,2,5,10], k = 3
输出:84
解释:数组变为 [15,15,15,9,10,10,10]

题解:dp[i] 以下标为i的位置为分割位置,则dp[i]表示i及其前面的最大分割和,只会影响i之前的元素。 

class Solution {

    int dp[505];
public:
    int maxSumAfterPartitioning(vector<int>& arr, int k) {
        int n=arr.size();
            for(int i=0;i<n;i++)
            {
                int maxn=0;
                dp[i]=0;
                for(int j=i;i-j+1<=k&&j>=0;j--)
                {
                    if(arr[j]>maxn)
                    {
                        maxn=arr[j];
                    }
                    if(j) dp[i]=max(dp[i],dp[j-1]+maxn*(i-j+1));
                    else dp[i]=max(dp[i],maxn*(i-j+1));
                }
            }
            return dp[n-1];
    }
};

139. 单词拆分

难度中等

2185

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

class Solution {

    int dp[305];

public:
    bool wordBreak(string s, vector<string>& wordDict) {
            int n=s.size();
            int m=wordDict.size();
            
            for(int i=0;i<n;i++)
            {
                for(int j=0;j<m;j++)
                {
                    
                    int le=wordDict[j].size();
                    if(le>i+1) continue;//字典中字符长度大于i,肯定不匹配
                    int f=0;
                    int k;
                    if(i-le!=-1&&dp[i-le]!=1) continue;//即使此字符可以匹配,前面一个紧邻字符未能匹配,也无效。
                    for( k=i;k>i-le;k--)
                    {
                        if(s[k]!=wordDict[j][k-i+le-1]) break;
                        if(k==i-le+1) dp[i]=1;
                    }
                 
                }

            }
        if(dp[n-1]) return true;
        else return false;
    }
};

1277. 统计全为 1 的正方形子矩阵

难度中等

285

给你一个 m * n 的矩阵,矩阵中的元素不是 0 就是 1,请你统计并返回其中完全由 1 组成的 正方形 子矩阵的个数。

示例 1:

输入:matrix =
[
  [0,1,1,1],
  [1,1,1,1],
  [0,1,1,1]
]
输出:15
解释: 
边长为 1 的正方形有 10 个。
边长为 2 的正方形有 4 个。
边长为 3 的正方形有 1 个。
正方形的总数 = 10 + 4 + 1 = 15.
class Solution {

    #define maxn 320
    bool dp[320][320][320];//开int会超题限内存
public:
    int countSquares(vector<vector<int>>& matrix) {
            int m=matrix.size();
            int n=matrix[0].size();
            int le=min(m,n);
            int ans=0;
   
            for(int l=1;l<=le;l++)//l为正方形边长
                {
                    for(int i=0;i+l<=m;i++)
                    {
                        for(int j=0;j+l<=n;j++)
                        {
                         
                            if(l==1 ) dp[l][i][j]=matrix[i][j];
                            else 
                            {
                               // if(i>=1&&j>=1)
                                dp[l][i][j]=matrix[i][j]&dp[l-1][i+1][j]&dp[l-1][i][j+1]&dp[l-1][i+1][j+1];
                            }
                            ans+=dp[l][i][j];
                            
                        }
                        
                    }
                    
                }
            return ans;
    }
};

剑指 Offer II 091. 粉刷房子

难度中等

154

假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。

当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。

例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。

请计算出粉刷完所有房子最少的花费成本。

示例 1:

输入: costs = [[17,2,17],[16,16,5],[14,3,19]]
输出: 10
解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色
     最少花费: 2 + 5 + 3 = 10。

 

class Solution {
   int dp[200][5];

public:
    int minCost(vector<vector<int>>& costs) {
        int n=costs.size();
        memset(dp,0x3f3f3f,sizeof dp);
        dp[0][0]=costs[0][0];
        dp[0][1]=costs[0][1];
        dp[0][2]=costs[0][2];
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<3;j++)
            {
                for(int k=0;k<3;k++)
                {
                    if(j!=k)
                    dp[i][k]=min(dp[i][k],dp[i-1][j]+costs[i][k]);
                }
            }
        }
        int ans=1e9;
        for(int i=0;i<3;i++)
        {
            ans=min(ans,dp[n-1][i]);
        }
        return ans;
    }
};

剑指 Offer 42. 连续子数组的最大和

难度简单

709

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution {
    #define maxn 100005
    int dp[maxn];
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size();
        dp[0]=nums[0];
        int ans=nums[0];
        for(int i=1;i<n;i++)
        {
            dp[i]=max(nums[i],dp[i-1]+nums[i]);//dp[i]表示前i个元素中包含num[i]的最大子数组和
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};

 

300. 最长递增子序列

难度中等

3262

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

方案一

class Solution {
    int dp[2550];
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        int ans=0;
        for(int i=0;i<n;i++)
        {
            dp[i]=1;
            for(int j=0;j<=i;j++)
            {
                if(nums[i]>nums[j]) dp[i]=max(dp[i],dp[j]+1);
            }
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};

方案二   贪心,二分

class Solution {
    int dp[2550];
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        int ans=1;
        dp[0]=-1;
        for(int i=1;i<n;i++)
        {
            if(nums[i]>dp[ans]) 
            {
                ans++;
                dp[ans]=nums[i];
            }
            else
            {
                int l,r,mid;
                l=0;r=ans;
                while(l<r)//二分查找第一个小于nums[i]的位置,以便将其后一个位置元素变为nums[i]
                {
                    mid=(l+r)>>1;
                    if(dp[mid]<nums[i]) l++;
                    else r--;
                }
                dp[mid+1]=nums[i];
            }
        }
        return ans;
    }
};

1027. 最长等差数列

难度中等

320

给你一个整数数组 nums,返回 nums 中最长等差子序列的长度

回想一下,nums 的子序列是一个列表 nums[i1], nums[i2], ..., nums[ik] ,且 0 <= i1 < i2 < ... < ik <= nums.length - 1。并且如果 seq[i+1] - seq[i]0 <= i < seq.length - 1) 的值都相同,那么序列 seq 是等差的。

示例 1:

输入:nums = [3,6,9,12]
输出:4
解释: 
整个数组是公差为 3 的等差数列。

 

class Solution {

    int dp[1005][1005]={0};
    public:
    int longestArithSeqLength(vector<int>& nums) {
        int n=nums.size();
        int ans=0;
        dp[0][0]=1;
        for(int i=0;i<n;i++)
        {
             for(int j=0;j<i;j++)
            {
                int k=nums[i]-nums[j]+500;
                if(dp[j][k]==0) dp[j][k]=1;//初始化为一
                dp[i][k]=dp[j][k]+1;
                    ans=max(ans,dp[i][k]);
             }
        }
        return ans;
    }
};

 

 

剑指 Offer II 093. 最长斐波那契数列

难度中等

77

如果序列 X_1, X_2, ..., X_n 满足下列条件,就说它是 斐波那契式 的:

  • n >= 3
  • 对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}

给定一个严格递增的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。如果一个不存在,返回  0 。

(回想一下,子序列是从原序列  arr 中派生出来的,它从 arr 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列)

示例 1:

输入: arr = [1,2,3,4,5,6,7,8]
输出: 5
解释: 最长的斐波那契式子序列为 [1,2,3,5,8] 。
class Solution {
    int dp[1005][1005];
    unordered_map<int,int> mp;//map哈希加快查找速度
    public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n=arr.size();
        int ans=0;
        for(int i=0;i<n;i++)
        {   mp[arr[i]]=i+1;
            for(int j=i-1;j>=0&&arr[j]*2>arr[i];j--)
            {
                int tep=arr[i]-arr[j];
                int k=mp[tep];
                if(k!=0) dp[i][j]=max(dp[j][k-1]+1,3);
                ans=max(ans,dp[i][j]);
            }
        }
        return ans;
    }
};

剑指 Offer II 095. 最长公共子序列

难度中等

142

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。
class Solution {
    int dp[1005][1005];
public:
    int longestCommonSubsequence(string text1, string text2) {
        text1='@'+text1;
        text2='%'+text2;
            int le1=text1.size();
            int le2=text2.size();
            for(int i=1;i<le1;i++)
            {
                for(int j=1;j<le2;j++)
                {
                    if(text1[i]==text2[j]) dp[i][j]=dp[i-1][j-1]+1;
                    else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
            return dp[le1-1][le2-1];
    }
};

516. 最长回文子序列

难度中等

1064

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

题解:转化为求s与s的逆序字符串的最长公共子串(子序列)

class Solution {
    int dp[1005][1005];
public:
    int longestPalindromeSubseq(string s) {
        string s1=s;
        reverse(s.begin(),s.end());
        s='#'+s;
        s1='%'+s1;
        for(int i=1;i<s.size();i++)
        {
            for(int j=1;j<s1.size();j++)
            {
                if(s[i]==s1[j]) dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
        return dp[s.size()-1][s1.size()-1];
    }
};

72. 编辑距离

难度困难

3021

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
class Solution {
    int dp[1005][1005];
public:
    int minDistance(string word1, string word2) {
        word1='#'+word1;
        word2='#'+word2;
        int le1=word1.size();
        int le2=word2.size();
        dp[0][0]=0;
        for(int i=1;i<le1;i++)
        {
            dp[i][0]=dp[i-1][0]+1;

        }
        for(int i=1;i<le2;i++)
        {
            dp[0][i]=dp[0][i-1]+1;
        }
        for(int i=1;i<le1;i++)
        {
            for(int j=1;j<le2;j++)
            {
                if(word1[i]==word2[j]) dp[i][j]=dp[i-1][j-1];
                else dp[i][j]=min(dp[i-1][j],min(dp[i-1][j-1],dp[i][j-1]))+1;//增,删插三种情况
            }
        }
        return dp[le1-1][le2-1];
    }
};

剑指 Offer 47. 礼物的最大价值

难度中等

502

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

 

class Solution {
    
    int dp[205][205];
public:
    int maxValue(vector<vector<int>>& grid) {

        int m=grid.size();
        int n=grid[0].size();
        dp[0][0]=grid[0][0];
        int i;
        for( i=1;i<n;i++)
        {
            dp[0][i]=dp[0][i-1]+grid[0][i];
        }
        for( i=1;i<m;i++)
        {
            dp[i][0]=dp[i-1][0]+grid[i][0];
        }
        for( i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i][j];         
           }
        }
        return dp[m-1][n-1];
    }
};

剑指 Offer II 101. 分割等和子集

难度简单

85

给定一个非空的正整数数组 nums ,请判断能否将这些数字分成元素和相等的两部分。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:nums 可以分割成 [1, 5, 5] 和 [11] 。

 

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int dp[20005];
        int sum=0;
        int n=nums.size();
        
        for(int i=0;i<n;i++)
        {
            sum+=nums[i];
        }
        if(sum%2) return false;

    sum=sum/2;
    memset(dp,0,sizeof dp);
    dp[0]=1;
    for(int i=0;i<n;i++)
    { 
        for(int j=sum;j>=nums[i];j--)
        {
            if(dp[j-nums[i]]) dp[j]=1;
           //或者写成 dp[j]|=dp[j-nums[i]];
        }
     if(dp[sum]) 
    return true;
    }

    return false;
    }
};

 

剑指 Offer II 103. 最少的硬币数目

难度中等

84

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1
class Solution {

public:
    int coinChange(vector<int>& coins, int amount) {
        int dp[10005];
        int n=coins.size();
        memset(dp,0x3f3f3f3f,sizeof dp);
        dp[0]=0;
        for(int i=0;i<n;i++)
        {
            if(coins[i]<=amount)
            dp[coins[i]]=1;
            for(int j=coins[i];j<=amount;j++)
            {
                if(dp[j-coins[i]]) dp[j]=min(dp[j-coins[i]]+1,dp[j]);
            }
        }
    
    if(dp[amount]<1e5) 
    return dp[amount];
    else 
    return -1;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linalw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值