[读书笔记]编程之美

2.14 动态规划 dynamic programming 之 求数组最大子数组之和
如要求 int A[]={a0,a1,a2,a3,a4}; 的最大子串之和,使用遍历所有子串的情况,复杂度达到o(n3)。而使用动态规划则可达到o(n)。
动态规划的思想是,把问题拆分为一个大的子问题和一个小的子问题,求解大的子问题需要用小子问题的结果。因此只需计算小子问题,就可以递推大问题,直到解决整个问题。
 
新浪面试题:10个阶梯,一次可以走1步或者2步,问有多少中走法?
答:考虑f(n)与f(n-1)的关系。如果最后一步是独立走一步完成的,则有f(n-1)*1种走法;如果最后一步是使用2步的话,则有f(n-2)种走法,先走到f(n-2)再使用2步走(剩下两步但不可以用两个1步,因为走一步的情况前面已经考虑了)。
因此f(n)=f(n-1)+f(n-2),就是裴波那契数列,用DP方法可以以O(n)复杂度算出。
这个算法还有一个好处就是,不必把所有数据放入内存,只需存放K个数据就可以。
 
把a0和其他元素分为两组。则最大子串的情况有三种。1、a0是一个最大子串;2、最大子串从a1开始;3、最大子串从a1之后的元素开始。
设start[i]为从第i个元素开始的最大子串之和;all[i]为从第i个元素开始的最大子串之和(未必包括i元素)。
则可知all[i] = max ( A[i] , start[i+1] , all[i+1]) 对应上面三种情况。 因此从数组最后一个元素可以一直往前推,知道求出all[0]。
 
int max(int a,int b)
{
    return (a>b) ? a : b;
}

int main()
{
    int A[]={1,-2,3,5,-3,2};
    int n=sizeof(A)/sizeof(int);  //数组长度
    int start[n]; //包含下标元素往右的最大连续子数组之和
    int all[n];   //从下标到最右区间之中  存在的最大子数组之和

    start[n-1]=A[n-1]; //从数组最后元素开始迭代,这两个初值是显而易见的。
    all[n-1]=A[n-1];

    for(int i=n-2;i>=0;--i)
    {
        start[i]=max(A[i],A[i]+start[i+1]);//理解:x1,x2,x3,x4,比如start[2]=x2+x3,证明从2开始的所有组合中,x2+x3是最大的,x2或x2+x3+x4
        all[i]=max(start[i],all[i+1]);         //都不是最大。再考虑x1.从x1开始的最大值,要不就start[2]+x1,要不就x1一个
    }                                                   //因为如果这两种情况以外的start[1]=x1+x2+x3+x4<x1+x2+x3,因为x2+x3=start[2]>x2+x3+x4
    cout<<all[0]<<endl;
        return 0;
}
 
注意到start[i]是从start[i+1]迭代得来,all[i]又只是用了start[i]而没是用start[i+1],因此可直接用变量代替数组,节省内存。
int start=A[n-1],all=A[n-1];

    for(int i=n-2;i>=0;--i)
    {
        start=max(A[i],A[i]+start);
        all=max(start,all);
    }
 
如果要求最大子串的位置呢?
需要把start和all的起止位置记录下来,用max对比时位置起止位置会发生改变,因此把max分为maxStart和maxAll。
 
int maxStart(int a,int b,int i,int &begin, int &end)
{
    if(a>b)   //A[i]>A[i]+start[i+1]时,start的起止位置改为i
    {
        begin=i;
        end=i;
        return a;
    }
    else      //否则终点位置不改变,因为start的定义,起点位置必须变
    {
        begin=i;
        return b;
    }
    //return (a>b) ? a : b;
}
int maxAll(int a,int b,int i,int &begin, int &end,int startBegin,int startEnd)
{
    if(a>b) //如果all[i]是从i开始,则all的起止位置为start[i]的起止位置
    {
        begin=startBegin;
        end=startEnd;
        return a;
    }
    else   //否则all的起止位置跟上次一样,不做改变
    {
        return b;
    }
    //return (a>b) ? a : b;
}


