一天复习一篇,个人学习记录
- 1.最大子数组和
- 2.最长的斐波那契子序列的长度
- 3.最大正方形
- 4.最长有效括号
- 5.乘积最大子数组
- 6.可被三整除的最大和
- 7.回文子串数目
- 8.最长回文子序列
- 9.最长回文子串
1.最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
注意初始化,res需要初始为dp[0]
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size(), 0);
dp[0] = nums[0];
int res = dp[0];
if(nums.size() <= 1) return dp[0];
for(int i = 1; i < nums.size(); i ++){
dp[i] = max(nums[i], dp[i - 1] + nums[i]);
if(dp[i] > res) res = dp[i];
}
return res;
}
};
2.最长的斐波那契子序列的长度
如果序列 X_1, X_2, …, X_n 满足下列条件,就说它是 斐波那契式 的:
- n >= 3
- 对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}
给定一个严格递增的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。
eg:
输入: arr = [1,2,3,4,5,6,7,8]
输出: 5
解释: 最长的斐波那契式子序列为 [1,2,3,5,8] 。
dp[i][j] 表示以 arr[i] 和 arr[j] 作为最后两个数字的最长斐波那契式子序列的长度。
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr) {
int n = arr.size();
unordered_map<int, int> indexMap;
for (int i = 0; i < n; i++) {
indexMap[arr[i]] = i;
}
vector<vector<int>> dp(n, vector<int>(n, 2));
int maxLen = 0;
for (int j = 1; j < n; j++) {
for (int i = 0; i < j; i++) {
int k = indexMap.find(arr[j] - arr[i]) != indexMap.end() ? indexMap[arr[j] - arr[i]] : -1;
if (k >= 0 && k < i) {
dp[i][j] = dp[k][i] + 1;
maxLen = max(maxLen, dp[i][j]);
}
}
}
return maxLen > 2 ? maxLen : 0;
}
};
3.最大正方形
在一个由0和1组成的二维矩阵内,找到只包含1的最大正方形,并返回其面积
前缀和做法:
和美团2024春招第一题平衡矩阵很像,给一个和平衡矩阵一样的做法
需要注意的是,在小美的平衡矩阵中,输入数组都是下标1开始,这里的matrix是从0开始的,而前缀和又必须从1开始,所以前缀和s要加的matrix[i - 1][j - 1] - ‘0’
左上角i,j的起始,已经是在前缀和s里面看了,所以从1开始而不是0
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
if (matrix.empty()) return 0;
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> s(m+1, vector<int>(n+1, 0));
int res = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
s[i][j] = (matrix[i-1][j-1] - '0') + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
}
// i 和 j 是左上角, l 和 r 是右下角
// len 是正方形边长
for (int len = 1; len <= min(m, n); len++) {
for (int i = 1; i <= m - len + 1; i++) {
for (int j = 1; j <= n - len + 1; j++) {
int l = i + len - 1;
int r = j + len - 1;
int total = s[l][r] - s[l][j - 1] - s[i - 1][r] + s[i - 1][j - 1];
if (total == len * len) res = max(res, len * len);
}
}
}
return res;
}
};
动态规划做法:
- dp[i][j] 代表(i,j)为右下角,且只包含1的正方形的边长最大值。
- 递推公式:
- 如果该位置是0,则dp[i][j] = 0,因为当前位置不可能由1组成的正方形中
- 如果该位置是1,则dp[i][j]的值由其上方、左方和左上方的三个相邻位置的dp值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1,状态转移方程如下:dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1
- 如果 i 和 j 中至少有一个为0,则位置(i,j)为右下角的最大正方形的边长只能是1,dp[i][j] = 1
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
if(matrix.empty()) return 0;
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
int len = 0;
for(int i = 0; i < m; i ++){
for(int j = 0; j < n; j ++){
if(matrix[i][j] == '1'){
if(i == 0 || j == 0) dp[i][j] = 1;
else dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
if(dp[i][j] > len) len = dp[i][j];
}
}
}
return len * len;
}
};
4.最长有效括号
给一个字符串只包含 ‘(’ 和 ‘)’ ,找出最长有效括号子串。
dp[i]的含义是以下标1结尾的字符串最长有效括号子串的长度
所以上图dp[1] = 2, dp[3] = 4
- 如果当前遍历到了‘(’,那么一定是非法序列,dp[i] = 0
- 如果当前遍历到了‘)’,那么分2种情况
- )的前面是(,那么dp[i] = dp[i - 2] + 2
- )的前面是),那么需要检查i - dp[i - 1] - 1,即前一个合法序列的前一个位置是不是左括号,类似于图中的dp[7],index = 7 的时候,此时 index - 1 也是右括号,我们需要知道 i - dp[i - 1] - 1 = 7 - dp [ 6 ] - 1 = 4 位置的括号的情况。而刚好 index = 4 的位置是左括号,此时 dp [ i ] = dp [ i - 1 ] + dp [ i - dp [ i - 1] - 2 ] + 2 (当前位置的前一个合法序列的长度,加上匹配的左括号前边的合法序列的长度,加上新增的长度 2)
class Solution {
public:
int longestValidParentheses(string s) {
vector<int> dp(s.size(), 0);
int res = 0;
for(int i = 1; i < s.size(); i ++){
if(s[i] == ')'){
if(s[i - 1] == '('){
dp[i] = (i - 2 >= 0 ? dp[i - 2] : 0) + 2;
}else if(i > 0 && i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '('){
dp[i] = dp[i - 1] + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
}
res = max(res, dp[i]);
}
return res;
}
};
5.乘积最大子数组
给你一个整数数组nums,请你找出数组中乘积最大的非空连续子数组,并将乘积返回
dpMax[i] 表示以第 i 个元素的结尾的子数组,乘积最大的值
- 当nums[i] >= 0 并且dpMax[i - 1] > 0,dpMax[i] = dpMax[i - 1] * nums[i]
- 当nums[i] >= 0 并且dpMax[i - 1] < 0,dpMax[i] = nums[i]
- 当nums[i] < 0时,dpMax需要分情况讨论
- 当dpMin[i - 1] < 0,dpMax[i] = dpMin[i - 1] * nums[i]
- 当dpMin[i - 1] >= 0,dpMax[i] = nums[i]
上面引入了dpMin数组,怎么求dpMin和dpMax其实是一样的。
首先
dpMax[i] = max(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i]);
求 dpMin[i] 同理
dpMin[i] = min(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i]);
由于都是上一个dpMax或者上一个dpMin推导的,所以可以不设数组
dpMax = max(dpMin * nums[i], max(dpMax * nums[i], (double)nums[i]));
dpMin = min(dpMin * nums[i], min(preMax * nums[i], (double)nums[i]));
如果是int的话会有一个测试用例过不了
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
if (n == 0) {
return 0;
}
double dpMax = nums[0];
double dpMin = nums[0];
double maxProd = nums[0];
for (int i = 1; i < n; i++) {
// 更新dpMin的时候需要dpMax之前的信息,所以先保存起来
double preMax = dpMax;
dpMax = max(dpMin * nums[i], max(dpMax * nums[i], (double)nums[i]));
dpMin = min(dpMin * nums[i], min(preMax * nums[i], (double)nums[i]));
maxProd = max(maxProd, dpMax);
}
return (int)maxProd; // 将结果转换回int,假设最终结果适合int类型。
}
};
6.可被三整除的最大和
给你一个整数数组 nums,请你找出并返回能被三整除的元素 最大和。
dp[i][j]:处理前i个元素,余数为j的最大和。j有三种可能0,1,2按照定义,最终答案就是dp[n][0]
对于nums[i - 1],可选可不选
- 如果nums[i - 1]%3 = 0,那么说明加入当前nums[i - 1]不会改变其余数,所以:
- dp[i][0] = max{dp[i - 1][0],dp[i - 1][0] + nums[i - 1]}
- dp[i][1] = max{dp[i - 1][1],dp[i - 1][1] + nums[i - 1]}
- dp[i][2] = max{dp[i - 1][2],dp[i - 1][2] + nums[i - 1]}
- 如果nums[i - 1]%3 = 1,那么说明加入当前nums[i - 1]之后,原来余数0会变成1,1变成2,2变成0
- dp[i][0] = max{dp[i - 1][0],dp[i - 1][2] + nums[i - 1]}
- dp[i][1] = max{dp[i - 1][1],dp[i - 1][0] + nums[i - 1]}
- dp[i][2] = max{dp[i - 1][2],dp[i - 1][1] + nums[i - 1]}
- 如果nums[i − 1]%3 = 2,那么说明加入当前nums[ i − 1]之后,原来的余数0会变成2,1变成 0,2变成1:
- dp[i][0] = max{dp[i - 1][0],dp[i - 1][1] + nums[i - 1]}
- dp[i][1] = max{dp[i - 1][1],dp[i - 1][2] + nums[i - 1]}
- dp[i][2] = max{dp[i - 1][2],dp[i - 1][0] + nums[i - 1]}
class Solution {
public:
int maxSumDivThree(vector<int>& nums) {
int n = nums.size();
int dp[n + 1][3];// dp[i][j]代表下标0~i-1的数中,÷3余数为j的最大和
memset(dp,0,sizeof dp);
dp[0][1] = -1e9 , dp[0][2] = -1e9;// dp[0][0]是前0个数。÷3余数为0的最大和,是0
for(int i = 1;i <= n;i++){
int r = nums[i - 1] % 3;
if(r == 0){
dp[i][0] = max(dp[i - 1][0] , dp[i - 1][0] + nums[i - 1]);
dp[i][1] = max(dp[i - 1][1] , dp[i - 1][1] + nums[i - 1]);
dp[i][2] = max(dp[i - 1][2] , dp[i - 1][2] + nums[i - 1]);
}
else if(r == 1){
dp[i][0] = max(dp[i - 1][0] , dp[i - 1][2] + nums[i - 1]);
dp[i][1] = max(dp[i - 1][1] , dp[i - 1][0] + nums[i - 1]);
dp[i][2] = max(dp[i - 1][2] , dp[i - 1][1] + nums[i - 1]);
}
else{
dp[i][0] = max(dp[i - 1][0] , dp[i - 1][1] + nums[i - 1]);
dp[i][1] = max(dp[i - 1][1] , dp[i - 1][2] + nums[i - 1]);
dp[i][2] = max(dp[i - 1][2] , dp[i - 1][0] + nums[i - 1]);
}
}
return dp[n][0];
}
};
7.回文子串数目
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
如果设置 dp[i] 的意义是指以下标 i 结尾的字符串有 dp[i] 个回文串,那就会很难想,因为dp[i] 和 dp[i-1] ,dp[i + 1] 看上去都没啥关系
所以应该这样设置:bool类型dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
假设s[i] != s[j],不是回文子串
s[i] == s[j],j - i <= 1,是回文串(a或aa的情况),如果 i 和 j 差距大于1,就撤回一步看dp[i + 1][j - 1]是不是回文子串
注意初始化,第二个for的j要从i开始不是i+1,差一步都不行
class Solution {
public:
int countSubstrings(string s) {
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
int res = 0;
for(int i = s.size() - 1; i >= 0; i --){
for(int j = i; j < s.size(); j ++){
if(s[i] == s[j]){
if(j - i <= 1){
res++;
dp[i][j] = true;
}else{
if(dp[i + 1][j - 1]){
res++;
dp[i][j] = true;
}
}
}
}
}
return res;
}
};
8.最长回文子序列
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。
子序列,所以可以不连续
大致逻辑和3一样
在s[i] == s[j]时,dp[i][j] = dp[i + 1][j - 1] + 2
在s[i] != s[j]时,加入s[j]的回文子序列长度为dp[i + 1][j]。加入s[i]的回文子序列长度为dp[i][j - 1]。
从递推式可以看出来,i == j的情况没有覆盖,所以需要初始化为1
另外加入s长度只有一个的话,无法进入for循环,这个时候应该输出1,所以res也需要初始化为1
class Solution {
public:
int longestPalindromeSubseq(string s) {
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
int res = 1;
for(int i = 0; i < s.size(); i ++) dp[i][i] = 1;
for(int i = s.size() - 1; i >= 0; i --){
for(int j = i + 1; j < s.size(); 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]);
if(dp[i][j] > res) res = dp[i][j];
}
}
return res;
}
};
9.最长回文子串
给你一个字符串 s,找到 s 中最长的 回文子串。
和3差不多
class Solution {
public:
string longestPalindrome(string s) {
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
int l = 0, maxLen = -1;
for(int i = s.size() - 1; i >= 0; i --){
for(int j = i; j < s.size(); j ++){
if(s[i] == s[j]){
if(j - i <= 1) dp[i][j] = true;
else if(dp[i + 1][j - 1]) dp[i][j] = true;
}
if(dp[i][j] && j - i + 1 > maxLen){
maxLen = j - i + 1;
l = i;
}
}
}
return s.substr(l, maxLen);
}
};