【leetcode】dp---中等(1)64. 最小路径和_dp迷宫(2)91. 解码方法_不定长子串(3)**95. 不同的二叉搜索树 II_dfs返回答案(4)**96. 不同的二叉搜索树_dp

64、给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[ [1,3,1],
  [1,5,1],
  [4,2,1]]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

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

// dp[i][j]:到(i,j)最短路劲和
// 状态转移:1)左走一步,2)上走一步
// dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(i == 0 && j == 0) continue;
                else if(i == 0) grid[i][j] += grid[i][j-1];
                else if(j == 0) grid[i][j] += grid[i-1][j];
                else grid[i][j] += min(grid[i-1][j], grid[i][j-1]);
            }
        }
        return grid[m-1][n-1];
    }
};

结果:

执行用时:12 ms, 在所有 C++ 提交中击败了43.25% 的用户

内存消耗:8.3 MB, 在所有 C++ 提交中击败了72.16% 的用户

 

91、一条包含字母 A-Z 的消息通过以下方式进行了编码:

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

给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例 1:

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

 dp[i][1] = dp[i-1][1] + dp[i-1][0];

 dp[i][0] = dp[i-1][1] 需要提前判断s[i]是否为1或2 

// dp[i][j]:位置i处编码方式总数。i:编码的结尾位置。j:i位置字符是否单独编码.1:单独。0:不单独。
// 状态转移:1)本字符单独编码;2)本字符包含在前一个编码中
// dp[i][1] = dp[i-1][1] + dp[i-1][0];
// dp[i][0] = dp[i-1][1] 需要提前判断s[i]是否为1或2 
class Solution {
public:
    int numDecodings(string s) {
        if(s[0] == '0') return 0;
        int m = s.size();
        vector<vector<int>> dp(m, vector<int>(2, 0));
        dp[0][1] = 1;
        dp[0][0] = 0;
        for(int i = 1; i < m; i++){
            if(s[i] == '0' && s[i-1] == '0') return 0;
            if(s[i-1] == '1' || s[i-1] == '2' && s[i] < '7') dp[i][0] = dp[i-1][1];
            if(s[i] == '0') dp[i][1] = 0;
            else dp[i][1] = dp[i-1][1] + dp[i-1][0];
        }
        return dp[m-1][0] + dp[m-1][1];
    }
};

结果:

执行用时:4 ms, 在所有 C++ 提交中击败了47.25% 的用户

内存消耗:6.8 MB, 在所有 C++ 提交中击败了5.01% 的用户

 

修改:二维数组优化为一维

dp[i] =  dp[i-2] + dp[i-1];      (dp[i-2]的前提为s[i-1]=='1' || s[i-1]=='2'&&s[i]<'7')

// dp[i]:到第i个位置的解码方法
// 状态转移:1)i字符与上一个字符配对,2)i字符无法配对
// dp[i] =  dp[i-2] + dp[i-1]; (dp[i-2]的前提为s[i-1]=='1' || s[i-1]=='2'&&s[i]<'7')
class Solution {
public:
    int numDecodings(string s) {
        if(s[0] == '0') return 0;
        int m = s.size();
        vector<int> dp(m, 0);
        dp[0] = 1;
        for(int i = 1; i < m; i++){
            if(s[i] == '0' && s[i-1] == '0') return 0;
            if(s[i-1] == '1' || s[i-1] == '2' && s[i] < '7') 
                dp[i] += i > 1 ? dp[i-2] : 1; // 与i-1配对
            if(s[i] != '0') dp[i] += dp[i-1]; // 单独
        }
    return dp[m-1];
    }
};

结果:

执行用时:4 ms, 在所有 C++ 提交中击败了47.25% 的用户

内存消耗:6.3 MB, 在所有 C++ 提交中击败了55.84% 的用户

 

95、给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树 。

示例:

输入:3
输出:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

如果我们枚举根节点的值为 i,根据二叉搜索树的性质,左子树的节点值的集合为 [1…i−1],右子树的节点值的集合为 [i+1…n]

vector<TreeNode*> dfs(int start, int end)

 vector<TreeNode*> l = dfs(start, i - 1);

 vector<TreeNode*> r = dfs(i + 1, end);     两个for组合左右子树所有的可能性。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<TreeNode*> dfs(int start, int end){
        if(start > end) return {nullptr};
        vector<TreeNode*> ans;
        for(int i = start; i <= end; i++){ // 根节点值的状态遍历
            vector<TreeNode*> l = dfs(start, i - 1); // 左子树所有可能性
            vector<TreeNode*> r = dfs(i + 1, end);   // 右子树都有可能性
            for(auto &a:l){
                for(auto &b:r){
                    TreeNode* cur = new TreeNode(i);
                    cur->left = a;
                    cur->right = b;
                    ans.push_back(cur);
                }
            }
        }
        return ans;
    }

    vector<TreeNode*> generateTrees(int n) {
        if(n == 0) return {};
       return dfs(1, n);
    }
};

结果:

执行用时:24 ms, 在所有 C++ 提交中击败了51.59% 的用户

内存消耗:14.5 MB, 在所有 C++ 提交中击败了28.39% 的用户

 

96、给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

用类似95的dfs方法,超时。13 / 19 个通过测试用例。当n = 18 时,超时。

class Solution {
public:
    int dfs(int start, int end){
        if(start > end) return 0;
        if(start == end) return 1;
        int ans = 0;
        for(int i = start; i <= end; i++){
            int l = dfs(start, i - 1);
            int r = dfs(i + 1, end);
            if(l * r) ans += l * r;
            else ans += l + r;
        }
        return ans;
    }

    int numTrees(int n) {
        if(n == 1) return 1;
        return dfs(1, n);
    }
};

 

修改:用dp的思想。

定义两个函数:

G(n) 长度为 nnn 的序列能构成的不同二叉搜索树的个数。 ---> 为求解所需函数.

F(i,n): 以 i 为根、序列长度为 n 的不同二叉搜索树个数 (1≤i≤n)。

(1)                       (2) 

(3)

dp[len] += dp[i-1] * dp[n-i];

// dp[i]:长度为i的二叉搜索树数目
// 状态转移:对长度i中的每个数,作为根节点,并将这些树数目相加,即为该长度i的所有二叉搜索树
// dp[i] = (dp[0] * dp[n]) +( dp[1] * dp[n-1]) +...
class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        dp[1] = 1;
        for(int len = 2; len <= n; len++){ // 长度 (1--len)
            for(int i = 1; i <= len; i++){ // 根节点
                dp[len] += dp[i-1] * dp[len-i];
            }
        }
        return dp[n];
    }
};

结果:

执行用时:0 ms, 在所有 C++ 提交中击败了100.00% 的用户

内存消耗:6 MB, 在所有 C++ 提交中击败了76.50% 的用户

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值