int main()
{
    int A[]={-9,-2,-3,-5,-3};
    int n=sizeof(A)/sizeof(int);  //数组长度

    int start=A[n-1],all=A[n-1];
    int startBegin=n-1,startEnd=n-1,allBegin=n-1,allEnd=n-1;//start和all的起止下标。

    for(int i=n-2;i>=0;--i)
    {
        start=maxStart(A[i],A[i]+start,i,startBegin,startEnd);
        all=maxAll(start,all,i,allBegin,allEnd,startBegin,startEnd);
    }
    cout<<all<<endl;
    for(int i=allBegin;i<=allEnd;++i)
    {
        cout<<A[i]<<" ";
    }cout<<endl;


        return 0;
}
查找网页正文问题
有一长度为n的数组 A,数组中所有元素已经确定,都为0或1求a,b使 ∑A[ i ] (from i = 0 to a-1) + ∑k*(1-A[ i ]) (from i = a to b) +  ∑A[ i ] (from i = b+1 to n-1) 的值最大
k是一个已经确定的常数。这是一个动态规划的问题,求大神给个动态规划的递推公式。谢谢!!
使用动态规划的算法复杂度应为o(n)
 
int main()
{
    int A[] = {0, 1, 0, 1, 0, 1, 0, 1, 1, 0};
    double t= 5.0, nt = 5.0;
double c = t / nt;

    int n=sizeof(A)/sizeof(int);  //数组长度
    double start[n]; //包含下标元素往右的最大连续子数组之和
    double all[n];   //从下标到最右区间之中  存在的最大子数组之和

    start[n-1]=t-A[n-1]+c*(1-A[n-1]); //从数组最后元素开始迭代,这两个初值是显而易见的。
    all[n-1]=t-A[n-1]+c*(1-A[n-1]);

    for(int i=n-2;i>=0;--i)
    {
        start[i]=max(t-A[i]+c*(1-A[i]),start[i+1]+c*(1-A[i])-A[i]);
        all[i]=max(start[i],all[i+1]);
    }
    cout<<all[0]<<endl;
        return 0;
}

//注意还要把maxAll maxStart的参数和返回值修改为DOUBLE
int main()
{
    int A[] = {0, 1, 0, 0, 0, 0, 0, 1, 1, 0};
    double t= 3.0, nt = 7.0;
    double c = t / nt;
    int n=sizeof(A)/sizeof(int);  //数组长度

    double start=t-A[n-1]+c*(1-A[n-1]);
    double all=t-A[n-1]+c*(1-A[n-1]);
    int startBegin=n-1,startEnd=n-1,allBegin=n-1,allEnd=n-1;//start和all的起止下标。

    for(int i=n-2;i>=0;--i)
    {
        start=maxStart(t-A[i]+c*(1-A[i]),start+c*(1-A[i])-A[i],i,startBegin,startEnd);
        all=maxAll(start,all,i,allBegin,allEnd,startBegin,startEnd);
    }
    cout<<all<<endl;
    for(int i=allBegin;i<=allEnd;++i)
    {
        cout<<A[i]<<" ";
    }cout<<endl;


        return 0;
}
2.5寻找最大的K个数
(1)新建一个长度为K的数组A,不断地把原数组的元素往里加。位置不够时,把A中最小的替换为新元素。(1、适用于数组长度非常巨大,不便多次遍历,此法只需遍历一次。2、可用堆存放K,用堆排序优化算法效率)
(2)找出第K大的数,然后自然可以找出前K大的数。P142
     if(数组中大于mid的元素个数>=K) min=mid; else max=mid; //证明前K大的数在min到mid之间
原文地址:https://www.cnblogs.com/iyjhabc/p/2987478.html