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% 的用户