[algorithm]动态规划问题

1.给定一个字符串 s,求最长回文子序列的长度。

思路:node

  • 子序列说明能够不连续。正则表达式

  • 对于任意字符串,若是其头尾相同,则其回文子序列的长度是其去头去尾字符串回文子序列长度+2,若是头尾不一样,则是去头或去尾字符串回文子序列中长的那个。数组

状态转移方程:
使用数组dpi表示子串i-j的最长回文子序列,则函数

if(i==j) dp[i][j] = 1;
 if(i>j) dp[i][j] = 0;  
 if(i<j && s[i]==s[j]) dp[i][j] = dp[i+1][j-1]+2;
 if(i<J && s[i]!=s[j]) dp[i][j] = max(dp[i+1][j],dp[i][j-1]);

代码:code

int longestPalindromeSubseq(string s) {
    int ss = s.size();
    int dp[ss][ss];
    memset(dp,0,sizeof(dp));
    for(int i=ss-1;i>=0;--i){
        dp[i][i] = 1;
        for(int j=i+1;j<ss;++j){
            if(s[i]==s[j])
                dp[i][j] = dp[i+1][j-1]+2;
            else
                dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
        }
    }
    return dp[0][ss-1];
}

2.有一种选数游戏,给定一个最大可选数m,和目标数d,两名玩家从1-m共m个数中轮流选数,当选数后,两人全部选择的数的和大于等于d时,该玩家获胜,对于给定的m和d,判断先选的玩家是否能胜利。

思路:
对于这种题,咱们须要遍历全部可能的游戏状态,并使用动态规划避免重复遍历。游戏状态能够表示成当前剩下的可选数列表。对于某个状态,有两种转移,一种是咱们从剩下数中能够选择一个数直接获胜,另外一个是咱们选择一个数后,在通过若干回合后对方会输。也就是两个递归的状态。递归

代码:游戏

unordered_map<int,bool> mp;
bool canIWin(int m, int d) {
    if(m>=d) return true;
    if(m*(m+1)/2 < d) return false;
    return dp(m,d,0);
}
bool dp(int m,int d,int state){
    if(mp.count(state)) return mp[state];
    for(int i=0;i<m;++i){
        if(((1<<i) & state)==0){
            if(i+1>=d || !dp(m,d-i-1,state | (1<<i))){
                return mp[state] = true;
            }
        }
    }
    return mp[state] = false;
    
}

3.输入一个n,求出全部由1,...,n组成的异构二叉查找树的数目。

思路:字符串

对于某个n,咱们能够分别把1-n做为根,求出其结构数,而后加起来就是总数。string

代码:it

int numTrees(int n) {
    if(n<2) return 1;
    int res[n+1] = {1,1};
    for(int i=2;i<=n;++i){
        for(int j=0;j<i;++j){
            res[i] += res[j]*res[i-j-1];
        }
    }
    return res[n];
}

4.输入一个n,列出全部由1,...,n组成的异构二叉查找树。

二叉树的结构:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

思路:
对于一个1-n中的i,咱们能够根据其i-1时的二叉树构建出新的二叉树,分为两种状况:

  1. 新节点做为根节点,那么i-1的全部二叉树分别做为其左子树就好

  2. 新节点不是根节点,那么对于i-1的二叉树,只要对于找寻最大节点路径上的每一个节点,把新节点插入进去就获得全部的情形

语言描述可能不太清楚,能够看代码

代码:

TreeNode * clone(TreeNode *root){
    if(root==nullptr) return nullptr;
    TreeNode *node = new TreeNode(root->val);
    node->left = clone(root->left);
    node->right = clone(root->right);
    return node;
}

vector<TreeNode*> generateTrees(int n) {
    vector<TreeNode*> res;
    if(n==0) return res;
    res.push_back(nullptr);
    for(int i=1;i<=n;++i){//ith new node
        vector<TreeNode *> temp;
        for(int j=0;j<res.size();++j){//jth old trees of i-1
            TreeNode * newtree = new TreeNode(i);
            newtree->left = res[j];
            temp.push_back(clone(newtree));
            if(res[j]!=nullptr){
                TreeNode *tnode = res[j];
                while(tnode->right!=nullptr){
                    newtree->left = tnode->right;
                    tnode->right = newtree;
                    temp.push_back(clone(res[j]));
                    tnode->right = newtree->left;
                    
                    tnode = tnode->right;
                }
                tnode->right = newtree;
                newtree->left = nullptr;
                temp.push_back(clone(res[j]));
            }
        }
        res = std::move(temp);
    }
    return res;
}

