【问题标题】:Java Combinatorics using Memoization/Dynamic Programming使用记忆/动态编程的 Java 组合
【发布时间】:2023-03-05 07:57:01
【问题描述】:

我正在编写一个程序,它给出两个数字的可能组合数量,例如 N 选择 K。我有一个递归解决方案,如下所示:

public static int combinations(int group, int members) {
    if (members == 1) {
        return group;
    }
    else if (members == group) {
        return 1;
    }
    else {
        return(combinations(group - 1, members - 1) + 
                combinations(group - 1, members));
    }
}

这个可行,但我需要使用记忆来提高时间复杂度并加快处理更大数字的速度,但我不知道该怎么做。我该怎么做呢?

【问题讨论】:

    标签: java dynamic-programming combinatorics memoization


    【解决方案1】:

    使用n choose k = ( n - 1 choose k - 1) + ( n-1 choose k ) 的公式 自下而上的动态规划方法是:

    dp[n][k] = dp[n-1][k-1] + dp[n-1][k] if n > k 
    else if n == k
    dp[n][k] = 1
    else
    dp[n][k] = 0
    

    n = 1k = 1开始

    dp[1][1] = 1; dp[1][0] = 1; 
    

    然后填充一个二维数组直到dp[n][k]

    也可以像你的情况一样通过记忆来完成。您的方法可以更改为:

    int[][] dp = new int[group][members];
    
    public static int combinations(int group, int members, int[][] dp ) {
    
        if (members == 1) {
            return group;
        } else if (members == group) {
            return 1;
        }
    
        if ( dp[group][members] != 0 ) {
           return dp[group][members];
        }
    
        int first = 0, second = 0;
        if ( members <= group - 1) {
          first = combinations( group - 1, members - 1, dp );
          second = combinations( group - 1, members );
        } else if ( members - 1 <= group - 1 ) {
          first = combinations( group - 1, members - 1, dp );
        }
        dp[group][members] = first + second;
    
        return dp[group][members];
    }
    

    【讨论】:

      【解决方案2】:

      一种方法是进行缓存,这会带来很大的内存使用代价。

      public static int combinations(int group, int members) {
          if (members > group - members) {
              members = group - members; // 21 choose 17 is same as 21 choose 4
          }
      
          final int[][] cache = new int[group][members];
          return combinations(group, members, cache);
      }
      private static int combinations(int group, int members, int[][] cache) {
          if (cache[group - 1][members - 1] > 0) {
              return cache[group - 1][members - 1];
          }
          else if (members == 1) {
              cache[group - 1][members - 1] = group;
              return group;
          }
          else if (members == group) {
              cache[group - 1][members - 1] = 1;
              return 1;
          }
          else {
              return (combinations(group - 1, members - 1, cache) + combinations(group - 1, members, cache));
          }
      }
      

      我做了一些快速测试(非专业基准测试),发现原来的方法占用了缓存方法的一半时间。看起来所有这些对数组缓存的读/写操作都大大减慢了速度。

      另一种方法是改变整个公式。

      public static int combinations(int group, int members) {
          if (members > group - members) {
              members = group - members;
          }
      
          int answer = 1;
          for (int i = group; i > group - members; i--) {
              answer *= i;
          }
      
          for (int i = 1; i <= members; i++) {
              answer /= i;
          }
      
          return answer;
      }
      

      再次,我用原始方法测试了新方法(我让他们使用BigInteger 进行测试),新方法速度更快(原始方法为 26 秒,后者为 0.00 秒,35 选择 15 )。

      补充一点,我认为使用递归调用的时间复杂度是O((group)(log members)),而使用新公式只是O(members)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-05-11
        • 2021-03-24
        • 1970-01-01
        • 2021-10-01
        • 2020-12-10
        • 2010-10-15
        • 1970-01-01
        相关资源
        最近更新 更多