【面试题】连续子数组乘积最大值与柱状图中找最大矩形
连续子数组求和最大值比较常见,乘积与求和有相通之处。10月9号美团的笔试考到了这题。
第一题:原题
给一个浮点数序列,求连续子串乘积的最大值,例如 -2.5,4,0,3,0.5,8,-1,则取出的最大乘积连续子串为3,0.5,8。也就是说,上述数组中,3 0.5 8这3个数的乘积3*0.5*8=12是最大的,而且是连续的。
解法一:
用动态规划很好做。问题的关键是,序列中有正数也有负数,所以,需要记录最大值,也要记录最小值。
1 double MaxMul(double *num, int len) 2 { 3 if(num == NULL || len < 1) 4 return 0; 5 6 double *maxMul = new double[len]; 7 double *minMul = new double[len]; 8 maxMul[0] = num[0]; 9 minMul[0] = num[0]; 10 double maxM = num[0]; 11 for(int i = 1; i < len; ++i) 12 { 13 maxMul[i] = maxMul[i-1]*num[i] > num[i] ? maxMul[i-1]*num[i] : num[i]; 14 maxMul[i] = maxMul[i] > minMul[i-1]*num[i] ? maxMul[i] : minMul[i-1]*num[i]; 15 minMul[i] = minMul[i-1]*num[i] < num[i] ? minMul[i-1]*num[i] : num[i]; 16 minMul[i] = minMul[i] < maxMul[i-1]*num[i] ? minMul[i] : maxMul[i-1]*num[i]; 17 maxM = maxM > maxMul[i] ? maxM : maxMul[i]; 18 } 19 delete[] maxMul; 20 delete[] minMul; 21 return maxM; 22 }
解法二:
也可以扫一遍序列做出来,不过也要记录最大值和最小值,以及当前的最大值和最小值,当当前的最大值小于1的时候,设置为当前的序列值,呃,感觉语言有点绕,+_+,可以看《剑指offer》面试题31:连续子数组的最大和,类比一下就行了。还是用代码说话吧,逻辑会比较清楚。
1 double MaxMul2(double *num, int len) 2 { 3 if(num == NULL || len < 1) 4 return 0; 5 6 double maxM = num[0]; 7 double maxMul = num[0]; 8 double minMul = num[0]; 9 double maxCur = num[0]; 10 double minCur = num[0]; 11 for(int i = 1; i < len; ++i) 12 { 13 if(maxCur < 1) 14 maxCur = num[i]; 15 else 16 maxCur *= num[i]; 17 minCur *= num[i]; 18 if(maxCur < minCur) 19 { 20 double tmp = maxCur; 21 maxCur = minCur; 22 minCur = tmp; 23 } 24 maxMul = maxMul > maxCur ? maxMul : maxCur; 25 minMul = minMul < minCur ? minMul : minCur; 26 } 27 return maxMul; 28 }
两个解法的时间复杂度都是O(1),解法一的空间复杂度是O(n),解法二的空间复杂度也是O(1)。其实解法一的空间复杂度也可以降为O(1),不用数组,用四个变量即可。
第二题:原题
在柱状图中找最大的矩形:给一组非负的整数来表示一个柱状图,设计一个算法找到最大面积的能适合到柱状图内的矩形。比如,对于这组数,1 2 3 4 1 ,有两种可能的方案,一种是适合到 2 3 4 内的矩形,面积是 2*3;另一种是适合到 3 4 内的矩形,面积是 3*2。
用数学点的描述就是,找所给数组的一个连续子数组,使该子数组的最小值与数组长度乘积最大。
解法是在待字闺中微信公众账号里面看到的,一个线性算法是用堆栈来保存当前可能的矩形(高度和起始位置)。从左到右扫描,对一个元素,如果
a)大于栈顶元素, push;
b)小于的话,pop所有的大于它的元素,计算面积,更新最大值。这时如果堆栈空,push一个新的元素,高度等于当前元素,起始位置为0;否则,push当前元素高度和栈顶的起始位置。
比如1 3 2 2 3这个数组,操作如下:
i |
C[i] |
栈操作 |
最大值 |
栈内容 |
0 |
1 |
push (1,0) |
(1,0) |
|
1 |
3 |
push (3,1) |
(3,1)(1,0) |
|
2 |
2 |
pop (3,1),push (2,1) |
(2-1)*3=3 |
(2,1)(1,0) |
3 |
2 |
什么都不做 |
(2,1)(1,0) |
|
4 |
3 |
push(3,4) |
(3,4)(2,1)(1,0) |
|
5 |
0 |
pop(3,4) |
(5-4)*3=3 |
(2,1)(1,0) |
5 |
0 |
pop(2,1) |
(5-1)*2=8 |
(1,0) |
5 |
0 |
pop(1,0) |
(5-0)*1=5 |
代码如下:
1 int MaxArea(int *num, int len) 2 { 3 if(num == NULL || len < 1) 4 return 0; 5 6 stack<int> numStack; 7 stack<int> indStack; 8 numStack.push(num[0]); 9 indStack.push(0); 10 int lastPopInd = 0; 11 int maxMul = num[0]; 12 for(int i = 1; i < len; ++i) 13 { 14 if(num[i] > numStack.top()) 15 { 16 numStack.push(num[i]); 17 indStack.push(i); 18 } 19 else if(num[i] < numStack.top()) 20 { 21 while(!numStack.empty() && num[i] < numStack.top()) 22 { 23 int numPop = numStack.top(); 24 lastPopInd = indStack.top(); 25 maxMul = (numPop * (i - lastPopInd)) > maxMul ? (numPop * (i - lastPopInd)) : maxMul; 26 numStack.pop(); 27 indStack.pop(); 28 } 29 if(numStack.empty() || num[i] > numStack.top()) 30 { 31 numStack.push(num[i]); 32 indStack.push(lastPopInd); 33 } 34 } 35 } 36 while(!numStack.empty()) 37 { 38 int numPop = numStack.top(); 39 lastPopInd = indStack.top(); 40 maxMul = (numPop * (len - lastPopInd)) > maxMul ? (numPop * (len - lastPopInd)) : maxMul; 41 numStack.pop(); 42 indStack.pop(); 43 } 44 return maxMul; 45 }
扩展问题是:
在一个位图中找面积最大的白色矩形:给你一个NxN的黑白位图,找一个面积最大的全白色的矩形。注意了,是一个矩形,不是任意一个白色相连的区域。
可以生成一个新的矩阵C,C[i][j]表示第j列,从第i个元素开始,包括第i个元素,向上数,直到遇到0时,1的个数。然后再每一行按一维做就可以了。