【问题标题】:Grid containing apples包含苹果的网格
【发布时间】:2012-07-23 13:57:04
【问题描述】:

我在编程论坛上发现了这个问题:

给定一个由 N*M 个单元组成的表格,每个单元有一定数量的苹果。你从左上角开始。在每一步中,您可以向下或向右移动一个单元格。如果您从左上角移动到右下角,请设计一种算法来找到您可以收集的最大苹果数。

我想到了三种不同的复杂性[在时间和空间方面]:

方法1[最快]:

for(j=1,i=0;j<column;j++)
    apple[i][j]=apple[i][j-1]+apple[i][j];
for(i=1,j=0;i<row;i++)
    apple[i][j]=apple[i-1][j]+apple[i][j];

for(i=1;i<row;i++)
{
    for(j=1;j<column;j++)
    {
        if(apple[i][j-1]>=apple[i-1][j])
            apple[i][j]=apple[i][j]+apple[i][j-1];
        else
            apple[i][j]=apple[i][j]+apple[i-1][j];
    }
}
printf("\n maximum apple u can pick=%d",apple[row-1][column-1]);    

方法二:

result 是所有槽初始为 0 的临时数组。

int getMax(int i, int j)
{
    if( (i<ROW) && (j<COL) )
    {
        if( result[i][j] != 0 )
            return result[i][j];
        else
        {
            int right = getMax(i, j+1);
            int down = getMax(i+1, j);

            result[i][j] = ( (right>down) ? right : down )+apples[i][j];
            return result[i][j];
        }
    }
    else
        return 0;
}

方法3[使用最少的空间]:

它不使用任何临时数组。

int getMax(int i, int j)
{
    if( (i<M) && (j<N) )
    {
            int right = getMax(i, j+1);
            int down = getMax(i+1, j);
            return apples[i][j]+(right>down?right:down);
    }
    else
        return 0;
}

我想知道解决这个问题的最佳方法是什么?

【问题讨论】:

  • 请定义“最佳”。最快?使用的空间最少?最直观?
  • 方法 2 中的一个小错误:如果 apples 数组完全为空,您将获得指数运行时间。

标签: c algorithm optimization structure


【解决方案1】:

方法 1 和 2 之间几乎没有区别,方法 1 可能稍微好一点,因为它不需要堆栈来进行方法 2 使用的递归,因为这会倒退。

方法 3 具有指数时间复杂度,因此它比其他两种复杂度为 O(rows*columns) 的方法差得多。

您可以制作方法 1 的变体,沿对角线前进以仅使用 O(max{rows,columns}) 额外空间。

【讨论】:

  • 不保证网格的大小为 NxN。那么,穿过对角线是否可行?
  • 是的,这是可行的,但当然要复杂一些。您使用的对角线由row_index + column_index == constant 给出。您从“对角线”(0,0) 及其苹果值开始,然后是for(sum = 1, diag_len = 1; sum &lt; N+M-1; ++sum) { /* allocate new diagonal of length new_len = min { 1 + diag_len, M, N } */ for(row = min(N-1,sum); row &lt; min(M,sum+1); ++row) { col = sum - row; /* compare and store new value */ } }(N 是行数,M 是列数)。我认为节省空间很少值得花精力检查一切是否正确。
  • @DanielFischer 看看我的答案,而不是做对角线,你可以只存储一个列或行作为临时。它实际上不那么复杂,并且使用 O( min{rows,columns} ) 额外空间。
【解决方案2】:

就时间而言,解决方案 1 是最好的,因为没有递归函数。 递归函数的调用需要时间

【讨论】:

    【解决方案3】:

    改进第一种方法

    你真的需要临时数组是 N 乘 M 吗?

    没有。

    如果初始二维数组有 N 列和 M 行,我们可以用长度为 M 的一维数组来解决这个问题。

    方法

    在您的第一种方法中,您可以随时保存所有小计,但是当您移动到下一列时,您实际上只需要知道左侧和上方单元格的苹果值。一旦你确定了这一点,你就不会再看那些以前的单元格了。

    然后解决方案是在您从下一列开始时改写旧值。

    代码将如下所示(我实际上不是 C 程序员,请耐心等待):

    代码

    int getMax()
    {
        //apple[][] is the original apple array
        //N is # of columns of apple[][]
        //M is # of rows of apple[][]
        //temp[] is initialized to zeroes, and has length M
    
        for (int currentCol = 0; currentCol < N; currentCol++)
        {
            temp[0] += apple[currentCol][0]; //Nothing above top row
    
            for (int i = 1; i < M; i++)
            {
                int applesToLeft = temp[i];
                int applesAbove = temp[i-1];
    
                if (applesToLeft > applesAbove)
                {
                    temp[i] = applesToLeft + apple[currentCol][i];
                }
                else
                {
                    temp[i] = applesAbove + apple[currentCol][i];
                }
            }
        }
    
        return temp[M - 1];
    }
    

    注意:没有任何理由将 applesToLeft 和 applesAbove 的值实际存储到局部变量中,您可以随意使用 ? : 赋值的语法。

    此外,如果列数少于行数,您应该旋转它,使一维数组的长度更短。

    这样做是对您的第一种方法的直接改进,因为它可以节省内存,而且迭代相同的一维数组确实有助于缓存。

    我只能想到一个使用不同方法的原因:

    多线程

    为了在这个问题上获得多线程的好处,你的第二种方法是正确的。

    在您的第二种方法中,您使用备忘录来存储中间结果。

    如果您使您的备忘录线程安全(通过锁定或使用无锁哈希集),那么您可以启动多个线程都试图获得右下角的答案。

    [// 编辑:实际上由于将整数分配给数组是一个原子操作,我认为您根本不需要锁定]。

    让每次调用 getMax 随机选择是先做左边的 getMax 还是上面的 getMax。

    这意味着每个线程处理问题的不同部分,并且由于有备忘录,它不会重复不同线程已经完成的工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-29
      • 2020-05-04
      • 2012-05-05
      • 2019-04-21
      • 1970-01-01
      • 1970-01-01
      • 2011-01-20
      相关资源
      最近更新 更多