簡體   English   中英

動態編程總和

[英]Dynamic programming sum

你將如何使用動態編程來找到一個數組中的正整數列表,其總和最接近但不等於某個正整數K?

我有點卡住這個想法。

通常的措辭就是你正在尋找最接近但不超過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之前突破循環(因為152給出了17的期望結果)。

其次,在表本身中,我們在任何給定時間只使用兩行:一行當前行和一行。 之前的行(在主表中)永遠不會再次使用(即,要計算row [n]我們需要row[n-1]的值,但不需要row[n-2]row[n-3] ,... row[0] 。為了減少存儲,我們可以使主表只有兩行,並且我們在第一行和第二行之間進行交換。這樣做非常類似C的技巧就是只使用最少的一行行號的重要位,所以你分別用table[i&1]table[(i-1)&1]替換table[i]table[i-1] (但僅用於主表 - 不是當解決used表格。

這是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]

這個想法只是為了跟蹤在您通過數組時可以從所有先前元素中得到的總和,以及實現該總和的一種方法。 最后,你只需選擇最接近你想要的東西。 它是一種動態編程解決方案,因為它將整體問題分解為子問題,並使用表來記住子問題的結果,而不是重新計算它們。

卡托在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})))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM