什么是解决这个问题的更好方法?

时间:2012-12-26 12:38:45

标签: algorithm search time dynamic-programming

这是我想解决的问题,

  

您将获得一个包含2行和N列的表格。每个单元格中都有一个整数。分数   这样的表的定义如下:对于每列,考虑两个数字的总和   在专栏中;如此获得的N个数的最大值是得分。例如,对于   表

     

7 1 6 2
  1 2 3 4

     

得分为max(7 + 1; 1 + 2; 6 + 3; 2 + 4)= 9。   表的第一行是固定的,并作为输入给出。第二种可能的方法   行被认为是:

     

1; 2; ::: ;; ñ
  2; 3; ::: ;; N; 1
  3; 4; ::: ;; N; 1; 2
  |
  N; 1; ::: ;; ; N 1

     

例如,对于上面的示例,我们将以下各项视为第二行的可能性。

     

1 2 3 4
  2 3 4 1
  3 4 1 2
  4 1 2 3

     

您的任务是找到第二行上述每个选项的分数。在里面   上面的示例,您将评估以下四个表,

     

7 1 6 2
  1 2 3 4
  7 1 6 2
  2 3 4 1
  7 1 6 2
  3 4 1 2
  7 1 6 2
  4 1 2 3
  并分别计算得分9,10,10和11

     

测试数据:N <= 200000
  时间限制:2秒

这是一个显而易见的方法:

维护两个阵列A,B,执行以下n次

  • 将每个元素A [i]添加到B [i]并保留一个变量max,它存储到目前为止的最大值。
  • print max
  • 循环遍历数组B [i]并将所有元素递增1,如果任何元素等于N,则将其设置为1。

此方法将花费O(n ^ 2)时间,外部循环运行N次,并且有两个内部循环,每个循环运行N次。

为了减少所花费的时间,我们可以在第一行中找到最大元素M(在线性扫描中),然后在A [i] + N <= M时移除A [i]和B [i] + 1.
因为他们永远不会是最大的。

但是这种方法在平均情况下表现可能更好,最坏情况时间仍然是O(N ^ 2)。

要在常量时间内查找max,我还考虑使用堆,堆的每个元素都有两个属性,它们的原始值和要添加的值。 但是,对于n个案例中的每一个,仍然需要一个线性时间来增加堆的所有元素的待添加值。
所以时间仍然是O(N ^ 2)

我无法找到能够比N ^ 2时间更快地解决这个问题的方法,因为N的值可能非常大,因此速度太慢。
任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:5)

还有O(n)算法。使用与之前答案中相同的观察结果:

  

现在考虑将第二行向左旋转时列的总和会发生什么变化(例如将其从1,2,...,N更改为2,3,...,N,1):每列总和将增加1,除了一列总和减少N-1。

     

我们可以将一列总和减少N,而不是修改所有列总和,然后取最大列总和加1来查找列总和的新最大值。所以我们只需要更新一列而不是所有列。

当我们迭代第二行的可能性时,出现最大值的列只能向左移动或跳回到具有总体最大值的列。候选列是从左到右最大扫描中的临时最大值。

  1. 计算第二行(1,2,...,N)第一选择的所有总和,并将它们存储在一个数组中。
  2. 从左到右扫描找到此数组中的最大值,并记住临时最大值的位置。
  3. 在从右到左的过程中,总和现在减少了N.如果此减少过程达到最大列,请检查该数字是否小于总体最大值 - N,在这种情况下,新的最大列是总体最大值列,它将保持在那里循环的其余部分。如果该数字仍然大于步骤2中确定的先前最大值,则对于循环的其余部分,最大列将保持不变。否则,先前的最大值将成为新的最大列。
  4. 以示例输入7,1,6,2算法运行如下: 步骤1计算总和8,3,9,6 步骤2从左到右找到临时最大值:col 1中为8,col 3中为9 第3步生成从右到左遍历数组的结果

    8 3 9 6 -> output 9 + 0 = 9
    8 3 9 2 -> output 9 + 1 = 10
    8 3 5 2 -> current max col is decreased, previous max 8 is larger and becomes current
               output 8 + 2 = 10
    8 -1 5 2 -> output 8 + 3 = 11
    

    以下是C:

    中的算法
    #include <stdio.h>
    
    int N;
    int A[200000];
    int M[200000];
    
    int main(){
        int i,m,max,j,mval,mmax;
    
        scanf("%d",&N);
    
        for(i = 0;i < N; i++){
            scanf("%d",&A[i]);
            A[i] = A[i]+i+1;
        }
    
        m = 0;
        max = 0;
        M[0] = 0;
    
        for(i = 1;i < N; i++){
            if(A[i] > A[max]){
                m++;
                M[m] = i;
                max = i;
            }
        }
    
        mval = A[max] - N;
        mmax = max;
    
        for(i = N-1,j = 0;i >=0;i --,j++){
            printf("%d ", A[max]+j);
    
            A[i] = A[i] - N;
    
            if(i == max){
                if (A[i] < mval) {
                    max = mmax;
                } else if(m > 0 && A[i] < A[M[m-1]]){
                    max = M[m-1];
                    m--;
                }
            }
        }
    
        printf("\n");
    
        return 0;
    }
    

答案 1 :(得分:2)

以下是O(n*logn)解决方案:

假设您计算第二行特定排列的所有列总和。

现在考虑将第二行向左旋转时列的总和会发生什么变化(例如将其从1,2,...,N更改为2,3,...,N,1):每列总和将增加1,但一列除外总和减少了N-1。

我们可以将一列总和减少N,而不是修改所有列总和,然后取最大列总和加1来查找列总和的新最大值。所以我们只需要更新一列而不是所有列。

所以我们的算法是:

  • 将第二行设置为1,2,...,N并计算每列的总和。将所有这些总和放在最大堆中。堆的根将是最大的总和。

  • 对于{1}中的i N

    • N-i列对应的堆节点的值减少N
    • 新的最大列总和是堆的根加i

每次堆更新需要O(logn),这会导致总时间O(n*logn)