目录
最大子序和 (最大子数组和)
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
【思路】动态规划,O(n)
状态定义:dp[i]为以nums[i]结尾的最大子序和
状态转移方程:dp[i] = max( dp[i-1] + nums[i] , nums[i] )
由于dp[i] 只和 前一个状态dp[i-1] 及当前元素有关,所以可不必定义dp数组
int maxSubArray(vector<int>& nums) {
if(nums.size()==0) return 0;
int sum = 0, max_sum = nums[0]; // max_sum不能初始化为0, 以防数组只有负数
for(int num : nums){
if(sum>0)
sum += num;
else sum = num;
if(sum>max_sum) max_sum = sum;
}
return max_sum;
}
最大子序和 (返回子数组位置)
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回它的开始、末尾元素的下标。
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: [3,6]
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
【思路】与上题唯一不同是 返回类型,所以同样是动态规划,只是在遍历过程中要记录当前子数组的开始下标。时间O(n)
vector<int> maxSubArray(vector<int>& nums) {
int maxsum = INT_MIN;
int dp_i = nums[0];
vector<int> ans(2); //用来记录答案
int begin = 0;
for(int i=1 ; i < nums.size() ; i++ ){
if( dp_i > 0 ) //dp[i-1]>0时
dp_i+=nums[i];
else{ //dp[i-1]<=0时
dp_i=nums[i];
begin = i; //当nums[i]自立门户时候,我们记录下子序列的起始位置
}
if(dp_i > maxsum){ //更新答案
maxsum = dp_i;
ans[0] = begin; //记录下起始和终止位置
ans[1] = i;
}
}
return ans;
}
最大子矩阵
给定一个正整数和负整数组成的 N × M 矩阵,找出元素总和最大的子矩阵,返回它的元素总和。
输入:
[1,3,-4,2],
[2,-1,2,-1]
输出: 5
【思路】 这题可转化为"最大子数组和"求解,只需要将二维转化为一维。
对于矩阵的每一列,我们将其加在一起,得到一维数组 b,b[i]表示矩阵第 i 列所有元素和。更准确的解释如下图:
那么怎么把每个子矩阵都遍历到呢?我们每次选择两行 i 和 j,代表子矩阵的上下边界, 并求每列的和(得到数组b),然后按"最大子数组和"的方法求数组 b 的最大子数组和。
时间复杂度:,其中遍历 i 和 j 需要O(n^2),再执行"最大子数组和"方法需要O(m)。
(参考题解)
int getMaxMatrix(vector<vector<int>>& matrix) {
vector<int> ans(4); //保存最大子矩阵的左上角和右下角的坐标
int N = matrix.size();
int M = matrix[0].size();
vector<int> b(M,0); //记录当前i~j行组成的子矩阵的每列的和,b[k]表示a[i][k]到a[j][k]的和
int sum; //相当于dp[i]
int maxsum = INT_MIN; //记录最大值
for(int i=0;i<N;i++){ // i为子矩阵的上边界,从上而下扫描
for(int t=0;t<M;t++)
b[t]=0; //每次改变子矩阵上边界,就要清空b,重新计算每列的和
for(int j=i;j<N;j++){ // j为子矩阵的下边界,遍历一次就相当于求一次最大子序列和
sum = 0; //重新求dp
for(int k=0;k<M;k++){
b[k] += matrix[j][k];
if(sum>0) sum += b[k];
else sum = b[k];
if(sum > maxsum) // 若当前子矩阵的和更大,则用它更新答案
maxsum = sum;
}
}
}
return maxsum;
}
最大子矩阵(返回矩阵坐标)
给定一个正整数和负整数组成的 N × M 矩阵,找出元素总和最大的子矩阵。
返回一个数组 [r1, c1, r2, c2],其中 r1, c1 分别代表子矩阵左上角的行号和列号,r2, c2 分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。
【与上题唯一不同是 返回类型】
输入:
[1,3,-4,2],
[2,-1,2,-1]
输出: [0,0,1,1]
【思路】和上题思路基本一样,只是在遍历过程中要更新当前最大子矩阵的坐标。
时间复杂度:,其中遍历 i 和 j 需要O(n^2),再执行"最大子数组和"方法需要O(m)。
(参考题解)
vector<int> getMaxMatrix(vector<vector<int>>& matrix) {
vector<int> ans(4); //保存最大子矩阵的左上角和右下角的坐标
int N = matrix.size();
int M = matrix[0].size();
vector<int> b(M,0);//记录当前i~j行组成的子矩阵的每列的和,b[k]表示a[i][k]到a[j][k]的和
int sum; //相当于dp[i]
int maxsum=INT_MIN; //记录最大值
int best_r1,best_c1; //暂时记录左上角,相当于最大子数组问题中的begin
for(int i=0;i<N;i++){ // i为子矩阵的上边界,从上而下扫描
for(int t=0;t<M;t++ ) b[t]=0; //每次改变子矩阵上边界,就要清空b,重新计算每列的和
for(int j=i;j<N;j++){ // j为子矩阵的下边界,遍历一次就相当于求一次最大子序列和
sum = 0; //重新求dp
for(int k=0;k<M;k++){
b[k] += matrix[j][k];
if(sum>0)
sum+=b[k];
else{
sum=b[k];
best_r1=i; //自立门户,暂时保存其左上角
best_c1=k;
}
if(sum > maxsum){ // 若当前子矩阵的和更大,则用它更新答案
maxsum = sum;
ans[0]=best_r1;
ans[1]=best_c1;
ans[2]=j;
ans[3]=k;
}
}
}
}
return ans;
}
环状矩阵的最大子矩阵和
猿辅导笔试题 20.8.22