• [读书笔记]编程之美


    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之间
  • 相关阅读:
    前端开发 —— 快速入门:JavaScript & CSS 脚手架
    MySQL全方位练习(学生表 教师表 课程表 分数表)
    php判断是爬虫在访问还是用户浏览器在访问
    Eclipse 安装Activiti插件
    随机生成一串字符串(java)
    js文字转语音(speechSynthesis)
    语音识别(Web Speech API)
    js判断是否为手机端访问
    tomcat部署安全证书文件(阿里云SSL证书)
    springboot文字转语音(jacob)
  • 原文地址:https://www.cnblogs.com/iyjhabc/p/2987478.html
Copyright © 2020-2023  润新知