[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];   
}
相關文章
相關標籤/搜索