题目来源
题目描述
class Solution {
public:
vector<int> getMaxMatrix(vector<vector<int>>& matrix) {
}
};
题目解析
分析数据量
1 <= matrix.length, matrix[0].length <= 200
: 1 0 8 / 1 0 2 = 1 0 6 , ( 1 0 2 ) 3 = 1 0 6 10^8 / 10^2 = 10^6,(10^2)^3 = 10^6 108/102=106,(102)3=106,所以算法最大时间复杂度是 O ( N 3 ) O(N^3) O(N3),可以用dp来做
分析
由简入难,先从最简单的问题的入手吧:
给出一个整形数组,问最大子数组的和是多少?要求数组连续。
- 输入样例:nums = [-3,4,-1,2,1,-5,4]
- 输出样例:6,即连续子数组[4,-1,2,1]的和最大
思路:
可以DP,但是没有必要。可以这样理解:
- 当之前已有的数组和大于0时,它必然对此时的和有贡献,因此在其基础上累加
if sum > 0 : sum += nums[i]
- 当之前已有的数组和小于0时,此时应该另起炉灶
if sum < 0 : sum = nums[i]
代码如下:
int maxSubArray(vector<int> &nums) {
if(nums.empty()){
return 0;
}
int n = nums.size();
int sum = nums[0];
int max = nums[0];
for (int i = 1; i < n; ++i) {
sum = nums[i] + (sum > 0 ? sum : 0);
max = std::max(max, sum);
}
return max;
}
如果想要的是最大数组位置而非最大数组和呢?
思路:
- 仍然需要维护最大值max,数组和sum
- 另外需要设置一个start表示数组的左边界,而右边界就是当前的位置 i i i,不用专门设置变量
- 那么,start应该在何时更新呢?在由于sum小于0而另起炉灶时更新start
代码:
std::vector<int> maxSubArray(vector<int> &nums) {
if(nums.empty()){
return {};
}
std::vector<int> ans(2);
int n = nums.size();
int sum = nums[0];
int max = nums[0];
int start = 0;
for (int i = 1; i < n; ++i) {
if(sum >= 0){
sum += nums[i];
}else{
sum = nums[i];
start = 1;
}
if(sum > max){
max = sum;
ans[0] = start;
ans[1] = i;
}
}
return ans;
}
问题升维,当数组变成矩阵,如何找到这个最大值及其位置:
思路:我们将任意连续行合并为一行,不就变成了多个最大子数组问题了吗?
也就是将二维转换为一维矩阵:
- 可以想象一块夹板,然后分别表示起始行、结束行
- 可以在起始行和结束行找一个起始列和结束列,这样就确定了一个矩阵
思路:
- 设置一个上边界top和一个下边界bottom,将之间的行进行合并([top, bottom]),尝试更新最大值;最终结果的"上下"即为当前的上下边界,"左右"在求解最大子数组问题时得出。
- 在进行行的合并时,可以进行前缀和优化,即对每一列生成前缀和数组。
std::vector<int> maxSubArray(vector<std::vector<int>> &m) {
int M = m.size(), N = m[0].size();
int max = m[0][0];
std::vector<int> ans(4);
// 构造列的前缀和
std::vector<std::vector<int>> preSum(M + 1, std::vector<int> (N));
for (int i = 1; i < M + 1; ++i) {
for (int j = 0; j < N; ++j) {
preSum[i][j] = preSum[i - 1][j] + m[i - 1][j];
}
}
// 合并行
for (int top = 0; top < M; ++top) {
for (int bottom = top; bottom < M; ++bottom) {
// 构造一维矩阵
std::vector<int> arr(N);
for (int i = 0; i < N; ++i) {
ans[i] = preSum[bottom + 1][i] - preSum[top][i];
}
// 最大子数组问题
int start = 0;
int sum = arr[0];
for (int i = 1; i < N; ++i) {
if(sum > 0){
sum += arr[i];
}else{
sum = arr[i];
start = i;
}
if(sum > max){
max = sum;
ans[0] = top;
ans[1] = start;
ans[2] = bottom;
ans[3] = i;
}
}
}
}
return ans;
}