正和<= K的最小长度子序列

时间:2018-09-06 19:32:08

标签: algorithm dynamic-programming knapsack-problem subsequence minimum-size

相反的问题:

  

具有正和<= K的最大长度子序列实际上是   标准的01背包问题。

解决方案非常简单:

int solve(const vector<int> &A, int K) {
    int dp[A.size()+1][K+1];
    int i, j;

    // Base Cases
    for(i=0; i<= K; i++)
        dp[0][i] = 0;
    for(i=0; i<= A.size(); i++)
        dp[i][0] = 0;


    for(i=1; i <= A.size(); i++)
    {
        for(j=1; j<= K; j++)
        {
            dp[i][j] = dp[i-1][j];
            if(A[i-1] <= j)
                dp[i][j] = max(dp[i][j], 1 + dp[i-1][j-A[i-1]]);
        }
    }
    return dp[A.size()][K]

我在思考如何以相同的方式实现总和<= K 的最小长度子序列时遇到了困难。

示例:

A = [14, 10, 4]
K = 14
Minimum Length Subsequence = 14
Maximum Length Subsequence = 10, 4 (Arrived from Knapsack)

肯定不像将max更改为min那样容易,因为答案始终是基本情况。这使我想到:我们需要调整基本情况吗?我被困在这里,需要一些推动。

关于应该如何解决此问题的任何想法?

3 个答案:

答案 0 :(得分:1)

用有序对(sum, length)替换总和。现在应用您知道的先前算法。顺序是字典式的,先求和再求长度。您正试图接近(target_sum, 0)

现在最接近的“和”将是具有最小正差的最短子序列。

答案 1 :(得分:1)

下面的代码片段显示了筛子的含义。这是一个简单的解决方案,可能对大量输入没有用。这不是像筛子那样查找素数,后者只包含true或false,而更像是组合及其和的字典,例如:

{value: 14, components: [4, 10]}  

如果您不熟悉Javascript,则数组的行为更像是带有字符串键的关联数组或字典(这就是为什么需要Number转换的原因),而for in仅迭代具有数组稀疏时的值。同样,sliceconcat创建该数组的副本。

function minSub(array, target) {
    var sieve = [[]];
    for (var i in array) {
        var temp = [];
        for (var j in sieve) {
            var val = Number(j) + array[i];
            if (!sieve[val] || sieve[val].length > sieve[j].length + 1) {
                temp[val] = sieve[j].concat([array[i]]);
            }
        }
        for (var j in temp) {
            if (Number(j) <= target) {
                sieve[j] = temp[j].slice();
            }
        }
    }
    var max = 0;
    for (var j in sieve) {
        if (Number(j) > max) {
            max = Number(j);
        }
    }
    return sieve[max];
}

console.log(minSub([4, 10, 14], 14));
console.log(minSub([0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0], 8));

请注意,与我在注释中建议的相反,以降序对输入进行排序并不能保证首先找到构成值的最简单组合。每当遇到筛子中已经存在的值时,您都必须检查组件的数量;例如输入:

{8, 4, 3, 2, 1}

您会找到组合:

{value: 9, components: [4, 3, 2]}

在找到之前:

{value: 9, components: [8, 1]}

答案 2 :(得分:1)

我认为这与您要查找的内容一致。在检查是否可以达到总和时,我们必须比制定最大子序列时更加谨慎。在此公式中,dp[i][j]是求和到j的最小子序列,并考虑到A[i]以下的元素(因此i不是子序列的长度)。

JavaScript代码(仅经过轻微测试):

function solve(A, K) {
  let i,j;

  let dp = new Array(length);

  for (i=0; i<A.length; i++)
    dp[i] = new Array(K + 1);

  // Base Cases
  for(i=0; i<A.length; i++)
    dp[i][0] = 0;

  for (i=0; i<A.length; i++){
    // Exact match
    if (A[i] == K)
      return 1;

    // We can reach this sum with only one element
    if (A[i] < K)
      dp[i][A[i]] = 1;
    
    // There are no previously achieved sums
    if (i == 0)
      continue;
    
    for (j=1; j<=K; j++){
      dp[i][j] = dp[i][j] || dp[i - 1][j];

      if (A[i] <= j){
        dp[i][j] = Math.min(
          dp[i][j] || Infinity,
          1 + (dp[i - 1][j - A[i]] || Infinity)
        );
      }
    }
  }
  
  for (i=K; i>=0; i--)
    if (![undefined, Infinity].includes(dp[A.length - 1][i]))
      return dp[A.length - 1][i];
}

console.log(solve([1,2,3,4,5,6,7,8,9,10], 11));
console.log(solve([14,10,4], 14));
console.log(solve([0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0], 8));
console.log(solve([7,7,2,3],15))