【问题标题】:Finding greatest sum of elements of array which is divisible by a given number查找可被给定数字整除的数组元素的最大总和
【发布时间】:2012-11-10 19:17:01
【问题描述】:

它来自一个编程问题。

问题如下:

将给出一个数字数组以及我们必须与之相除的数字 k。 我们必须从该数组中选择元素,使得这些元素的总和可以被 k 整除。这些元素的总和应该尽可能大。

输入:

第一行n,表示元素个数。

在下一行给出 n 个数字。

在下一行,k 给出了我们必须除以它的值。

输出:

通过从该数组 s.t. 中选择元素来获得尽可能大的总和。 sum 可以被 k 整除。

示例输入:

5 
1 6 2 9 5
8

样本输出:

16

请注意,16 可以通过多个数字组合获得,但我们在这里只关心最大和。

我提出的解决方案:

我遍历数组并在数组 b 中为给定的输入数组维护累积和,例如:

b=[1 7 9 18 23]

将数组b中的数字乘以k并将其存储到

c=[1 7 1 2 7]

现在 c 中具有相同值的数字,即索引 0 和索引 2;索引 1 和索引 4。 现在我已经有了所有的解决方案,答案是

max(b[2]-b[0],b[4]-b[1])

如果三个索引在 c 中具有相同的值,即如果

c=[1 2 3 1 1 2]

答案是

max(b[4]-b[0],b[5]-b[1])

基本上用最右边的数字减去最左边的数字。

我的解决方案只有在有连续元素 s.t. 时才有效。元素之和可被 k 整除。期待正确解决方案的描述

【问题讨论】:

  • 所有的数字都是整数吗?都是正面的?
  • @PaulR 是的,都是整数。
  • 所有的整数都是正数吗?
  • @HighPerformanceMark 对 b[5] 的参考不适用于这种情况,我已经给出了,我只是假设另一个情况以进行澄清。我给了我没有提供 b 数组的 c 数组。
  • 您是否必须从数组中选择连续的元素,或者是否会这样做?比如数组是1 2 3 4 5,可以选择1 2 5吗?

标签: algorithm dynamic-programming


【解决方案1】:

以下代码专门针对给定的数字 3。即。可被 3 整除的数组元素的总和。您可以进一步概括这一点。 主要思想是跟踪每个 mod 3,可以达到的最大总和。 时间复杂度:O(N)。 空间复杂度:O(K),其中 K 是整数,总和可以被它整除。这里 K = 3。

class Solution {
    public int maxSumDivThree(int[] nums) {
        int[] dp = new int[3];
        dp[1] = dp[2] = Integer.MIN_VALUE;
        for(int x : nums) {
            int[] dpNext = new int[3];
            dpNext[0] = Math.max(dp[x%3] + x, dp[0]);
            dpNext[1] = Math.max(dp[(x+1)%3] + x,dp[1]);
            dpNext[2] = Math.max(dp[(x+2)%3] + x,dp[2]);
            dp = dpNext;
        }
        return dp[0];
    }
}

LeetCode 每周竞赛 163 Link to the problem

