水滴容纳问题
给定数字串,比如2 1 3 4 5,每个数字表示木棍的长度,只考虑一维的
情况,假如空中有水滴不断的落下,问木棍之间最多能容纳多少水?
比如上面的1在2和3之间,所以能够容纳1滴水。
解法-
分别计算出每个木棍左边和右边的最长木棍,假定为L,R,则容纳的水
为:min(L,R)- 当前木棍的长度。 使用两个数组,每个数组元素存储
0-i和i-n的最大值
解法二
使用两个指针pL, pR从两边向中间变量,maxL,maxR分别记录两个指针
遍历过的最大值。对于pL,其左边最大值为maxL,右边最大值大于等于
maxR,如下图所示,假定3的右边最大值为maxTR,maxTR>=maxR,当
maxR>=maxL,则maxTR>=maxL,则pL最多容纳maxL-*pL, 则下一步移动pL
指针,同理,当maxL>maxR时,可以计算出pR的最多容水,下一步移动pR
指针。
| 2 | 1 | 3 | 4 | 5 |
| maxL | | pL | PR | maxR |
情况,假如空中有水滴不断的落下,问木棍之间最多能容纳多少水?
比如上面的1在2和3之间,所以能够容纳1滴水。
解法-
分别计算出每个木棍左边和右边的最长木棍,假定为L,R,则容纳的水
为:min(L,R)- 当前木棍的长度。 使用两个数组,每个数组元素存储
0-i和i-n的最大值
#include <algorithm>
#include <iostream>
void max_forward(int a[], int len, int max[]) {
max[0] = a[0];
for(int i = 1 ; i < len ; i++ )
max[i] = std::max( a[i], max[i-1] );
}
void max_backward(int a[], int len, int max[]) {
max[len - 1] = a[len - 1];
for( int i = len - 2 ; i >= 0 ; i-- )
max[i] = std::max( a[i], max[i+1] );
}
int main(int argc, char *argv[])
{
int a[100], b[100];
int c[] = { 0,1,0,2,1,0,1,3,2,1,2,1 };
const int len = sizeof(c) / sizeof(int);
max_backward(c, len, a);
max_forward(c, len, b);
int sum = 0;
for( int i = 0 ; i < len; i++ )
sum += std::min(b[i], a[i]) - c[i];
std::cout << sum << std::endl;
getchar();
return 0;
}
解法二
使用两个指针pL, pR从两边向中间变量,maxL,maxR分别记录两个指针
遍历过的最大值。对于pL,其左边最大值为maxL,右边最大值大于等于
maxR,如下图所示,假定3的右边最大值为maxTR,maxTR>=maxR,当
maxR>=maxL,则maxTR>=maxL,则pL最多容纳maxL-*pL, 则下一步移动pL
指针,同理,当maxL>maxR时,可以计算出pR的最多容水,下一步移动pR
指针。
| 2 | 1 | 3 | 4 | 5 |
| maxL | | pL | PR | maxR |
#include <climits>
#include <iostream>
#include <algorithm>
int holderWater( int *a, int len ) {
int sum = 0;
int pL = 0, pR = len - 1;
int maxL = a[pL], maxR = a[pR];
while( pL <= pR ) {
if( maxL < maxR ) {
sum += maxL - a[pL];
++pL;
maxL = std::max( maxL, a[pL] );
} else {
sum += maxR - a[pR];
--pR;
maxR = std::max( maxR, a[pR] );
}
}
return sum;
}
int main(int argc, char *argv[])
{
int a[] = {3, 4, 1, 2, 5, 2, 7, 0};
const int len = sizeof(a) / sizeof(a[0]);
std::cout << holderWater( a, len ) << std::endl ;
getchar();
return 0;
}
上面水滴问题是一维的,当我们将问题扩展到二维,给定一个数字矩阵,每个数字表示木棍的长度,加入不断的水滴落在木棍上,问最多可以容纳
多少水,对于任意木棍,只考虑上下左右的情况。
该问题是一个比较复杂的搜索问题。分三种情况,情况一、当搜索到某一
个位置,发现上下左右有一个数字比当前数字小,则显然不能盛水,情况
二,当发现周围都比当前位置数字大,则可以盛水,H = min{T,B,L,R} -
cur, 情况三,当发现周围有的比当前大,有的和当前位置相等,则应该接
着搜索周围的位置,情况三的解决方法如下:
设计函数tryNeigMin, 首先将当前节点位置标记为true,含义是该位置能
容纳多少水要看四周的数字的最小值,如果不标记,则四周的位置的周围
也包含当前节点,则是一个死循环。搜索到某位置,发现周围有的位置标
记为true,则忽略该位置,如果发现周围有的位置比当前小,则结束搜
索,表明当前位置不能盛水,如果发现都比当前位置大,则可以盛水,否
则,接着搜索四周标记为false的位置。
遍历所有位置,使用上面的三种情况考虑盛水,一次遍历并不能结束,如
下图所示,第一次遍历将长度为0的木棍上面装3滴水,但是还是可以继续
盛水。
| 4 | 4 | 4 | 4 |
| 4 | 3 | 3 | 4 |
| 4 | 3 | 0 | 4 |
| 4 | 4 | 4 | 4 |
#include <iostream>
#include <vector>
#include <limits.h>
using namespace std;
int a[][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
//-1 边界,m[i][j]相等,表明要查看周围的,大于,表明可以容纳水。
int neigMin_1( vector<vector<int> > &m, int i, int j,
vector<vector<bool> > &Visited) {
int maxR = m.size(), maxC = m[0].size();
int min = 1000;
for( int k = 0; k < 4; k++ ) {
int p1 = i + a[k][0], p2 = j + a[k][1];
if( p1 < 0 || p1 >= maxR || p2 < 0 || p2 > maxC )
return -1;
if( Visited[p1][p2] ) continue;
if( m[p1][p2] < m[i][j] ) return m[p1][p2];
min = std::min( min, m[p1][p2] );
}
//返回INT_MAX,表明四周标记了,最终的返回值由四周的返回值确定。
return min;
}
int tryNeigMin( vector<vector<int> > &m, int i, int j,
vector<vector<bool> > &Visited ) {
Visited[i][j] = true;
int min = neigMin_1(m, i, j, Visited);
if( min != m[i][j] )
return min;
//所有边界返回-1,不会到这里
int rMin = 1000;
for( int k = 0; k < 4; k++ ) {
int p1 = i + a[k][0], p2 = j + a[k][1];
if( !Visited[p1][p2] && m[p1][p2] == m[i][j] ) {
rMin = std::min( rMin, tryNeigMin(m, p1, p2, Visited) );
} else if( !Visited[p1][p2] ){
rMin = std::min( rMin, m[p1][p2] );
}
if( rMin <= m[i][j] )
return rMin;
}
return rMin;
}
int solve(vector<vector<int> > &m)
{
int water = 0;
while( true ) {
bool find = 0;
for( int i = 1 ; static_cast<unsigned>(i) < m.size() - 1 ; i++) {
for( int j = 1 ; static_cast<unsigned>(j) < m[i].size() - 1 ; j++ ) {
vector<vector<bool> > Visited(m.size(),
vector<bool>(m[0].size(), false));
int w = tryNeigMin(m, i, j, Visited);
if( w > m[i][j] ) {
water += w - m[i][j];
m[i][j] = w;
find = true;
}
}
}
if( !find ) break;
}
return water;
}
int main(int argc, char *argv[])
{
int a[][4] = {
{4, 4, 4, 4},
{4, 3, 3, 4},
{4, 3, 0, 4},
{4, 4, 4, 4}
};
vector<vector<int> > m;
for( int i = 0 ; i < 4 ; i++ )
m.push_back(vector<int>(a[i], a[i]+4));
std::cout << "totally can hold " << solve(m) << " drop(s) water\n";
getchar();
return 0;
}
直线上的硬币问题
各种面值的硬币放在直线上,比如:3, 2, 2, 3, 1, 2,现在有A,B两个人轮流取硬币,只能从直线的两边取硬币。
1. 假如有偶数个硬币,A先拿,如何保证A拿的硬币一定比B多。
2. 假定A先拿,如何保证A拿到尽量多的钱,
如果有偶数个硬币,A可以保证拿到所有的奇数或者偶数编号的硬币,
比如硬币的编号为0 1 2 3 4 5, 如果A想拿到所有偶数编号的硬币,则
A先拿0号硬币,这样B就只能拿奇数编号的1或者5,然后A可以继续拿偶
数,,,,,。如果A想拿奇数编号的硬币,过程类似。只要计算出偶数
、奇数编号的硬币之和,取其中的较大者,则能保证A拿的硬币不比B少。
第二个问题采用动态规划求解。(前提是B也聪明,知道动态规划^-^)
F[i,j]表示编号为i到j的硬币,如果先拿,最多可以拿多少钱
S[i,j]表示i到j的硬币之和
F[i,j] = max{S[i,j]-F[i+1,j] 选择拿i,剩下的由B先拿
S[i,j]-F[i,j+1] 选择拿j,剩下由B先拿
实际上,如果A可以选择先拿还是后拿,只需要计算F[0,n],
S[0,n]-F[0,n]中较大者,然后就可以选择先拿
#define N 100
int f[N][N];
int s[N];
void preProcess( int coins[], int len )
{
s[0] = coins[0];
for( int i = 1; i < len; i++ )
s[i] = s[i - 1] + coins[i];
}
int DP( int coins[], int len )
{
preProcess( coins, len );
for( int i = 0; i < len; i++ )
f[i][i] = coins[i];
int sum = 0;
for( int i = 2; i <= len; i++ ) {
for( int j = 0; j < len; j++ ) {
if( j == 0 )
sum = s[j + i - 1];
else
sum = s[j + i - 1] - s[j - 1];
f[j][j + i - 1] = sum - min(f[j + 1][j + i - 1], f[j][j + i - 2]);
}
}
return f[0][len - 1];
}
int main()
{
int coins[] = { 3, 2, 2, 3, 1, 2 };
int len = sizeof(coins) / sizeof(coins[0]);
cout<<DP(coins, len)<<endl;
getchar();
return 0;
}
最大和子序列问题
一维最大和子序列
给出一系列的数字,比如4, -9, 3, 5, -7, 10, 13, -20, -8, 7, 9,
求解其连续子序列,使序列和最大。
假定以i结尾的和最大子序列为S[start, i],则以i+1结尾的最大和子序
列为S[k, i+1], k不可能小于start,因为
S[k,i+1]=S[i+1,i+1]+S[k,i], 其值最大,则S[k, i]也是以i结尾最大,所
有k不可能小于start。k有可能等于start或者大于start,如果k在start
和i之间,则不可能,因为S[k,i]不是最大的,k只能取i+1, 或者
start,显然,如果S[start,i] > 0, k取start,否则,k取i+1.
求解其连续子序列,使序列和最大。
假定以i结尾的和最大子序列为S[start, i],则以i+1结尾的最大和子序
列为S[k, i+1], k不可能小于start,因为
S[k,i+1]=S[i+1,i+1]+S[k,i], 其值最大,则S[k, i]也是以i结尾最大,所
有k不可能小于start。k有可能等于start或者大于start,如果k在start
和i之间,则不可能,因为S[k,i]不是最大的,k只能取i+1, 或者
start,显然,如果S[start,i] > 0, k取start,否则,k取i+1.
#include <iostream>
#include <utility>
#include <climits>
#include <vector>
std::pair<int, int> findSubMax( int A[], int len, int &max ) {
std::pair<int, int> ret;
int start = -1, maxt = INT_MIN, cur = 0;
for( int i = 0; i < len; i++ ) {
if( cur > 0 ) {
cur += A[i];
} else {
start = i;
cur = A[i];
}
if( cur > maxt ) {
maxt = cur;
ret = std::pair<int, int>( start, i );
}
}
max = maxt;
return ret;
}
二维最大和子矩阵
将该问题扩展到二维的情况,假定给定一个矩阵,求解和最大的子矩阵。假定首先固定行,计算从i行到j行的最大子矩阵,则可以将i行到j行的列
值加起来,这样就将二维问题转换为上面的一维问题,就可以求解出i行
到j行最大子矩阵的列值。
上面固定了i行和j行,因为i和j是不固定,需要穷举所有可能的i和j。
const int LEN = 20 ;
int sums[LEN][LEN];
int tmp[LEN];
int maxSubMaxtri( int m[][5], int r, int c,
std::vector<std::pair<int, int> > &vp ) {
int max = INT_MIN;
std::pair<int, int> lr, td;
for( int i = 0; i < r; i++ ) {
for( int j = 0; j < c; j++ ) {
sums[i][j] = m[i][j] + (i == 0 ? 0 : sums[i - 1][j]);
}
}
for( int i = 0; i < r; i++ ) {
for( int j = i; j < r; j++ ) {
for( int k = 0; k < c; k++ ) {
tmp[k] = sums[j][k] - (i == 0 ? 0 : sums[i - 1][k]);
}
int maxt = 0;
std::pair<int, int> cr = findSubMax(tmp, c, maxt);
if(maxt > max){
td.first = i;
td.second = j;
lr = cr;
max = maxt;
}
}
}
vp.push_back(lr);
vp.push_back(td);
return max;
}
测试
int main()
{
int A[] = {
4, -9, 3, 5, -7, 10, 13, -20, -8, 7, 9
};
const int len = sizeof(A) / sizeof(A[0]);
int max;
std::pair<int, int> p = findSubMax(A, len, max);
std::cout<<"max:"<<max<<"pair:"<<p.first<<" "<<p.second<<std::endl;
std::vector<std::pair<int, int> > vp;
int B[4][5] = {
{
1, -2, 3, -7,9
},
{
-4, 3, 6, 7, 2
},{
3, -6, 2, 4, 5
}, {
-8, 9, -5, -1, 2
}
};
int maxt = maxSubMaxtri(B, 4, 5, vp);
std::cout<<"maxt:"<<maxt<<" bord:"<<vp[0].first
<<" "<<vp[0].second<<"\t"<<vp[1].first
<<" "<<vp[1].second;
getchar();
return 0;
}