• 动态规划-最长连续子序列和与最大子矩阵


    问题:给一列数n个,求最大连续子序列和(即连续的子序列中和最大的序列)若所有K个元素都是负数,则定义其最大和为0,输出整个序列的首尾元素 

    拓展:给一个n*n的矩阵,求其中和最大的子矩阵(即所有子矩阵中和最大的阵)

    首先也是从最简单的着手,拿到问题,很容易想到的就是直接爆搜(求所有可能的子序列和并找出最大的即可)时间复杂度为n^2

    1. #include <stdio.h>  
    2. #include <string.h>  
    3. #include <limits.h>  
    4.   
    5. #define N 10002  
    6. /* 
    7. problem:求最大连续子序列的问题dp 
    8. time:n^2 
    9. */  
    10.   
    11. int main(){  
    12.     int a[N],i,j,maxx,sum,n,ps,pe;  
    13.   
    14.     scanf("%d",&n);  
    15.     for(i=0;i<n;++i){  
    16.         scanf("%d",&a[i]);  
    17.     }  
    18.   
    19.     maxx = INT_MIN;//初始为最小整数  
    20.     for(i=0;i<n;++i){  
    21.         sum = 0;  
    22.         for(j=i;j<n;++j){//i,j两个子循环来遍历所有的子序列并计算其和(a[i]加到a[j])  
    23.             sum += a[j];  
    24.             if(sum>maxx){//把和大的保留并记录下最大序列的始末位置ps、pe  
    25.                 maxx = sum;  
    26.                 ps = i;  
    27.                 pe = j;  
    28.             }  
    29.         }  
    30.     }  
    31.     printf("%d %d %d ",maxx,a[ps],a[pe]);  
    32. }  


    上面的这段代码思路非常的清晰也便于理解,但是数据量大的话时间消耗也会很大,当然ACM的要求肯定是达不到了,现在做时间复杂度为n的算法来解决该问题。

    算法基于的思想也很简单,最大连续子序列和的第一个元素不可能是负数,这点很好证明(反证),假设a[i…j]为最大的连续子序列且a[i]为负,那我a[i+1…j]的和将会大于a[i…j]的和,所以与原假设矛盾,这就能推出最大子序列和的第一个元素不可能是负数。得到这个结论我们就可以进一步进行推广,那就是如果一个子序列的和为负数,那么这个序列不可能是最大连续子序列中的开始的一段序列(类似于第一个元素的方法可得到证明即把这段和看做是一个元素)。根据这一思想就可以得到本体线性的算法。

    1. #include <stdio.h>  
    2. #include <string.h>  
    3. #include <limits.h>  
    4.   
    5. #define N 10002  
    6.   
    7. int main(){  
    8.     int a[N],maxx,i,n,sum,ps,pe,ts,te;  
    9.   
    10.     scanf("%d",&n);  
    11.     for(i=0;i<n;++i){  
    12.         scanf("%d",&a[i]);  
    13.     }  
    14.   
    15.     sum = 0;  
    16.     maxx = INT_MIN;  
    17.   
    18.     for(i=0;i<n;++i){  
    19.         if(sum<=0){//如果前面的和为负,则前面的序列舍掉从本元素开始重新确定新序列  
    20.             sum = a[i];  
    21.             ts = i;  
    22.             te = i;  
    23.         }else{//如果前面的和为正,则可能出现在最大序列中,所以要继续累加  
    24.             sum += a[i];  
    25.             te = i;  
    26.         }  
    27.   
    28.         if(sum>maxx){//记录下最大子序列和及起始和结束位置  
    29.             maxx = sum;  
    30.             ps = ts;  
    31.             pe = te;  
    32.         }  
    33.     }  
    34.   
    35.     printf("%d %d %d ",maxx,a[ps],a[pe]);  
    36.     return 0;  
    37. }  

    至此,最大连续子序列问题得到解决。

    下面记录下最大子矩阵和的求法。

    其实最大子矩阵和的算法就是最大连续子序列的一个拓展问题,思路很简单,就是将矩阵先预处理下,按列累加,然后通过三个循环变量来遍历所有的子矩阵,对每个子矩阵以列和为元素转化为n个元素序列求最大连续子序列的问题就可以了。

    题目是这样的:http://ac.jobdu.com/problem.php?pid=1139

    已知矩阵的大小定义为矩阵中所有元素的和。
    给定一个矩阵,你的任务是找到最大的非空(大小至少是1 1)子矩阵。
    比如,如下4 
    4的矩阵
    0 -2 -7 0
    9 2 -6 2
    -4 1 -4 1
    -1 8 0 -2
    的最大子矩阵是
    9 2
    -4 1
    -1 8
    这个子矩阵的大小是15。

    最开始我的分析是这样的:要确定一个矩阵至少得4个元素,即4个角;或者起始坐标以及长度宽度。我们可以遍历每个顶点以及每种边长。
    可是这样的复杂度简直是爆炸的。

    直觉告诉我,只能用动态规划了。
    因为动态规划可以把复杂的问题划分成很小的部分。

    那么问题来了,这个问题的子问题是什么?

    其实找到子问题是解题思路里面最重要的部分。

    我们之前碰到的一个问题是,求一维数组里面的最大和。感觉这里可以用,又不知道怎么用。

    我们上面说到了,确定一个子矩阵得至少4个元素,那假设我们已经知道了其中的两个:
    假设最优解在第j行和第i行之间,剩下的就是去确定两个列了。

    • 既然我们已经把解的范围局限在i,j两行之间了,我们真的需要去求具体的哪一列吗?

    • 先这样看,如果i,j相等的话,也就是解在同一列。这样的话,问题是不是就转换为求一维数组的最大和了呢?

    • 扩展到一般情况:i,j不想等:比如两行为:
      1 2 -3 -4
      -5 7 2 3
      那么我们如何求呢?
      降维!
      我们把每一列压缩为一个数,然后求一维的最大和就ok了。

    整理一下思路:

    1,我们遍历所有的 行 的组合情况,即第i行到第j行的所有情况。
    2,然后对每个组合之间的两行之间的元素求这一列的值
    3,对一个一维的和数组求最大和
    4,对上述的最大和求最大值

    在具体实现的时候,我们定住第i行不动,移动第j行,然后不断的求两行之间的每一列的和(压缩)。
    然后在每次移动i的时候,我们清空储存列的和的数组。

    程序:

     1 //我们有第i行到第j行,然后求出每一列的从i到j的和,转化为一维数组,然后求这个数组的最大和
     2 #include <iostream>
     3 #include <cstring>
     4 int maxSubArray(int* a,int n)//一维数组的最大和
     5 {
     6     if(!a||n<=0)
     7         return 0;
     8     int curmax=0,max=0;
     9     max=curmax=a[0];
    10     for(int i=1;i<n;i++)
    11     {
    12         if(curmax>=0)
    13         {
    14             curmax+=a[i];
    15         }
    16         else 
    17             curmax=a[i];
    18         if(curmax>max)
    19             max=curmax;
    20     }
    21     return max;
    22  } 
    23 
    24  int maxSumInMatrix(int a[200][200],int n)
    25  {
    26      int i=0,j=0,k=0;
    27      int sumij[200]={0};//从i到j的每一列的和
    28 
    29      int max_n=a[0][0],max=a[0][0];
    30 
    31      for(i=0;i<n;i++)
    32      {
    33          memset(sumij,0,sizeof(sumij));//clear,每次移动i的时候清除
    34          for(j=i;j<n;j++)
    35          {
    36 
    37              for(k=0;k<n;k++)
    38              {
    39                  sumij[k]+=a[j][k];
    40              }
    41              max_n=maxSubArray(sumij,n);
    42              if(max_n>max)//检查并更新最大值
    43              max=max_n;
    44          }         
    45      }
    46      return max;
    47 }
    48 
    49  int main()
    50  {
    51      int a[200][200];
    52      memset(a,0,sizeof(a));
    53      int n;
    54      std::cin>>n;
    55      for(int i=0;i<n;i++)
    56      {
    57          for(int j=0;j<n;j++)
    58          std::cin>>a[i][j];
    59      }
    60      std::cout<<maxSumInMatrix(a,n);
    61  }

    本文作者 凌风 csdn(iaccepted)

    作者博客:http://blog.csdn.Net/iaccepted,欢迎交流

    http://www.jianshu.com/p/d5a5c75720c8

  • 相关阅读:
    JS中的间歇(周期)调用setInterval()与超时(延迟)调用setTimeout()相关总结
    jQuery中的height()、innerheight()、outerheight()的区别总结
    单行及多行文本溢出以省略号显示的方法总结
    Android图片缩放 指定尺寸
    Android开源SlidingMenu的使用
    说说Android应用的persistent属性
    Android使用init.rc触发脚本实现隐藏内置应用
    android之实现上下左右翻页效果
    Android中播放声音
    Android中StatFs获取系统/sdcard存储(剩余空间)大小
  • 原文地址:https://www.cnblogs.com/aabbcc/p/6506502.html
Copyright © 2020-2023  润新知