【问题标题】:DP: Longest Increasing Subsequence Thought Process & SolutionDP:最长递增子序列思维过程和解决方案
【发布时间】:2016-06-09 05:20:36
【问题描述】:

对于Longest Increasing Subsequence 问题,我设想保留一个始终有序的DP 数组,以便将最大值保持在最远端。看起来像这样的东西:

{1, 1, 2, 3, 3, 4, 5, 6, 6, 6}

我产生第一个错误解决方案的思考过程是,我们想查看整个数组,仅从第一个元素开始,计算 LIS,然后在数组末尾递增地添加一个值。在执行此操作时,我们将 DP 数组中的 LIS 递增计算到旧子数组的 LIS 加上我们添加的新元素。这意味着在dp 数组的索引i 处存在长度为i 的子数组的LCS 值。

说得更清楚

array => {5, 6, 7, 1, 2, 3, 4}
dp    => {1, 2, 3, 3, 3, 3, 4}

这样,DP 数组的最后一个条目将是当前数组的 LIS。这将作为我们的不变量,所以当我们得到结束时,我们可以确信最后一个值是我们唯一需要的值。然后我恍然大悟,当我们以 DP 的感觉遍历一个数组时,下一个值不依赖于数组中先前列出的任何值,所以这种方法与维护一个 maxLIS 变量相同,我在许多O(n) 解决方案中看到的模式。所以我最接近正确的解决方案如下:

1.) 将输入数组/向量的副本另存为old

2.) 对原始输入数组进行排序

3.) 遍历排序后的数组,每次下一个值(应该大于当前值)出现在原始数组中的当前值之前,变量longest 就加一。

4.) 返回longest

代码是 ~this:

int lengthOfLIS(vector<int>& seq) {
  if (!seq.size()) return 0;
  vector<int> old = seq;

  sort(seq.begin(), seq.end());

  int longest = 1;

  for (int i = 1; i < seq.size(); ++i) {
    if (seq[i] > seq[i-1] && find(old.begin(), old.end(), seq[i]) - old.begin() > find(old.begin(), old.end(), seq[i-1]) - old.begin()) longest++;
  }
  return longest;
}

我们有find 方法(我假设是线性操作),我们可以通过创建一个数据结构来存储值的原始索引以及值本身来进行常量操作,所以我们不这样做'不必进行任何遍历来查找原始数组中元素的索引 (old)。我相信这将是一个O(nlog(n)) 解决方案,但是这个输入数组失败了:[1,3,6,7,9,4,10,5,6]CHECK HERE

最后我做了一些研究,发现我读过的所有解决方案指南都偷偷地认为他们的解决方案不是按顺序保持其 DP 数组的值,而是像这样:DP 数组中的值表示长度递增的子序列,子序列的最后一个值为originalArray[index]

说得更清楚,

array => {5, 6, 7, 1, 2, 3}
dp    => {1, 2, 3, 1, 2, 3}

这里,5 是递增子序列的最后一个值,它之前没有任何值,因此它的长度必须为 1。如果 6 是递增子序列的最后一个值,我们必须查看它之前的所有值以确定以 6 结尾的子序列可以有多长。只有 5 可以出现在它之前,因此使迄今为止最长的递增子序列为 2。这样继续下去,您将返回 DP 数组中的最大值。这个解决方案的时间复杂度是O(n^2),标准的朴素解决方案。

问题:

我很好奇如何正确思考这个问题。我想微调我的思维过程,以便我可以从头开始提出最佳解决方案(至少这是目标)所以我想知道

1.) 这个问题的什么属性应该触发我使用与我使用它的方式不同的 DP 数组?事后看来,我原来的方法只是简单地等同于保留一个最大变量,但即便如此,我仍然很难看到这个问题的一个属性,它会引发这样的想法'嘿,我的 DP 数组中索引 i 处的条目的值应该是以 originalArray[i] 结尾的递增子序列。我正在努力看看我应该如何想出这个。

2.) 我提出的O(nlog(n)) 解决方案是否可行?我知道存在O(nlog(n)) 解决方案,但由于我无法让我的工作,我认为我需要朝着正确的方向轻推。

【问题讨论】:

    标签: c++ algorithm time-complexity dynamic-programming


    【解决方案1】:

    我承认,这是一个有趣的问题,我没有确切的答案,但我想我可以给你一个正确的方向。就这样吧:

    面对这样的困境,我通常会转向基础知识。就像你的情况一样,通过动态编程的定义。它有两个属性:

    1. 重叠子问题
    2. 最佳子结构。

    您可以轻松找到这些属性反映在标准解决方案中,但不是您的。您可以在 cormen 中了解它们,或者在 DP 的上下文中搜索它们。

    在我看来,您的解决方案不是 DP,您只是找到了一些模式,并且您正在尝试基于此模式解决。如果您没有得到解决方案,则意味着您的模式错误或您的解决方案忽略了某些东西。在这种情况下,尝试从数学上证明您观察到的模式是正确的,并证明解决方案也应该有效。

    再给我一些时间,虽然我正在研究您的解决方案,但同时您也可以尝试为您的解决方案开发证明。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-18
      • 2022-01-07
      • 2017-07-10
      • 1970-01-01
      • 2014-08-24
      • 2023-04-03
      • 2021-08-22
      相关资源
      最近更新 更多