数组上一系列查找问题

来源:互联网 发布:pano.js 编辑:程序博客网 时间:2024/06/03 03:36

问题一:已知一数组是有两个递增子序列组合而成,找到两个子序列的分界点

情况一:如果该数组是旋转数组,即第二个递增子序列的元素都比第一个递增子序列的元素小或可以通过旋转将2个子序列合并成一个递增子序列的问题。

例如:{3,4,5,1,2}就是旋转数组,找到分界点值

分析:由于第二个子序列的元素都比第一个子序列的元素小,我们可以采用二分思想,如果mid值比left值大,说明mid值在第一子序列上,将left更新为mid的位置;如果mid值比right值小,说明mid值在第二个子序列上,将right更新为mid所在位置如果left与right相邻,则结束,此时,left和right分别为两个子序列的末元素和首元素。注意:如果left值、mid值和right值相等时,此时是无法判断赋值语句的。比如{1,0,1,1,1}和{1,1,1,0,1}这两种情况。

代码如下:

#include "stdafx.h"#include <iostream>#include <assert.h>using namespace std;int MinOrder(int* pArr, int left, int right){//此时数组的left和right值相等,只要找到第一个比left值小的,则此时就是分界点int result = pArr[left];for ( int i = left+1; i <= right; i++ ){if (pArr[i] < result)return (i-1);}return right;//如果数组从left到right值全部相等,返回最右端下标}//返回的是第一个子数组的末元素的位置int MaxBound(int* pArr, int len){assert(pArr);int left = 0, right = len-1,mid;while ( (right-left)>1 ){mid = left+ ((right-left)>>1);if (pArr[left] == pArr[right] &&pArr[left] == pArr[mid] ){return MinOrder(pArr, left, right);break;}if ( pArr[left] <= pArr[mid] )left = mid;else if ( pArr[right] >= pArr[mid] )right = mid;}return left;}int _tmain(int argc, _TCHAR* argv[]){int a[]={3,4,5,1,2};int b[]={1,0,1,1,1};int c[]={1,1,1,0,1};cout <<"第一个数组的分界点下标为: "<< MaxBound(a, 5) <<endl;cout <<"第二个数组的分界点下标为: "<< MaxBound(b, 5) <<endl;cout <<"第三个数组的分界点下标为: "<< MaxBound(c, 5) <<endl;return 0;}

情况二:不符合情况一的数组,我们称之为情况二数组。

例如:{2,3,5,7,9,3,4,6,8,10},,找到分界点值

分析:这种情况,我还没有想出好方法,只能遍历,时间复杂度为O(n)。希望网友指点指点。。。。

代码:

#include "stdafx.h"#include <iostream>#include <assert.h>using namespace std;//返回的是第一个子数组的末元素位置int MaxBound(int* pArr, int len){assert(pArr);for ( int i = 0; i < len-1; i++ ){if ( pArr[i] > pArr[i+1] )return i;}return (len-1);//如果数组是递增的,则返回最后一个元素位置}int _tmain(int argc, _TCHAR* argv[]){int a[]={2,3,5,7,9,3,4,6,8,10};int b[]={2,5,8,9,9,9,9,9,9,9};cout <<"第一个数组的分界点下标为: "<< MaxBound(a, 10) <<endl;cout <<"第二个数组的分界点下标为: "<< MaxBound(b, 10) <<endl;return 0;}


问题二:已知一数组先递增后递减,求该数组的最大值,时间复杂度要求log级

比如一数组为{1,3,5,7,9,10,8,6,5,4},我们可以采用三分查找去解决该问题,每次mid为left和right的中间值下标,midmid为left和right的3/4处的值下标。通过比较数组中mid和midmid值,如果mid值比midmid值大,那说明mid值靠近极值,将right值更新为midmid值,反之midmid值接近极值,用mid值更新left值。三分查找可以解决在凸函数上的极值问题。不管先增后减还是先减后增。

代码:

#include "stdafx.h"#include <iostream>#include <assert.h>using namespace std;//返回的是第一个递增子数组的末元素位置int MaxBound(int* pArr, int len){assert(pArr);int left = 0, right = len-1;int mid = left+( (right-left)>>1 );int midmid = mid +( (right-mid)>>1 );while (mid != midmid){if ( pArr[mid] > pArr[midmid] )right = midmid;else if ( pArr[mid] < pArr[midmid] )left = mid;else//此时,下标分别为mid和midmid的值相等,那么第一个非严格递增的末元素位置应该在midmid或之后{left = midmid;}mid = left+( (right-left)>>1 );midmid = mid +( (right-mid)>>1 );}//如果区间末位置的两个元素相等,保证返回最后一个元素的下标if (pArr[mid] <= pArr[mid+1])mid++;return mid;//如果数组是递增的,则返回最后一个元素位置}int _tmain(int argc, _TCHAR* argv[]){int a[]={1,3,5,7,9,10,8,6,5,4};int b[]={2,5,8,9,9,9,9,9,9,9};cout <<"第一个数组的分界点下标为: "<< MaxBound(a, 10) <<endl;cout <<"第二个数组的分界点下标为: "<< MaxBound(b, 10) <<endl;return 0;}