【讨论】:

    【解决方案2】:

    我认为您的解决方案不正确,因为您只考虑连续数字。例如,如果输入是

    4
    1 6 2 9
    8
    

    答案仍然是 16 (=1+6+9)。我不确定你的解决方案能否给出这个答案。


    要有效解决此问题,请尝试动态规划。我会省略细节但指出要点。

    假设数字在数组a[i] 中,其中i 是从1n

    f(i,j) 表示通过从a[1]a[i](即a[1], a[2], ..., a[i])中选择数字可以获得的最大总和,并且以k 为模的总和为j

    考虑f(i,j),显然我们有两个选择:(1)在总和中包含a[i]; (2) 不包括a[i]。因此f(i,j) = max{ f(i-1,x) + a[i], f(i-1,j) }x + a[i] == j (mod k)。所有j的边界为f(0,j) = 0


    要实现这个算法,基本骨架如下。

    for (j = 0; j < k; j++) f[0][j] = 0;
    for (i = 1; i <= n; i++)
      for (j = 0; j < k; j++) {
        x = (j + k - a[i]%k) % k;
        f[i][j] = max(f[i-1][x], f[i-1][j]);
      }
    

    为了节省内存,你也可以使用大小为[2][k]的数组来代替[n][k]

    for (j = 0; j < k; j++) f[0][j] = 0;
    for (i = 1; i <= n; i++)
      for (j = 0; j < k; j++) {
        x = (j + k - a[i]%k) % k;
        f[i%2][j] = max(f[(i-1)%2][x], f[(i-1)%2][j]);
      }
    

    你也可以使用i&amp;1(和(i-1)&amp;1)来加速2的模数。


    动态规划的进一步参考:

    【讨论】:

    • @AkashdeepSaluja 我想我的解决方案对于初学者来说更容易理解。为了节省内存以免我们最终得到O(n*k),您可以安全地使用这个[2][k] 版本,而不会覆盖之前计算的结果。但是,在经典的背包问题中,您可以对背包容量执行此操作,因为它在这个意义上是严格递减的。
    • 谢谢,我明白了。您能否帮我提供一些很好的参考资料,以阅读有关动态编程的更多信息。
    • @AkashdeepSaluja 我用一些参考资料更新了答案。有兴趣的可以一一尝试:-D
    • @XiaoJia,如果您可以为上述 dp 编写递归代码,将会有很大帮助。我想从递归驱动到记忆再到 dp。这样我就可以更好地理解了。非常感谢:)
    【解决方案3】:

    注意:对于号码为3的特殊情况,可以在O(n log n)时间轻松找到答案。

    S = sum(array).
    现在,如果S % 3 == 0,那么S 就是答案。
    如果S % 3 == 1,那么要使总和可被3 整除,您可以删除最小的元素i 使得i % 3 == 1,或者删除最小的jk 使得j % 3 == k % 3 == 2
    如果S % 3 == 2,那么您可以删除最小的i,如i % 3 == 2,或最小的jk,如j % 3 == k % 3 == 1

    【讨论】:

      【解决方案4】:
      import java.util.*;
      
      public class MaxSumDivisible 
      {
      
          static int max,divisor;
      
      public static void main(String...okok)
      {
          Scanner sc=new Scanner(System.in);
          String str=sc.nextLine();
          String ss[]=str.split(" ");
          LinkedList<Integer> list= new LinkedList<Integer>();
          for(int i=0;i<ss.length;i++)
          {
              list.add(Integer.parseInt(ss[i]));
          }
          divisor=sc.nextInt();
          FindMaxSum(list,0);
          System.out.println(max);
      }
      public static void FindMaxSum(LinkedList<Integer> list, int currentsum)
      {
          if(currentsum%divisor==0 && currentsum>max)
          {
              max=currentsum;
          }
      
          for(int num:list)
          {
              LinkedList<Integer> li2= new LinkedList<Integer>(list);
              li2.remove(new Integer(num));
              FindMaxSum(li2,currentsum+num);
      
          }
      }
      }
      

      它适用于任何数字。(仅适用于 int)。

      【讨论】:

      • 这是回溯,时间复杂度比动态规划方案差。就我而言,如果我在 OA 中实现这一点,则会给出时间限制异常。
      【解决方案5】:

      听起来像是subset sum 的变体:您想要最大和可被k 整除的子集。

      dp[i] = largest sum obtainable that gives remainder i modulo k。但是,为了避免使用两次相同的元素,我们必须使用两个数组,因为模数:包含dp (dp1) 的当前值的数组和包含dp (dp2) 先前值的数组)。我们有:

      a = original array
      dp1[*] = dp2[*] = 0 
      for i = 1 to n do
        for j = k - 1 down to 0 do
          dp1[j] = max(dp1[j], dp2[(j - a[i]) mod k] + a[i])
      
        copy dp1 to dp2: on the next iteration, the current array will must become the
        previous one (*)
      

      (*) 请注意,如果执行时间非常重要,则不必进行任何复制。您可以使用数组dp[2, k] 并交替使用它的行:计算机从dp[0, _]dp[1, _] 在奇数迭代中,反之在偶数迭代中。

      答案将在dp1[0, 0]dp2[0, 0] 中。使用的内存为O(n + k),时间复杂度为O(n * k)

      注意:在实现此功能时,您可能需要以这种方式进行取模以避免负值:((j - a[i]) mod k + k) mod k。或者您可以使用if,并且仅在初始值为负时添加k

      【讨论】:

      • @AkashdeepSaluja 我想这不是答案,因为 (i) j 可能小于 a[i],并且 (ii) 不能保证 (j - a[i]) mod k 小于 j (注意它是模数,而不是直接减法!)。不要将其与经典的背包问题混为一谈。 :-)
      • 谢谢,实际上我已经研究过动态规划,但在新问题中通常无法弄清楚。所以我想问题在于我对它的理解,所以如果你知道动态编程的任何好的参考,请提供给我。
      • @XiaoJia - 你的观点:那又怎样?我看不出这会如何影响正确性。正是因为我们正在使用 mod k,所以这些事情并不重要。
      • @IVlad 不应该使用覆盖这个词,但是您的代码可能会在dp[j] 中添加两次(或者更多我想是)a[i]。我试图想出一个具体的例子来说明这一点。
      • @IVlad 假设只有 1 个 a[1]=2k=3。按照你的代码,j[2] 将是 2,然后 j[1] 将是 4,最后 j[0] 将是 6。 (或者也许我在某个地方犯了一个错误,因为我很久没有做数学了:-)
      猜你喜欢
      • 1970-01-01
      • 2012-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多