动态编程总和

时间:2012-05-05 18:52:02

标签: algorithm sum dynamic-programming knapsack-problem subset-sum

如何使用动态编程来查找数组中的正整数列表,其总和最接近但不等于某个正整数K?

我有点想不起。

3 个答案:

答案 0 :(得分:6)

通常的措辞是你正在寻找最接近但不超过K的值。如果你的意思是“小于K”,那就意味着你的K值比通常的值大1。如果你真的只是意味着“不等于K”,那么你基本上会经历算法两次,一旦找到小于K的最大和,然后再次找到大于K的最小和,然后选择其中一个绝对值与K的差异是最小的。

目前我假设你真的是指小于或等于K的最大总和,因为这是最常见的公式,而其他可能性对算法没有太大影响。

基本思想相当简单,尽管它(至少可能)使用了大量存储空间。我们构建一个包含K + 1列和N + 1行的表(其中N =输入数)。我们将表中的第一行初始化为0。

然后我们开始遍历表格,并为每个可能的最大值建立最佳值,直到实际最大值,逐行,所以我们从一个输入开始,然后是两个可能的输入,然后是三个,等等。在表中的每个点,只有两种可能的最佳值:不使用当前输入的先前最佳值,或者当前输入加上最大值的前一个最佳值减去当前输入(以及我们按顺序计算表值,我们总是已经有了这个值。

我们通常还希望跟踪实际用于生成结果的项目。为此,我们将表中给定点的布尔值设置为true,当且仅当我们使用该行的新输入计算表中该点的值时(而不是仅复制前一行的最佳值)。最好的结果是在右下角,所以我们从那里开始,一次一行地向后走过桌子。当我们到达该列的布尔值设置为true的行时,我们知道我们找到了一个使用的输入。我们打印出该项目,然后从总计中减去该项目以获得左侧的下一列,我们将找到用于生成此输出的下一个输入。

这是一个技术上用C ++实现的实现,但主要是以类似C的方式编写,以使每个步骤尽可能明确。

#include <iostream>
#include <functional>

#define elements(array) (sizeof(array)/sizeof(array[0]))

int main() {

    // Since we're assuming subscripts from 1..N, I've inserted a dummy value
    // for v[0].
    int v[] = {0, 7, 15, 2, 1};

    // For the moment I'm assuming a maximum <= MAX.
    const int MAX = 17;

    // ... but if you want to specify K as the question implies, where sum<K, 
    // you can get rid of MAX and just specify K directly:
    const int K = MAX + 1;

    const int rows = elements(v);

    int table[rows][K] = {0};
    bool used[rows][K] = {false};

    for (int i=1; i<rows; i++)
        for (int c = 0; c<K; c++) {
            int prev_val = table[i-1][c];
            int new_val;

            // we compute new_val inside the if statement so we won't 
            // accidentally try to use a negative column from the table if v[i]>c
            if (v[i] <= c && (new_val=v[i]+table[i-1][c-v[i]]) > prev_val) {
                table[i][c] = new_val;
                used[i][c] = true;
            }
            else
                table[i][c] = prev_val;
        }

    std::cout << "Result: " << table[rows-1][MAX] << "\n";
    std::cout << "Used items where:\n";
    int column = MAX;
    for (int i=rows; i>-1; i--)
        if (used[i][column]) {
            std::cout << "\tv[" << i << "] = " << v[i] << "\n";
            column -= v[i];
        }

    return 0;
}

在这方面你通常会优化一些事情(我没有为了便于阅读)。首先,如果你达到一个最佳总和,你可以停止搜索,所以在这种情况下,我们实际上可以在考虑1的最终输入之前突破循环(从15和{{1给出2)的期望结果。

其次,在表本身中,我们在任何给定时间只使用两行:一行当前行和一行。之前的行(在主表中)永远不会再次使用(即,为了计算行[n],我们需要来自17的值,而不是row[n-1]row[n-2],... 。row[n-3]。为了减少存储,我们可以使主表只有两行,我们在第一行和第二行之间交换。一个非常类似于C的技巧就是只使用最低有效位行号,因此您分别用row[0]table[i]替换table[i-1]table[i&1](但仅针对主表 - 而不是在解决{{1}时表格。

答案 1 :(得分:3)

这是python中的一个例子:

def closestSum(a,k):
  s={0:[]}
  for x in a:
    ns=dict(s)
    for j in s:
      ns[j+x]=s[j]+[x]
    s=ns
  if k in s:
    del s[k]
  return s[min(s,key=lambda i:abs(i-k))]

示例:

>>> print closestSum([1,2,5,6],10)
[1, 2, 6]

这个想法只是为了跟踪在通过数组时可以从所有先前元素中得到的总和,以及实现这一总和的一种方法。最后,你只需选择最接近你想要的东西。它是一个动态编程解决方案,因为它将整体问题分解为子问题,并使用表来记住子问题的结果,而不是重新计算它们。

答案 2 :(得分:1)

Cato在Racket中的想法:

#lang racket
(define (closest-sum xs k)
  (define h (make-hash '([0 . ()])))
  (for* ([x xs] [(s ys) (hash-copy h)])
    (hash-set! h (+ x s) (cons x ys))
    (hash-set! h x (list x)))
  (when (hash-ref h k #f) (hash-remove! h k))
  (cdr (argmin (λ (a) (abs (- k (car a)))) (hash->list h))))

要获得一个更加平坦的程序,可以从GitHub抓取terse-hash.rkt并写下:

(define (closest-sum xs k)
  (define h {make '([0 . ()])})
  (for* ([x xs] [(s ys) {copy h}])
    {! h (+ x s) (cons x ys)}
    {! h x (list x)})
  (when {h k #f} {remove! h k})
  (cdr (argmin (λ (a) (abs (- k (car a)))) {->list h})))