动态编程算法N,K问题

时间:2010-02-11 14:35:56

标签: c++ algorithm dynamic-programming

一种算法,它将取两个正数N和K,并计算通过从N中删除K个数字将N转换为另一个数字而得到的最大可能数。

对于前者,假设我们有N = 12345和K = 3所以我们通过从N中删除3位数得到的最大可能数是45(其他变换将是12,15,35但45是最大的)。此外,您无法更改N中的数字顺序(因此54不是解决方案)。另一个例子是N = 66621542和K = 3,因此解决方案将是66654.

我知道这是一个动态编程相关的问题,我无法理解解决它。我需要解决这个问题2天,所以任何帮助都表示赞赏。如果你不想为我解决这个问题,你不必这样做,但请指出我的技巧或至少一些材料,我可以阅读更多有关类似问题的信息。

提前谢谢。

5 个答案:

答案 0 :(得分:6)

这可以在O(L)中求解,其中L =位数。当我们可以使用堆栈来执行此操作时,为什么要使用复杂的DP公式:

For:66621542 在堆栈上添加一个数字,同时堆栈上的L - K数字小于或等于: 66621.现在,当它们小于当前读取的数字时,从堆栈中删除数字并将当前数字放在堆栈上:

阅读5:5> 2,从堆栈弹出1。 5> 2,弹出2也。放5:6665 读4:堆栈不满,放4:66654 阅读2:2< 4,什么都不做。

您还需要一个条件:请确保不要从堆叠中弹出更多项目,而不是数字中剩余的数字,否则您的解决方案将不完整!

另一个例子:12345 L = 5,K = 3 将L - K = 2位数放在堆栈上:12

阅读3,3> 2,弹出2,3> 1,弹出1,放3.堆栈:3 阅读4,4> 3,弹出3,放4:4 阅读5:5> 4,但我们不能弹出4,否则我们将没有足够的数字。所以按5:45。

答案 1 :(得分:5)

那么,要解决任何动态编程问题,您需要将其分解为重复的子解决方案。

假设我们将您的问题定义为A(n,k),它通过从n中删除k个数字来返回可能的最大数字。

我们可以从中定义一个简单的递归算法。

使用你的例子,A​​(12345,3)= max {A(2345,2),A(1345,2),A(1245,2),A(1234,2)}

更一般地说,A(n,k)= max {A(n除1位数,k - 1)}

你的基础案例是A(n,0)= n。

使用此方法,您可以创建一个缓存n和k值的表。

int A(int n, int k)
{
  typedef std::pair<int, int> input;
  static std::map<input, int> cache;

  if (k == 0) return n;

  input i(n, k);
  if (cache.find(i) != cache.end())
    return cache[i];

  cache[i] = /* ... as above ... */

  return cache[i];
}

现在,这是直接的解决方案,但有一个更好的解决方案适用于非常小的一维缓存。考虑重写这样的问题:“给定一个字符串n和整数k,找到长度为k的n中按字典顺序排列的最大子序列”。这基本上就是你的问题所在,而且解决方案要简单得多。

我们现在可以定义一个不同的函数 B(i,j),它只使用第一个(i - j)给出最大的词典序列(i - j) em> i n 的数字(换句话说,已从 n的第一个 i 数字中删除 j 数字)。

再次使用您的示例,我们会:

B(1,0)= 1

B(2,0)= 12

B(3,0)= 123

B(3,1)= 23

B(3,2)= 3

通过一些思考,我们可以找到递归关系:

B(i,j)= max(10B(i-1,j)+ n i ,B(i-1,j-1)) < / p>

或者,如果 j = i 那么 B(i,j)= B(i-1,j-1)

B(0,0)= 0

你可以用与上述类似的方式编写代码。

答案 2 :(得分:4)

解决动态编程问题的技巧通常是弄清楚解决方案的结构是什么样的,更具体地说,如果它展示最佳子结构

在这种情况下,在我看来,N = 12345和K = 3的最优解将具有N = 12345和K = 2的最优解作为解的一部分。如果你可以说服自己这一点,那么你应该能够递归地表达问题的解决方案。然后用memoisation或自下而上实现它。

答案 3 :(得分:1)

任何动态编程解决方案的两个最重要的元素是:

  1. 定义正确的子问题
  2. 在子问题的答案与较小的子问题的答案之间定义递归关系
  3. 查找基本情况,其答案不依赖于任何其他答案的最小子问题
  4. 确定必须解决子问题的扫描顺序(以便永远不会使用基于未初始化数据的重复关系
  5. 您将知道在

    时定义了正确的子问题
    • 您需要答案的问题是其中之一
    • 基本案例真的很简单
    • 复发很容易评估
    • 扫描顺序很简单

    在您的情况下,指定子问题很简单。由于这可能是家庭作业,我只会给你一个提示你可能希望N的数字开头少

答案 4 :(得分:0)

这就是我的想法:

考虑左边的第一个k + 1位数。寻找最大的一个,找到它并删除左边的数字。如果存在两个相同的最大数字,找到最左边的一个并删除左边的数字。存储删除的数字的数量(将其命名为j)。

用新的数字作为N和k + 1-j作为K做同样的事情。直到k + 1 -j等于1(希望,如果我没有记错的话,它会这样做)。

您最终得到的号码将是您要查找的号码。