水滴、最大和子序列问题

水滴容纳问题

       给定数字串,比如2 1 3 4 5,每个数字表示木棍的长度,只考虑一维的
       情况,假如空中有水滴不断的落下,问木棍之间最多能容纳多少水?
       比如上面的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.
#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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值