leecode 96 不同的二叉搜索树
刚开始一直以为是跟括号生成类似的回溯,于是写了这样的代码:
void dfs(const int n, int left, int right, int &res){
if(left + right == n - 1){
//cout << "l: "<<left<<" r: "<<right<<endl;
res += 1;
return;
}
dfs(n, left + 1, right, res);
dfs(n, left, right + 1, res);
}
int numTrees(int n) {
int res = 0;
dfs(n, 0, 0, res);
return n ? res + 1 : 0;
}
后来发现有一种情况怎么都考虑不到,就是从根结点分别左右两边建立二叉树,看了题解官方题目解释,才发现用的是递归。
最近在ACwing学算法基础课,刚好讲到递归,对于动态规划要找到其状态表示,状态表示是一个结合,官解中的G[n]表示程度为n的序列构成二叉树的个数,假设在i点,i左侧的每个点均可能成为其左子数的根节点,右边同理,因此以该节点作为根节点,左右序列可能构成二叉树的笛卡尔积就是该段的笛卡尔积个数。
两重for循环,i 确定的是长度n,j是用来划分长度i
int numTrees(int n) {
vector<int> dp(n+1,0);
dp[0] = dp[1] = 1;
for(int i=2;i<=n;++i)
for(int j=0;j<=i-1;++j)
dp[i] += dp[j]*dp[i-j-1];
return dp[n];
}
Leecode 64.最小路径和
第一眼看很坚定的觉得是一个bfs,后来超时了。
看了题解,是dp,状态转移方程就是我从哪里来。g[i][j]代表[i][j]这个格子是从哪几个可能的格子转移过来的,其属性值表示的是左上角到该点的最短距离。而状态计算则是依据dp的含义进行划分,代码如下:
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++){
int tmp = grid[i][j];
if( i && j)grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]);
else if(i)grid[i][j] = grid[ i - 1][j];
else if(j)grid[i][j] = grid[i][j - 1];
if(i || j)grid[i][j] += tmp;
}
return grid[m - 1][n - 1];
}
leecode 198.打家劫舍
这里是用滚动数组代替dp,空间复杂度为O(1),时间为O(n)
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty()) {
return 0;
}
int size = nums.size();
if (size == 1) {
return nums[0];
}
int first = nums[0], second = max(nums[0], nums[1]);
//vector<int>dp;
for (int i = 2; i < size; i++) {
//dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
int temp = second;
second = max(first + nums[i], second);
first = temp;
}
return second;//dp[nums.size() - 1];
}
};
leecode 198.打家劫舍II
进阶版本,是一个环形。如果偷窃了第一间就不要最后一间
return max(maxRob(nums, 0, n - 2), maxRob(nums, 1, n - 1));
//maxRob与1中的函数相同。
完整代码
class Solution {
public:
int maxRob(vector<int>& nums, int s, int e){
int first = nums[s], second = max(nums[s], nums[s+1]);
for(int i = s + 2; i <= e; i++){
int temp = second;
second = max(first + nums[i], second);
first = temp;
}
return second;
}
int rob(vector<int>& nums) {
int n = nums.size();
if(!n)return 0;
if(n == 1)return nums[0];
if(n == 2)return max(nums[0], nums[1]);
return max(maxRob(nums, 0, n - 2), maxRob(nums, 1, n - 1));
}
};
Leecode. 279 完全平方数
按照动规的逻辑来做,有一个公式:
假设当前枚举到 j,那么我们还需要取若干数的平方,构成i - j 2,则可以据此得到状态转移方程:
f
[
i
]
=
1
+
m
i
n
⌊
i
⌋
j
=
1
f
[
i
−
j
2
]
f[i] = 1 + \underset{j = 1}{\overset{\lfloor\sqrt{i} \rfloor}{min}}f[i - j ^2]
f[i]=1+j=1min⌊i⌋f[i−j2]
(没搞明白为啥要加1)
class Solution {
public:
int numSquares(int n) {
vector<int>f(n + 1, 0);
//vector<vector<int>>dp(0, vector<int>(n));
for(int i = 1; i <= n; i++){
//cout <<"i: "<<i<<endl;
int Min_ = INT_MAX;
for(int j = 1; j <= i/j; j++){
//cout <<"f["<<i - j*j<<"]: "<<f[i - j*j] <<" "<< Min_<<endl;
Min_ = min(Min_, f[i - j*j]);
}
f[i] = Min_ + 1; //没懂为什么要+1啊
}
return f[n];
}
};
152. 乘积最大子数组
乍看一眼感觉是动规,不死心尝试一下别的,因为太菜又想不出来什么别的做法,终于屈服了。
动态规划真的好难啊,淦!
对于一个乘积数组,我们维护一个数组maxF[n], 其用来表示长度为n的最大子数组的乘积。但是,因为乘积形式,若只维护最大,则会遗漏负负得正的情况,因此需要维护两个数组:
考虑当前位置如果是一个负数的话,那么我们希望以它前一个位置结尾的某个段的积也是个负数,这样就可以负负得正,并且我们希望这个积尽可能「负得更多」,即尽可能小。如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数,并且希望它尽可能地大。
于是便得到了如下实现的动态规划转移方程组:
class Solution {
public:
int maxProduct(vector<int>& nums) {
vector<int>maxF(nums), minF(nums);//复制两个数组
for(int i = 1; i < nums.size(); i++){
maxF[i] = max(maxF[i - 1] * nums[i], max(minF[i - 1] * nums[i], nums[i]));
minF[i] = min(minF[i - 1] * nums[i], min(maxF[i - 1]* nums[i], nums[i]));
}
return *max_element(maxF.begin(), maxF.end());
}
};
时间优化:
class Solution {
public:
int maxProduct(vector<int>& nums) {
vector<int>maxdp(nums.size()), mindp(nums.size());
maxdp[0] = max(nums[0], maxdp[0]);
mindp[0] = min(nums[0], mindp[0]);
int ret = 0;
for(int i = 1; i < nums.size(); i++){
if(nums[i] >= 0){
maxdp[i] = max(maxdp[i - 1] * nums[i], nums[i]);
mindp[i] = min(mindp[i - 1] * nums[i], 0);
}else {
maxdp[i] = max(mindp[i - 1] * nums[i], 0);
mindp[i] = min(mindp[i - 1] * nums[i], nums[i]);
}
ret = max(maxdp[i], ret);
}
return ret;
}
};
Leecode 5.最长回文子串
这里的动态转移方程为:
dp[ i ][ j ]其表示的含义为 字符串第 i 位到第 j 位之间的字符串是不是回文子串
其状态转移方程为:
当一个字符串为回文字串时,若给其左右两端加上一个相同的字符,则新的字符串一定也是回文子串。当 s[i] == s[j]时,
d
p
[
i
]
[
j
]
=
d
p
[
i
+
1
]
[
j
−
1
]
dp[i][j] = dp[i + 1][j - 1]
dp[i][j]=dp[i+1][j−1]
还有一种情况,若字符串的长度小于等于2,又因为 s[i] == s[j] ,所以一定是两个相等的字符串或者单个字符。
class Solution {
public:
string longestPalindrome(string s) {
int maxLen = 0, l = -1, r = - 1;
int n = s.length();
if(n < 2)return s;
vector<vector<bool>>dp(n,vector<bool>(n, false));
for(int end = 0; end < n; end++){
for(int start = end; start >= 0; start--){
if(s[start] == s[end] && (end <= start + 2 || dp[start + 1][end - 1])){
dp[start][end] = true;
if(maxLen < end - start + 1){
maxLen = end - start + 1;
l = start;
}
}
}
}
return s.substr(l, maxLen);
}
};
此种解法会被官方解法快个四倍左右