问题三:求一维子数组的最大和

分析:可以采用分治思想:可以考虑数组的第一个元素A[0],以及最大的一段数组(A[i],……,A[j])跟A[0]的关系,有几种情况1:当0=i=j时,元素A[0]本身构成和最大的一段;2:当0=i<j时,和最大的一段以A[0]开始;3:当0<i时,元素A[0\跟和最大的一段没有关系。因此,我们可以将一个大问题(N个元素的数组)转换成为一个较小的问题(n-1个元素的数组)。

代码:

#include "stdafx.h"#include <iostream>#include <assert.h>using namespace std;int MaxSum(int* A, int n){assert(A);int nStart = A[n-1];int nAll = A[n-1];for ( int i = n-2; i >= 0; i-- ){nStart = ( (nStart+A[i])>A[i] )? (nStart+A[i]) : A[i];nAll = (nStart > nAll)? nStart : nAll;}return nAll;}int _tmain(int argc, _TCHAR* argv[]){int a[]={1,-2,3,5,-3,2 };int b[]={0,-2,3,5,-1,2 };int c[]={-9,-2,-3,-5,-3};cout << MaxSum(a,6) <<endl;//8cout << MaxSum(b,6) <<endl;//9cout << MaxSum(c,5) <<endl;//-2return 0;}

问题四:求二维子数组的最大和

分析:我们可以将二维问题转化成一维来做。假设已经确定了矩形区域的上下界,比如知道矩形区域的上下边界分别是第a行和第c行,现在确定左右边界。和问题三类似。时间复杂度为O(n^2*m)

代码:

#include "stdafx.h"#include <iostream>#include <limits.h>#include <assert.h>using namespace std;//求在第a行和第c行之间的第colomn列的所有元素之和int BC(int A[][6], int a, int c, int colomn){assert(A);int sum = 0;for ( int i = a; i <= c; i++ ){sum += A[i][colomn];}return sum;}int MaxSum(int A[][6], int row, int colomn){assert(A);int maximum = INT_MIN;int Start,All;for ( int a = 0; a < row; a++ ){for ( int c = a; c < row; c++ ){Start = BC(A,a,c,colomn-1);//计算第a行和第c行之间的第colomn-1列的所有元素之和All = BC(A,a,c,colomn-1);for ( int i = colomn-2; i > 0; i-- ){int temp = BC(A,a,c,i);Start = ( (Start+temp)>temp )? (Start+temp) : temp;All = ( Start>All )? Start : All;}if (All > maximum)maximum = All;}}return maximum;}int _tmain(int argc, _TCHAR* argv[]){int a[3][6]={{1,-2,3,5,-3,2 }, {0,-2,3,5,-1,2 }, {-9,-2,-3,-5,-3,0 } };cout << MaxSum( a, 3, 6 ) <<endl; //16return 0;}


问题五:求数组最长递增子序列

写一个时间复杂度尽可能低的程序,求一个一维数组N中最长的递增子序列的长度。

例如:在序列1,-1,2,-3,4,-5,6,-7中,其中最长递增子序列的长度为4(如1,2,4,6)。

分析:对于某个给定阶段的状态来说,它以前各阶段的状态无法直接影响它未来的决策,而只能间接地通过当前状态来影响。换句话说,每个状态都是过去历史的一个完整总结。以上述例子为例,我们在找到4之后,并不关心4之前的两个值具体是怎样,因为它对找到6并没有直接影响。因此这个问题满足无后效应,可以采用动态规划来解决。

            可以通过数字的规律来分析目标串:1,-1,2,-3,4,-5,6,-7。

使用i来表示当前遍历的位置:

当i=1时,显然,最长的递增序列为(1),序列长度为1.

当i=2时,由于-1<1.因此,必须丢弃第一个值然后重新建立串。当前的递增序列为(-1),长度为1.

当i=3时,由于2>1,2>-1.因此,最长的递增序列为(1,2)或(-1,2),长度为2.在这里,2前面是1还是-1对求出后面的递增序列没有直接影响。

一次类推之后,可以得出如下结论。

假设在目标数组arr[]的前i个元素中,最长递增子序列的长度为LIS[i]。那么,

LIS[i+1]=max{1, LIS[k]+1},  arr[i+1]>arr[k],  for each k<= i

即如果arr[i+1]大于arr[k],那么第i+1个広可以接在LIS[k}长的子序列后面构成一个更长的子序列。与此同时arr[i+1]本身至少可以构成一个长度为1的子序列。

时间复杂度为O(n^2)。

代码:

int LIS(int* pArr, int len){int* pLIS = new int[len];//记录到当前下标位置是的最长递增子序列的长度for ( int i = 0; i < len; i++ ){pLIS[i] = 1;//初始为1,表示递增子序列只有它本身一个元素for ( int j = 0; j < i; j++ ){if (pArr[i]>pArr[j] && pLIS[j]+1 > pLIS[i]){pLIS[i] = pLIS[j]+1;}}}//遍历pLIS数组,取出最大值int maxLIS = pArr[0];for ( int i = 1; i < len; i++ ){if ( maxLIS < pLIS[i] )maxLIS = pLIS[i];}return maxLIS;}int _tmain(int argc, _TCHAR* argv[]){int arr[8] = {1,-1,2,-3,4,-5,6,-7}; cout << LIS(arr, 8) <<endl;return 0;}



问题六:数组分割

有一个无序,元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个子数组,并使两个子数组的和最接近?例如:数组为1,5,7,8,9,6,3,11,20,17,分割成1,3,11,8,20和5,7,9,6,17两个数组

分析:假设2n个元素之和为sum,从2n个数中找出n个元素之和,有2中可能,一是大于sum/2,一是小于或等于sum/2.这两种没有本质的区别。因此,可以只考虑小于等于sum/2的情况。采用动态规划思想

可以把任务分成2n步,第k步的定义是前k个元素中任意i个元素之和,所有可能取值之集用Sk表示(只考虑取值小于等于sum/2的情况)。

然后将第k步拆分成两小步,即首先得到(k-1)个元素中任意i个元素,总共有2^(k-1) -1种取值,设这个取值集合为Sk-1={Vi}。第二步,就是令Sk=Sk-1+{ Vi+arr[k] },即完成第k步。

但是,由k值逐渐增大,Sk的计算量是指数级,能否设计一个算法使得第k步所花费时间与k无关呢?我们不妨倒过来想,原来是给定Sk-1={Vi},求Sk。那么我们能不能给定Sk的可能值v和arr[k],去寻找v-arr[k]是否在Sk-1中呢?由于Sk可能值的集合大小与k无关,所以这样设计的动态规划算法其第k步的时间复杂度与k无关。

代码:


问题七:区间重合判断

给定一个源区间[x,y](y>=x)和N个无序的目标区间[x1,y1],[x2,y2],[x3,y3],[x4,y4],……[xn,yn],,

判断源区间[x,y]是不是在目标区间内。例如,给定源区间[1,6]和一组无序的目标区间

[2,3][1,2][3,9], 即可认为区间[1,6]在区间[2,3][1,2][3,9]内,因为目标区间实际上是[1,9]。

分析:已知这么多区间,可以开辟长度为源区间长度的数组,初始值为0,再扫描目标区间,如果目标区间覆盖了相应的源区间,只需将对应的区间赋1即可。最终判断数组中是否有0,有0则未覆盖完全。

但是这种方法有局限性,只适合线段端点是整型的。

代码:

struct Region{int x;int y;};bool IsOverlap(const Region* pSour, const vector<Region> targetR ){if(pSour==NULL || targetR.size()==0)return true;bool* pArrayBool = new bool[pSour->y - pSour->x];//开辟源区间长度的数组memset(pArrayBool, 0, sizeof(bool)*(pSour->y-pSour->x) );int start,end;for ( int i = 0; i < targetR.size(); i++ ){start = (pSour->x > targetR[i].x)? pSour->x : targetR[i].x;end = (pSour->y < targetR[i].y)? pSour->y : targetR[i].y;for ( int j = start-pSour->x; j<end-pSour->x; j++ )pArrayBool[j] = true;}bool flag = true;for ( int k = 0; k < (pSour->y-pSour->x); k++ ){flag &= pArrayBool[k];}return flag;}int _tmain(int argc, _TCHAR* argv[]){Region source={1,6};Region target1={2,3};Region target2={1,2};Region target3={3,9};vector<Region> container;container.push_back(target1);container.push_back(target2);container.push_back(target3);if ( IsOverlap(&source, container) )cout << "区间重合" <<endl;elsecout << "区间不重合" <<endl;return 0;}