zoukankan      html  css  js  c++  java
  • 编程之美第2章 数字之魅

    2.1 求二进制数数中1的个数

    clip_image002

    clip_image004

    clip_image006clip_image008clip_image010

    2.2 不要被阶乘吓到

    clip_image012

    clip_image014

    clip_image016clip_image018

    clip_image020

    2.3 寻找发帖”水王”

    这个问题就是寻找数组中总数超过一半的数.

    clip_image022clip_image024

    现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。

    第1种方法:

    创建一个hash_map,key为数组中的数,value为此数出现的次数。遍历一遍数组,用hash_map统计每个数出现的次数,并用两个值存储目前出现次数最多的数和对应出现的次数。

    这样可以做到O(n)的时间复杂度和O(n)的空间复杂度,满足题目的要求。

    但是没有利用“一个数出现的次数超过了一半”这个特点。也许算法还有提高的空间。

    第2种方法(推荐):

    使用两个变量A和B,其中A存储某个数组中的数,B用来计数。开始时将B初始化为0。

    遍历数组,如果B=0,则令A等于当前数,令B等于1;如果当前数与A相同,则B=B+1;如果当前数与A不同,则令B=B-1。遍历结束时,A中的数就是要找的数。

    这个算法的时间复杂度是O(n),空间复杂度为O(1)。

    #if 0
    /*
     * 2.3
     */
    int overHalf(int *a,int n){
        int A,B;
        B=0;
        for(int i=0;i<n;i++){
            if(B==0){
                A=a[i];
                B=1;
            }
            else {
                if(A==a[i])
                    B++;
                else
                    B--;
            }
        }
        return A;
    }
    int main(){
    int array[]= {1, 2, 3, 1, 4, 5, 1, 1, 3, 1, 2, 1, 1};
    int sz=sizeof(array)/sizeof(array[0]);
    cout<<overHalf(array,sz)<<endl;
    
    }
    
    #endif

    参考: http://www.cnblogs.com/dartagnan/archive/2011/09/29/2195949.html

    2.4 1的数目问题

    clip_image026

    一种直接的方法是从1到N,对每一个数分别进行判断.

    clip_image028

    clip_image030

    imageimage

    #if 0
    /*
     * 2.4
     */
    int countof1(int N){
        int icnt=0;
        int ifactor=1;
        int ihigh=0,ilow=0,icurr=0;
        while(N/ifactor!=0){
            ilow=N-(N/ifactor)*ifactor;
            icurr=(N/ifactor)%10;
            ihigh=N/(ifactor*10);
            switch(icurr){
                case 0:
                    icnt+=ihigh*ifactor;
                    break;
                case 1:
                    icnt+=ihigh*ifactor+ilow+1;
                    break;
                default:
                    icnt+=(ihigh+1)*ifactor;
                    break;
            }
            ifactor*=10;
    
        }
        return icnt;
    }
    int main(){
        cout<<countof1(1111111110)<<endl;
    }
    
    #endif

    clip_image032

    clip_image034

    clip_image036

    2.5 寻找最大的K个数

    clip_image038

    clip_image040

    代码如下:

    /*
     * 2.5
     */
    int partition(int *a,int low,int high){
        int i=low,j=high+1;
        int pivot=a[low];
        while(1){
            do
                i++;
            while(a[i]<pivot);
            do
                j--;
            while(a[j]>pivot);
            if(i>=j)
                break;
            swap(a[i],a[j]);
        }
        swap(a[low],a[j]);
        return j;
    }
    int findK(int *a,int low,int high,int k){
        if(low<high){
            int mid=partition(a,low,high);
            int len=high-mid+1;
            if(len>k)
                return findK(a,mid+1,high,k);
            else if(len<k)
                return findK(a,low,mid-1,k-len);
            else
                return mid;
        }
    }
    void test_findk(){
        const int N=8;
        const int K=4;
        int a[N] = {5 ,2 ,66 ,23, 11 ,1 ,4 ,55} ;  
        int pos=findK(a , 0 , N - 1 , K) ;    
    
        for(int i = 0 ; i < N ; i++)  
            cout<<a[i]<<' ' ;  
        cout<<endl;
        cout<<pos<<endl;
    }
    void adjust(int *b,int m,int n){
        int j=m;
        int k=2*m;
        while(k<=n){
            if(k<n&&b[k]>b[k+1])
                k++;
            if(b[j]>b[k])
                swap(b[j],b[k]);
            j=k;
            k*=2;
        }
    }
    void make_heap(int *a,int n){
        int *b=a-1;
        for(int i=n/2;i>=1;i--)
            adjust(b,i,n);
    }

    clip_image042clip_image044clip_image046clip_image048clip_image050

    代码如下:

    void findK2(int *a,int n,int k){
        assert(k<n);
        int *b=new int[k];
        int cnt=0;
        for(int i=0;i<n;i++){
            if(cnt<k){
                b[cnt++]=a[i];
            }
            else{
                make_heap(b,k);
                for(int j=0;j<k;j++)
                    cout<<b[j]<<' ';
                cout<<endl;
                if(a[i]>b[0])
                    b[0]=a[i];
            }
        }
        for(int i=0;i<k;i++)
            cout<<b[i]<<' ';
        cout<<endl;
        delete [] b;
    }
    void test_findk2(){
        const int N=8;
        const int K=4;
        int a[N] = {5 ,2 ,66 ,23, 11 ,1 ,4 ,55} ;  
        findK2(a,N,K);
    }
    int main(){
        test_findk2();
    }

    clip_image052

    clip_image054clip_image056clip_image058

    2.6 精确表示浮点数

    clip_image060clip_image062clip_image064clip_image066

    2.7 最大公约数问题

    clip_image068clip_image070

    2.8 找出符合条件的整数

    clip_image072

    题目:任意给定一个正整数N,求一个最小的正整数M(M>1),使得N*M的十进制表示形式里只含有1和0.
    解决这个问题首先考虑对于任意的N,是否这样的M一定存在。可以证明,M是一定存在的,而且不唯一。
    简单证明:因为

    clip_image073

    这是一个无穷数列,但是数列中的每一项取值范围都在[0, N-1]之间。所以这个无穷数列中间必定存在循环节。即假设有s,t均是正整数,且s<t,有 。于是循环节长度为t-s。于是10^s = 10^t。因此有:
    clip_image074,所以
    clip_image075

    例如,取N=3,因为10的任何非负次方模3都为1,所以循环节周期为1.有:

    clip_image076

    给定N,求M的方法:
    方法一:给定N,令M从2开始,枚举M的值直到遇到一个M使得N*M的十进制表示中只有1和0.
    方法二:求出10的次方序列模N的余数序列并找出循环节。然后搜索这个余数序列,搜索的目的就是要在这个余数序列中找到一些数出来让它们的和是N的倍数。例如N=13,这个序列就是1,10,9,12,3,4然后不断循环。很明显有1+12=13,而1是10的0次方,12是10的3次方,所以这个数就是1000+1=1001,M就是1001/13=77。
    方法三:因为N*M的取值就是1,10,11,100,101,110,111,......所以直接在这个空间搜索,这是对方法一的改进。搜索这个序列直到找到一个能被N整除的数,它就是N*M,然后可计算出M。例如N=3时,搜索树如下:
    clip_image077
    上图中括号内表示模3的余数。括号外表示被搜索的数。左子树表示0,右子树表示1.上图中搜索到第二层(根是第0层)时遇到111,它模3余数为0.所以N*M=111, M=111/3=37。
    方法四:对方法三的改进。将方法三的搜索空间按模N余数分类,使得搜索时间和空间都由原来的指数级降到了O(N)。改进的原理:假设当前正在搜索由0,1组成的K位十进制数,这样的K位十进制数共有2^k个。假设其中有两个数X、Y,它们模N同余,那么在搜索由0、1组成的K+1位十进制数时,X和Y会被扩展出四个数:10X, 10X+1, 10Y, 10Y+1。因为X和Y同余(同余完全可以看作相等),所以10X与10Y同余,10X+1与10Y+1同余。也就是说由Y扩展出来的子树和由X扩展产生出来的子树产生完全相同的余数,如果X比Y小,那么Y肯定不是满足要求的最小的数,所以Y这棵子树可以被剪掉。这样,2^K个数按照模N余数分类,每类中只保留最小的那个数以供扩展。原来在这一层需要搜索2^K个数,现在只需要搜索O(N)个数。例如,当N=9时,第0层是1(1),
    clip_image078
    如上图所示,第2层的110,第三层的1010、1110都因为同一层有和它同余且更小的数而被剪掉。如果按照方法三搜索,第三层本来应该有8个结点,但现在只有4个结点。

    代码如下:

    #if 0
    bool has_only_one_and_zero(unsigned int n){
        while(n!=0){
            if(n%10>=2)
                return false;
            n/=10;
        }
        return true;
    }
    void method1(int n){
        int m;
        for(m=1;;m++){
            if(has_only_one_and_zero(n*m)){
                printf("n=%d,m=%d,n*m=%d\n",n,m,n*m);
                break;
            }
        }
    }
    void findNM(int N){
        queue<int> Q;
        Q.push(1);
        while(!Q.empty()){
            int t=Q.front();
            Q.pop();
            if(t%N==0){
                printf("n=%d,m=%d,n*m=%d\n",N,t/N,t);
                break;
            }
            Q.push(t*10);
            Q.push(t*10+1);
        }
    }
    struct QNode{
        int v,r;
        QNode(int value=0,int remain=0):v(value),r(remain){}
    };
    void findNM2(int N){
        queue<QNode> Q;
        Q.push(QNode(1,1));
        while(!Q.empty()){
            int sz=Q.size();
            vector<bool> bn(N,false);
            while(sz--){
                QNode t=Q.front();
                Q.pop();
                if(t.r==0){
                    printf("n=%d,m=%d,n*m=%d\n",N,t.v/N,t.v);
                    return ;
                }
                if(!bn[t.r*10%N]){
                    bn[t.r*10%N]=true;
                    Q.push(QNode(t.v*10,t.r*10%N));
                }
                if(!bn[(t.r*10+1)%N]){
                    bn[(t.r*10+1)%N]=true;
                    Q.push(QNode(t.v*10+1,(t.r*10+1)%N));
                }
            }
        }
    }
    void test(){
        int n;
        while(scanf("%d",&n)!=EOF){
            method1(n);
            findNM(n);
            findNM2(n);
        }
    
    }
    
    int main(){
        test();
    }
    
    #endif

    clip_image080

    参考: http://www.cnblogs.com/bvbook/archive/2009/02/06/1385448.html

    2.9 斐波那契数列问题

    递推公式如下:

    clip_image081

    clip_image083

    clip_image085clip_image087clip_image089clip_image091

    clip_image093

    2.10 寻找数组中的最大值和最小值

    试着用最小的比较次数去寻找数组中的最大值和最小值。

    clip_image095

    clip_image097

    解法三,设置两个变量Max,Min。遍历数组,每两个数据进行比较,大的再跟Max比较,小的跟Min比较,共1.5N次。似乎是最少的比较次数了。

    clip_image099

    clip_image101

    2.11 寻找最近点对

    clip_image103clip_image105clip_image107clip_image109

    clip_image111clip_image113clip_image115clip_image117clip_image119

    2.12 快速寻找满足条件的两个数

    clip_image121clip_image123clip_image125clip_image127clip_image129

    参考: http://www.cnblogs.com/justinzhang/archive/2012/04/25/2470405.html

    2.13 子数组的最大乘积

    clip_image131

    clip_image133clip_image135clip_image137

    两种方法的代码如下:

    #if 0
    int sub_array_max_multiply1(int *a,int n){
        if(a==0||n<0) 
            return -1;
        int *s=new int[n];//s[i]=s[i-1]*a[i-1];
        int *t=new int[n];//t[j]=t[j+1]*a[j+1];
        s[0]=1,t[n-1]=1;
        for(int i=1,j=n-2;i<n;i++,j--)
            s[i]=s[i-1]*a[i-1],
            t[j]=t[j+1]*a[j+1];
        int p=0x80000000;
        int index=-1;
        for(int i=0;i<n;i++)
            if(p<s[i]*t[i])//s[i]*t[i]是除a[i]之外的其他元素的乘积
                p=s[i]*t[i],
                    index=i;
        cout<<p<<endl;
        delete [] s;
        delete [] t;
        return index;
    }
    int sub_array_max_multiply2(int *a,int n){
        int zero_cnt=0,neg_cnt=0;//统计0和负数的个数
        int neg_abs_min=0x7fffffff,//记录绝对值最小负数
            neg_abs_max=0,//记录绝对值最大负数
            pos_min=0x7fffffff;//记录最小正数
        int zero_idx,neg_abs_min_idx,
            neg_abs_max_idx,
            pos_min_idx;
        for(int i=0;i<n;i++){
            if(a[i]==0) 
                zero_cnt++,zero_idx=i;
            else if(a[i]>0){
                if(a[i]<pos_min)
                    pos_min=a[i],
                        pos_min_idx=i;
    
            }
            else{
                neg_cnt++;
                int neg_abs=-a[i];
                if(neg_abs<neg_abs_min)
                    neg_abs_min=neg_abs,
                        neg_abs_min_idx=i;
                else if(neg_abs>neg_abs_max)
                    neg_abs_max=neg_abs,
                        neg_abs_max_idx=i;
            }
        }
        if(zero_cnt>1){//0的个数多于1,所以N-1个元素的结果肯定是0
            cout<<0<<endl;
            return zero_idx;
        }
        else if(zero_cnt==1){//只有一个0
            if(neg_cnt%2==0)//如果负数个数为偶数,那么去除这个0之外的N-1个元素的乘积最大
                return zero_idx;//
            else //负数个数为奇数个,那么用0替换任意一个数都可以,结果为0
                return neg_abs_min_idx;
        }
        else {//不存在0
            if(neg_cnt%2)//负数个数为奇数,去除绝对值最小的负数即可
                return neg_abs_min_idx;
            else
                if(neg_cnt==n)//不存在正数情况,去除绝对值最大的负数
                    return neg_abs_max_idx;
                else//负数个数为偶数,去除最小正数
                    return pos_min_idx;
        }
    
    }
    int main(){
        int arr[5] = {-2,-3,4,-1,6};
        cout<<sub_array_max_multiply1(arr,5)<<endl;
        cout<<sub_array_max_multiply2(arr,5)<<endl;
    
    }
    #endif

    2.14 求子数组之和的最大值

    clip_image139clip_image141clip_image143clip_image145clip_image147clip_image149clip_image151clip_image153

    2.15 子数组之和的最大值(二维)

    clip_image155

    clip_image157 clip_image159

    所以, 解法1就是遍历所有大小的矩形, 根据得到的部分和PS可以很容易计算矩形的内数字的和. 复杂度为O(M^2*N^2)

    解法2是转化为一维的情况.

    我们发现一维的解答可以线性完成,这里我们把问题从二维转化为一维以提高算法性能。

    假设已经确定了矩阵区域的上下边界,不如知道矩阵区域的上下边界分布是第a行和第c行,接下来要确定左右边界。

    我们把第a行和第c行之间的每一列看成一个整体,相当于一维数组中的一个元素(通过子矩阵部分和可以在O(1)时间内计算出整体之和)。

    代码如下:

    #if 0
    /*
     * 2.15
     */
     //根据部分和矩阵PS计算出行范围[i1,i2],列范围[j1,j2]内的和
    int matrix_sum(int **PS,int i1,int i2,int j1,int j2){
        return PS[i2][j2]-PS[i1-1][j2]-PS[i2][j1-1]+PS[i1-1][j1-1];
    }
    //打印出二维矩阵
    void TMP(int **M,int row,int col){
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++)
                cout<<setw(3)<<M[i][j];
            cout<<endl;
        }
    }
    //方法1:遍历所有的矩形进行求和
    void matrix_max_sum(int **M,int row,int col){
        int *Mem=new int[(row+1)*(col+1)];
        int **PS=new int*[row+1];
        for(int i=0;i<=row;i++)
            PS[i]=Mem+(col+1)*i;
        for(int i=0;i<=row;i++)//初始化第0列
            PS[i][0]=0;
        for(int j=0;j<=col;j++)//初始化第0行
            PS[0][j]=0;
        for(int i=1;i<=row;i++)
            for(int j=1;j<=col;j++)
                PS[i][j]=M[i][j]+PS[i-1][j]+PS[i][j-1]-PS[i-1][j-1];//计算部分和
        TMP(PS,row+1,col+1);
        int c1=-1,c2=-1,r1=-1,r2=-1;
        int max_sum=0x80000000;
        for(int i1=1;i1<=row;i1++)
            for(int i2=i1;i2<=row;i2++)
                for(int j1=1;j1<=col;j1++)
                    for(int j2=j1;j2<=col;j2++) {
                        int tmp=matrix_sum(PS,i1,i2,j1,j2);//计算[i1,i2][j1,j2]范围内矩形,并判断是否为最大值
                        if(tmp>max_sum){
                            max_sum=tmp;
                            r1=i1,r2=i2,c1=j1,c2=j2;
                        }
                    }
    
        cout<<'['<<r1<<' '<<r2<<' '
                 <<c1<<' '<<c2<<"]="<<max_sum<<endl;
    
        delete[] Mem;
        delete[] PS;
    
    }
    //方法2:转化为一维的情况
    void matrix_max_sum2(int **M,int row,int col){
        int *Mem=new int[(row+1)*(col+1)];
        int **PS=new int*[row+1];
        for(int i=0;i<=row;i++)
            PS[i]=Mem+(col+1)*i;
        for(int i=0;i<=row;i++)
            PS[i][0]=0;
        for(int j=0;j<=col;j++)
            PS[0][j]=0;
        for(int i=1;i<=row;i++)
            for(int j=1;j<=col;j++)
                PS[i][j]=M[i][j]+PS[i-1][j]+PS[i][j-1]-PS[i-1][j-1];
        TMP(PS,row+1,col+1);
        int c1=-1,c2=-1,r1=-1,r2=-1;
        int max_sum=0x80000000;
        
        for(int a=1;a<=row;a++)
            for(int c=a;c<=row;c++){
             // 将子矩阵上下边界设为第a行和第c行,在这些子矩阵中取最大值 
                int tail=matrix_sum(PS,a,c,1,1);
    //            cout<<"tail="<<tail<<' ';
                int c1_t=1;//记录最大和的起始位置
                for(int j=2;j<=col;j++){
                    int tmp=matrix_sum(PS,a,c,j,j);
                    if(tail<0)
                        tail=tmp,c1_t=j;
                    else
                        tail+=tmp;
    //                cout<<tail<<"<c1="<<c1<<"> ";
                    if(tail>max_sum) 
                        max_sum=tail,r1=a,r2=c,c1=c1_t,c2=j;
                }
    //            cout<<endl;
            }
        cout<<'['<<r1<<' '<<r2<<' '
                 <<c1<<' '<<c2<<"]="<<max_sum<<endl;
    
        delete[] Mem;
        delete[] PS;
    
    }
    int main(){
        const int row=4,col=4;
        int B[][col]={0,-2,-7,0,
            9,2,-6,2,
            -4,1,-4,1,
            -1,8,0,-2 };
        int *tmp[]={0,(int*)(*B-1),(int*)(*(B+1)-1),(int*)(*(B+2)-1),(int*)(*(B+3)-1)};
    //    TMP(tmp,row,col);
    
        matrix_max_sum(tmp,row,col);
        matrix_max_sum2(tmp,row,col);
    
    }
    
    #endif

    clip_image161

    对于问题1:

    代码如下:

    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define MAXN 1003
    int A[MAXN][MAXN];
    long long PS[MAXN][MAXN];
    inline long long MatrixSum(int s, int t, int i, int j)
    {
        return PS[i][j]-PS[i][t-1]-PS[s-1][j]+PS[s-1][t-1];
    }
    int main()
    {
        int m, n, i, j;
        cin >> n >> m;
        for (i=1; i<=n; i++)
            for (j=1; j<=m; j++)
                cin >> A[i][j];
        for (i=0; i<=n; i++)
            PS[i][0] = 0;
        for (j=0; j<=m; j++)
            PS[0][j] = 0;
        // 计算矩阵的部分和
        for (i=1; i<=n; i++)
            for (j=1; j<=m; j++)
                PS[i][j] = A[i][j]+PS[i-1][j]+PS[i][j-1]-PS[i-1][j-1];
        int a, c;
        long long All = A[1][1];
        // 上下边界不会跨过第n行和第1行
        for (a=1; a<=n; a++)
            for (c=a; c<=n; c++)
            {
                // 将子矩阵上下边界设为第a行和第c行
                // 左右边界不会跨过第m列和第1列
                long long Tail = MatrixSum(a, 1, c, 1);
                for (j=2; j<=m; j++)
                {
                    Tail = max(MatrixSum(a, j, c, j),
                            MatrixSum(a, j, c, j)+Tail);
                    All = max(Tail, All);
                }
                // 左右边界会跨过第n列和第1列
                long long Sum = MatrixSum(a, 1, c, 1);
                long long Start = Sum;
                int sind = 1;
                for (i=2; i<=m; i++)
                {
                    Sum += MatrixSum(a, i, c, i);
                    if (Sum > Start) {Start = Sum; sind = i;}
                }
                Tail = MatrixSum(a, m, c, m);
                int tind = m;
                for (j=m-1; j>=1; j--)
                {
                    Sum += MatrixSum(a, j, c, j);
                    if (Sum > Tail) {Tail = Sum; tind = j;}
                }
                if (sind<tind && Start+Tail>All)
                    All = Start+Tail;
            }
        cout << All;
    }

    对于问题3:

    思路和二维一样,但时间复杂度增加到O(n^5)。通过将高维转化为低维的方法,每增加一维时间复杂度要增加O(n^2)。

    方体的部分和计算公式如下:PS[i][j][k] = A[i][j][k]+PS[i-1][j][k]+PS[i][j-1][k]+PS[i][j][k-1]-PS[i-1][j-1][k]-PS[i-1][j][k-1]-PS[i][j-1][k-1]+PS[i-1][j-1][k-1];

    代码如下:

    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define MAXN 1003
    int A[MAXN][MAXN][MAXN];
    int PS[MAXN][MAXN][MAXN];
    inline int CubeSum(int a, int b, int c, int d, int i, int j)
    {
        return PS[b][d][j]-PS[a-1][d][j]-PS[b][c-1][j]-PS[b][d][i-1]+
            PS[a-1][c-1][j]+PS[a-1][d][i-1]+PS[b][c-1][i-1]-PS[a-1][c-1][i-1];
    }
    int main()
    {
        int n, m, h, i, j, k;
        cin >> n >> m >> h;
        for (i=1; i<=n; i++)
            for (j=1; j<=m; j++)
                for (k=1; k<=h; k++)
                    cin >> A[i][j][k];
        for (i=0; i<=n; i++)
            for (j=0; j<=m; j++)
                PS[i][j][0] = 0;
        for (i=0; i<=n; i++)
            for (k=0; k<=h; k++)
                PS[i][0][k] = 0;
        for (j=0; j<=m ; j++)
            for (k=0; k<=h; k++)
                PS[0][j][k] = 0;
        // 计算长方体的部分和
        for (i=1; i<=n; i++)
            for (j=1; j<=m; j++)
                for (k=1; k<=h; k++)
                    PS[i][j][k] = A[i][j][k]+PS[i-1][j][k]+PS[i][j-1][k]+PS[i][j][k-1]-
                        PS[i-1][j-1][k]-PS[i-1][j][k-1]-PS[i][j-1][k-1]+PS[i-1][j-1][k-1];
        int a, b, c, d;
        int All = A[1][1][1];
        // 限制第一维的取值范围
        for (a=1; a<=n; a++)
            for (b=a; b<=n; b++)
                // 限制第二维的取值范围
                for (c=1; c<=m; c++)
                    for (d=c; d<=m; d++)
                    {
                        // 只剩下最后一维没有确定,利用一维部分和的方法
                        int Tail = CubeSum(a,b,c,d,1,1);
                        for (j=2; j<=k; j++)
                        {
                            int cur = CubeSum(a,b,c,d,j,j);
                            Tail = max(Tail+cur, cur);
                            All = max(Tail, All);
                        }
                    }
        cout << All;
    }

    参考: http://blog.csdn.net/linyunzju/article/details/7723730

    2.16 数组中的最长递增子序列

    clip_image163

    解法1:

    clip_image165 clip_image167

    2.17 数组循环移位

    2.18 数组分割

    clip_image169

    clip_image171 clip_image173

    clip_image175clip_image177 clip_image179 clip_image181

    问题:

    1. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为两个子数组,子数组的元素个数不限,并使两个子数组之和最接近。

    2. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组之和最接近。

    1. 解法1:

    由于对两个子数组和最接近的判断不太直观,我们需要对题目进行适当转化。我们知道当一个子数组之和最接近原数组之和sum的一半时,两个子数组之和是最接近的。所以转化后的题目是:从2n个数中选出任意个数,其和尽量接近于给定值sum/2。

    这个问题存储的是从前k个数中选取任意个数,且其和为s的取法是否存在dp[k][s]。之所以将选出的数之和放在下标中,而不是作为dp[k]的值,是因为那种做法不满足动态规划的前提——最优化原理,假设我们找到最优解有k个数p1p2...pk(选出的这k个数之和是最接近sum/2的),但最优解的前k-1个数p1p2...pk-1之和可能并不是最接近sum/2的,也就是说可能在访问到pk之前有另一组数q1q2....qk-1其和相比p1p2...pk-1之和会更接近sum/2,即最优解的子问题并不是最优的,所以不满足最优化原理。因此我们需要将dp[k]的值作为下标存储起来,将这个最优问题转化为判定问题,用带动态规划的思想的递推法来解。

    外阶段:在前k1个数中进行选择,k1=1,2...2*n。

    内阶段:从这k1个数中任意选出k2个数,k2=1,2...k1。

    状态:这k2个数的和为s,s=1,2...sum/2。

    决策:决定这k2个数的和有两种决策,一个是这k2个数中包含第k1个数,另一个是不包含第k1个数。

    dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。

    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define MAXN 101
    #define MAXSUM 100000
    int A[MAXN];
    bool dp[MAXN][MAXSUM];
    // dp[k][s]表示从前k个数中去任意个数,且这些数之和为s的取法是否存在
    int main()
    {
        int n, i, k1, k2, s, u;
        cin >> n;
        for (i=1; i<=2*n; i++)
            cin >> A[i];
        int sum = 0;
        for (i=1; i<=2*n; i++)
            sum += A[i];
        memset(dp,0,sizeof(dp));
        dp[0][0]=true;
        // 外阶段k1表示第k1个数,内阶段k2表示选取数的个数
        for (k1=1; k1<=2*n; k1++) // 外阶段k1
        {
            for (k2=k1; k2>=1; k2--) // 内阶段k2
                for (s=1; s<=sum/2; s++) // 状态s
                {
                    //dp[k1][s] = dp[k1-1][s];
                    // 有两个决策包含或不包含元素k1
                    if (s>=A[k1] && dp[k2-1][s-A[k1]])
                        dp[k2][s] = true;
                }
        }
        // 之前的dp[k][s]表示从前k个数中取任意k个数,经过下面的步骤后
        // 即表示从前k个数中取任意个数
        for (k1=2; k1<=2*n; k1++)
            for (s=1; s<=sum/2; s++)
                if (dp[k1-1][s]) dp[k1][s]=true;
        // 确定最接近的给定值sum/2的和
        for (s=sum/2; s>=1 && !dp[2*n][s]; s--);
        printf("the differece between two sub array is %d\n", sum-2*s);
    }

    解法2:

    由于题目不限制子数组的元素个数,限制条件少,可以进行优化。实际上解法1的思路主要是为了题目2做铺垫,使得题目2的解法不至于太难理解。该题实际上有更简单的解法,该解法的思路和0-1背包问题的思路是一样的。

    #include <iostream>
    using namespace std;
    #define MAXN 101
    #define MAXSUM 100000
    int A[MAXN];
    bool dp[MAXN][MAXSUM];
    // dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在
    int main()
    {
        int k, s, u, i, n;
        cin >> n;
        for (i=1; i<=2*n; ++i)
            cin >> A[i];
        int sum = 0;
        for (i=1; i<=2*n; ++i)
            sum += A[i];
        dp[0][0] = true;
        // 阶段k表示第k个数
        for (k=1; k<=2*n; ++k)
            // 注意状态可取0
            for (s=0; s<=(sum>>1); ++s)
            {
                // 加上第k个数,或不加它所能得到的和
                if (s>=A[k])
                    dp[k][s] = dp[k-1][s-A[k]] || dp[k-1][s];
                else
                    dp[k][s] = dp[k-1][s];
            }
        for (s=(sum>>1); s>=1 && !dp[2*n][s]; --s);
        cout << sum-2*s;
    }

    2. 解法:

    但本题还增加了一个限制条件,即选出的物体数必须为n,这个条件限制了内阶段k2的取值范围,并且dp[k][s]的含义也发生变化。这里的dp[k][s]表示从前k个数中取k个数,且k不超过n,且这些数之和为s的取法是否存在。

    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define MAXN 101
    #define MAXSUM 100000
    int A[MAXN];
    bool dp[MAXN][MAXSUM];
    // 题目可转换为从2n个数中选出n个数,其和尽量接近于给定值sum/2
    int main()
    {
        int n, i, k1, k2, s, u;
        cin >> n;
        for (i=1; i<=2*n; i++)
            cin >> A[i];
        int sum = 0;
        for (i=1; i<=2*n; i++)
            sum += A[i];
        memset(dp,0,sizeof(dp));
        dp[0][0]=true;
        // 对于dp[k][s]要进行u次决策,由于阶段k的选择受到决策的限制,
        // 这里决策选择不允许重复,但阶段可以重复,比较特别
        for (k1=1; k1<=2*n; k1++) // 外阶段k1
            for (k2=min(k1,n); k2>=1; k2--) // 内阶段k2
                for (s=1; s<=sum/2; s++) // 状态s
                    // 有两个决策包含或不包含元素k1
                    if (s>=A[k1] && dp[k2-1][s-A[k1]])
                        dp[k2][s] = true;
        // 确定最接近的给定值sum/2的和
        for (s=sum/2; s>=1 && !dp[n][s]; s--);
        printf("the differece between two sub array is %d\n", sum-2*s);
    }

    参考: http://blog.csdn.net/linyunzju/article/details/7729774

    2.19 区间重合判断

    clip_image183

    clip_image185 clip_image187 clip_image189 clip_image191

    问题:

    1. 给定一个源区间[x,y]和N个无序的目标区间[x1,y1] [x2,y2] ... [xn,yn],判断源区间[x,y]是不是在目标区间内。

    2. 给定一个窗口区域和系统界面上的N个窗口,判断这个窗口区域是否被已有的窗口覆盖。

    1. 解法:

    先用区间的左边界值对目标区间进行排序O(nlogn),对排好序的区间进行合并O(n),对每次待查找的源区间,用二分查出其左右两边界点分别处于合并后的哪个源区间中O(logn),若属于同一个源区间则说明其在目标区间中,否则就说明不在。

    #include <algorithm>
    using namespace std;
    struct Line
    {
        int low, high;
        bool operator<(const Line &l) const
        {return low<l.low;}
    };
    #define MAXN 10001
    Line lines[MAXN]; // 目标区间
    int ncnt = 0; // 合并后区间的个数
    #define N 101
    Line sl[N]; // 待查询的源区间
    // 用二分查找找出key所在的区间,以区间的low作为划分
    int GetIndex(int key)
    {
        int u, v;
        u = 0; v = ncnt-1;
        while (u<=v) // u,v可取等号
        {
            int m = (u+v)>>1;
            if (key >= lines[m].low)
                u = m+1;
            else
                v = m-1;
        }
        return v;
    }
    int main()
    {
        int n, k, i, j;
        cin >> n >> k; // n是目标区间的个数,k是待查询的源区间的个数
        for (i=0; i<n; i++)
            cin >> lines[i].low >> lines[i].high;
        for (i=0; i<k; i++)
            cin >> sl[i].low >> sl[i].high;
        // 排序O(nlogn)
        sort(lines, lines+n);
        // 合并O(n)
        int lasthigh = lines[0].high;
        for (i=1; i<n; i++)
            if (lasthigh >= lines[i].low)
                lasthigh = lines[i].high;
            else
            {
                lines[ncnt++].high = lasthigh;
                lines[ncnt].low = lines[i].low;
                lasthigh = lines[i].high;
            }
        lines[ncnt++].high = lasthigh;
        for (i=0; i<k; i++)
        {
            // 单词查找时间O(logn)
            int s1 = GetIndex(sl[i].low);
            int s2 = GetIndex(sl[i].high);
            if (s1==s2 && sl[i].high <= lines[s2].high)
                printf("Yes\n");
            else
                printf("No\n");
        }
    }

    2. 解法:

    这个问题适合使用线段树来解答,单次查找的时间复杂度为O(nlogn),当然也能用数组解答,但单次查找的时间复杂度会增加到O(n^2)。这里我们直接使用线段树来解答。

    线段树是一棵二叉树,将数轴划分成一系列的初等区间[I, I+1] (I=1,2,..,N-1)。每个初等区间对应于线段树的一个叶结点。线段树的内部结点对应于形如[ I, J ](J – I > 1)的一般区间。由于线段树给每一个区间都分配了结点,利用线段树可以求区间并后的总长度与区间并后的线段数。先给出测试数据(前4行是系统界面上已有的N个窗口,之后的一行是待测试的窗口区域),后面是代码:

    4

    -15 0 5 10

    -5 8 20 25

    15 -4 24 14

    0 -6 16 4

    2 15 10 22

    #include <iostream>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    // 线段树的结点
    struct SegNode
    {
        int low, high; // 线段的两端点索引
        int ncover; // 线段被覆盖的次数
        SegNode *left; // 结点的左子树
        SegNode *right; // 结点的右子树
        SegNode() {low=high=0;ncover=0;
            left=right=NULL;}
    };
    // 构造线段树,它是一个完全二叉树
    void BuildSegTree(SegNode *&tree, int *index, int low, int high)
    {
        if (low < high)
        {
            tree = new SegNode;
            tree->low = low;
            tree->high = high;
            if (high-low>1)
            {
                int m = (low+high)/2;
                BuildSegTree(tree->left, index, low, m);
                BuildSegTree(tree->right, index, m, high);
            }
        }
    }
    // 往线段树中插入线段,即用线段(low,high)来覆盖线段树
    void InsertSegTree(SegNode *tree, int low, int high)
    {
        // 先序遍历
        if (low<=tree->low && tree->high<=high)
            tree->ncover++;
        else if (tree->high-tree->low > 1)
        {
            int m = (tree->low+tree->high)/2;
            if (low < m) InsertSegTree(tree->left, low, high);
            if (m < high) InsertSegTree(tree->right, low, high);
        }
    }
    // 从线段树中删除线段
    void DeleteSegTree(SegNode *tree, int low, int high)
    {
        if (low<=tree->low && tree->high<=high)
            tree->ncover--;
        else if (tree->high-tree->low > 1)
        {
            int m = (tree->low+tree->high)/2;
            if (low < m) DeleteSegTree(tree->left, low, high);
            if (m < high) DeleteSegTree(tree->right, low, high);
        }
    }
    // 线段树中是否包含线段(low,high)
    bool FindSegTree(SegNode *tree, int low, int high)
    {
        // 若当前区间被覆盖,且线段(low,high)属于当前区间则返回覆盖
        if (tree->ncover && tree->low <= low && high <= tree->high )
            return true;
        // 若(low,high)没被当前区间覆盖,则将其分为两段,
        // 分别考虑是否被子结点表示的区间覆盖
        else if (tree->high - tree->low > 1)
        {
            int m = (tree->low + tree->high) >> 1;
            bool ret = true;
            if (low<m) ret = FindSegTree(tree->left, low, high<m?high:m);
            if (!ret) return false;
            if (m<high) ret = FindSegTree(tree->right, m<low?low:m, high);
            if (!ret) return false;
            return true;
        }
        return false;
    }
    #define LEFT true
    #define RIGHT false
    #define INF 10000
    // 表示竖直方向的线段
    struct Line
    {
        int starty, endy; // 竖线的长度
        int x; // 竖线的位置
        bool inout; // 竖线是长方形的左边还是右边
        bool operator<(const Line& a) const{ // 依据x坐标进行排序
            return x<a.x;
        }
    };
    // 所有竖直方向的线段
    Line lines[INF];
    // 对横向超元线段进行分组
    int index[INF];
    int nCnt = 0;
    // 获取key的位置
    int GetIndex(int key)
    {
        // 用二分查找查出key在index中的位置
        return lower_bound(index,index+nCnt,key)-index; 
    }
    // 获取key的位置或比它小的最大数的位置
    int GetLower(int key)
    {
        size_t pos = lower_bound(index,index+nCnt,key)-index;
        if (key == index[pos]) return pos;
        else return pos-1;
    }
    // 获取key的位置或比它大的最小数的位置
    int GetUpper(int key)
    {
        return lower_bound(index,index+nCnt,key)-index;
    }
    int main()
    {
        int nRec;
        cin >> nRec;
        int i, j;
        int x[2], y[2];
        // 读取nRec个窗口的数据
        for (i=0; i<nRec; i++)
        {
            cin >> x[0] >> y[0] >> x[1] >> y[1];
            // 记录每个长方形的两条竖直边
            lines[2*i].x=x[0]; lines[2*i+1].x=x[1];
            lines[2*i].starty=lines[2*i+1].starty=min(y[0],y[1]);
            lines[2*i].endy=lines[2*i+1].endy=max(y[0],y[1]);
            lines[2*i].inout=LEFT; lines[2*i+1].inout=RIGHT;
            // 对竖直的线段进行离散化
            index[2*i]=y[0]; index[2*i+1]=y[1];
        }
        // 待查询的窗口区域
        Line search[2];
        cin >> x[0] >> y[0] >> x[1] >> y[1];
        search[0].x=x[0]; search[1].x=x[1];
        search[0].starty=search[1].starty=min(y[0],y[1]);
        search[0].endy=search[1].endy=max(y[0],y[1]);
        search[0].inout=LEFT; search[1].inout=RIGHT;
        // 对x坐标进行排序O(nlogn)
        sort(index, index+2*nRec);
        sort(lines, lines+2*nRec);
        // 排除index数组中的重复数据O(n)
        for (i=1; i<2*nRec; i++)
            if (index[i]!=index[i-1])
                index[nCnt++] = index[i-1];
        index[nCnt++] = index[2*nRec-1];
        // 建立线段树
        SegNode *tree;
        BuildSegTree(tree, index, 0, nCnt-1);
        // 单词查找的时间复杂度为O(nlogn)
        bool res;
        InsertSegTree(tree, GetIndex(lines[0].starty), GetIndex(lines[0].endy));
        for (i=1; i<2*nRec; i++)
        {
            if (lines[i].inout==LEFT) // 遇窗口的左边界,将其加入线段树
                InsertSegTree(tree, GetIndex(lines[i].starty), GetIndex(lines[i].endy));
            else // 遇窗口的右边界,将其删出线段树
                DeleteSegTree(tree, GetIndex(lines[i].starty), GetIndex(lines[i].endy));
            if (lines[i].x!=lines[i-1].x && search[0].x < lines[i+1].x && search[1].x > lines[i].x)
            {
                // 从待查窗口区域的左边界开始查询直到其右边界结束查询
                res = FindSegTree(tree, GetLower(search[0].starty), GetUpper(search[0].endy));
                if (!res) break;
            }else if (search[1].x <= lines[i].x)
                break;
        }
        if (res) printf("Yes\n");
        else printf("No\n");
        return 0;
    }

    参考: http://blog.csdn.net/linyunzju/article/details/7737060

    2.21 只考加法的面试题

    clip_image193

  • 相关阅读:
    配置动态刷新RefreshScope注解使用局限性(一)
    OAuth2 Token 一定要放在请求头中吗?
    Spring Boot 2.3 新特配置文件属性跟踪
    为什么 Spring Boot 2.3.0 放弃Maven最终拥抱Gradle
    Spring Boot 2.3.0 新特性Redis 拓扑动态感应
    【spring cloud hoxton】Ribbon 真的能被 spring-cloud-loadbalancer 替代吗
    Spring Cloud Gateway 扩展支持动态限流
    聊聊 OAuth 2.0 的 token expire_in 使用
    「starter推荐」简单高效Excel 导出工具
    用mint-ui tabber写餐厅分层
  • 原文地址:https://www.cnblogs.com/xkfz007/p/2758317.html
Copyright © 2011-2022 走看看