5.有一个行数为m,列数为n的二维数组,求出从左上到右下的这样的路径:路径上的值的和最小。每一步只能向右或向上走。

思路:
很显然,状态转移方程是

dp[i][j] = grid[i][j] + min(dp[i+1][j],dp[i][j+1]);

根据这个方程,咱们能够直接写出代码以下:

int minPathSum(vector<vector<int>>& grid) {
    if(grid.size()==0) return 0;
    int m = grid.size()-1;
    int n = grid[0].size()-1;
    int dp[m+1][n+1];
    memset(dp,0,sizeof dp);
    dp[m][n] = grid[m][n];
    for(int j=n-1;j>=0;--j){
        dp[m][j] = grid[m][j] + dp[m][j+1];
    }
    for(int i=m-1;i>=0;--i){
        for(int j=n;j>=0;--j){
            dp[i][j] = grid[i][j] + min(dp[i+1][j],dp[i][j+1]);
        }
    }
    return dp[0][0];
}

可是这样作的空间复杂度有O(N^2),咱们还能够作的更好,咱们注意到,在每一次状态转移时,咱们只用到了两行,一行是当前要修改的行,另外一行是修改行的下面一行,因此咱们能够只用到O(N)的空间来解决:

int minPathSum(vector<vector<int>>& grid) {
    if(grid.size()==0) return 0;
    int m = grid.size()-1;
    int n = grid[0].size()-1;
    int dp[2][n+1];
    memset(dp,0,sizeof dp);
    dp[0][n] = grid[m][n];
    for(int j=n-1;j>=0;--j){
        dp[0][j] = grid[m][j] + dp[0][j+1];
    }
    int cur = 1,old = 0;
    for(int i=m-1;i>=0;--i){
        for(int j=n;j>=0;--j){
            dp[cur][j] = grid[i][j] + min(dp[old][j],dp[cur][j+1]);
        }
        swap(old,cur);
    }
    return dp[old][0];
}

6.实现bool isMatch(string s, string p)函数,其s是一个字符串,p是一个仅有'.'和'*'语法的正则表达式,若是p能匹配s,返回true,不然返回false。

思路

首先设一个bool型dp数组,dpi表示s[0:i-1]和p[0:j-1]是否匹配。
显而易见的是dpi = i==0?true:false;
而dp0取决于pattern中是否都是a的形式,dp0确定是false,i>1时,dp0 = p[j-1]==''&&dp0==true;

对于通常状况,咱们要注意的是p的最后一个字符是否是'*',由于'.'没有讨论的必要,它能够匹配任何字符。
若是p[j-1]不是'*',那么dpi仅仅取决于p[j-1]是否能和s[i-1]匹配,而且dpi-1须要是能匹配空字符串的格式;
若是p[j-1]是'',又能够分红两种状况,一种是p最后的x不匹配字符,另外一种是x*匹配一个字符,而且dpi-1是true的。

状态转移方程

dp[i][0] = i==0?true:false;
dp[0][i] = i==1?false:(p[i-1]=='*' && dp[0][i-2]);
dp[i][j] = p[j-1]=='*'?(dp[i][j-2] || (s[i-1]==p[j-2] || p[j-2]=='.') && dp[i-1][j]):(dp[i][j] = (s[i-1]==p[j-1] || p[j-1]=='.') && dp[i-1][j-1]);

代码

bool isMatch(string s, string p) {
    int m = s.size(),n = p.size();
    bool dp[m+1][n+1];
    memset(dp,0,sizeof dp);
    dp[0][0] = true;
    for(int i=2;i<=n;++i){
        dp[0][i] = p[i-1]=='*' && dp[0][i-2];
    }
    for(int i=1;i<=m;++i){
        for(int j=1;j<=n;++j){
            if(p[j-1]=='*'){
                dp[i][j] = dp[i][j-2] || (s[i-1]==p[j-2] || p[j-2]=='.') && dp[i-1][j];
            }else{
                dp[i][j] = (s[i-1]==p[j-1] || p[j-1]=='.') && dp[i-1][j-1];
            }
        }
    }
    return dp[m][n];   
}
相关文章
相关标签/搜索