【问题标题】:Data structure for dynamically changing n-length sequence with longest subsequence length query具有最长子序列长度查询的动态改变n长度序列的数据结构
【发布时间】:2016-01-09 22:20:36
【问题描述】:

我需要设计一个数据结构来保存n-length 序列,方法如下:

  • increasing() - 返回最长递增子序列的长度
  • change(i, x) - 将 x 添加到序列的第 i 个元素

直观地说,这听起来像是可以用某种区间树来解决的。但我不知道怎么想。

我想知道如何使用这个事实,我们完全不需要知道这个子序列的样子,我们只需要它的长度......

也许这是可以使用的东西,但我几乎被困在这一点上。

【问题讨论】:

  • 您的意思是连续子序列吗?
  • 不,我的意思是一般的子序列,我认为连续的情况要简单得多。
  • 我假设了这么多,但我想在尝试对 valdem 的回答做出正面或反面之前确定一下
  • 所以如果我理解正确的话,它应该是可变数量的可变长度序列。这个数据结构中的每个序列可以是任何顺序。 increasing() 返回最长的递增子序列,它可能是整个序列,也可能是其中的一部分。 'change(i,x)' 追加 (?) xith 子序列?那么,是否存在恒定数量的序列?或者是否有其他操作来创建一个新的空序列?
  • 或者..再读一遍,数据结构保存了一个single可变长度序列和change(i,x) inserts x@987654329 @th 在单个序列中的位置?你打算采取哪种行为?

标签: algorithm data-structures


【解决方案1】:

这仅解决了连续间隔的问题。它不能解决任意子序列。 :-(

可以使用时间O(1) 来实现此功能,O(log(n)) 用于change

首先,我们需要一个用于所有当前间隔的堆,其中最大的在顶部。找到最长的间隔只是看堆顶的问题。

接下来,我们需要为每个 n 插槽提供大量信息。

value: Current value in this slot
interval_start: Where the interval containing this point starts
interval_end: Where the interval containing this point ends
heap_index: Where to find this interval in the heap NOTE: Heap operations MUST maintain this!

现在是聪明的把戏!我们始终存储每个插槽的值。但我们将区间信息存储在区间中其索引可被 2 的最高幂整除的点处。任何区间始终只有一个这样的点,因此存储/修改此点工作量很小。

然后,要确定数组中给定位置当前所处的区间,我们必须查看所有以 2 的幂次方递增的邻居,直到找到具有我们值的最后一个。因此,例如,位置13 的信息可能会在任何位置0, 8, 12, 13, 14, 16, 32, 64, .... 中找到(我们将在列表0, ..., 64, 32, 16, 8, 12, 14, 13 中找到它的第一个区间。) O(log(n)) 列表,O(log(n)) 工作。

现在我们如何实现change

  1. 更新值。
  2. 找出我们所处的区间,以及我们是否处于区间边界。
  3. 如果间隔已更改,请从堆中删除旧的。 (我们可能会删除 0、1 或 2)
  4. 如果间隔发生变化,请将新的间隔插入堆中。 (我们可以插入 0、1 或 2)

该更新非常复杂,但它是固定数量的O(log(n)) 操作,因此应该是O(log(n))

【讨论】:

  • 我认为这是在寻找 contiguous 子序列,就像 valdem 的答案,但与问题不同。
【解决方案2】:

我试图解释我的想法。它可能比实现区间树要简单一些,并且应该给出理想的复杂度 - 增加()的 O(1) 和 change() 的 O(logS),其中 S 是序列计数(在最坏的情况下可以减少到 N当然)。

首先你需要原始数组。它需要在 change() 之后检查区间的边界(我将使用单词区间作为序列的同义词)。让它成为A

第二个你需要一个双向的区间列表。此列表的元素应存储左右边框。每个递增的序列都应该作为这个列表的单独元素呈现,并且这个间隔应该一个接一个,因为它们在 A 中呈现。将此列表设为L。我们需要在元素上操作指针,所以,我不知道是否可以在标准容器的迭代器上这样做。

第三个你需要优先队列来存储数组中所有间隔的长度。因此,increasing() 函数可以用 O(1) 时间完成。但是您还需要将指向节点的指针从 L 存储到查找间隔。让这个优先级队列为PQ。更正式地说,您的优先级队列包含对(间隔长度,指向列表节点的指针),仅按长度进行比较。

你需要树,它可以检索特定元素的间隔边界(或范围)。它可以用 std::map 简单地实现,其中 key 是树的左边界,所以在 map::lower_bound 的帮助下,你可以找到这个间隔。值应该在 L 中存储指向区间的指针。让这张地图成为MP

还有下一件重要的事情——列表节点应该在优先级队列中存储相应元素的索引。并且您不应该使用优先级队列而没有从 L 链接到节点的链接(PQ 上的每个交换操作都应该更新 L 上的相应指标>)。

change(i, x) 操作可以是这样的:

  1. 用地图查找我所在的区间。 -> 你在 L 中找到指向相应节点的指针。所以,你知道边界和间隔长度
  2. 尝试了解需要执行的操作:无、分割区间、粘合区间。
  3. 在与 PQ 连接的列表和地图上执行此操作。如果需要拆分区间,请将其从 PQ 中移除(这不是 remove-max 操作),然后在 PQ 中添加 2 个新元素。类似的,如果你需要粘合间隔,你可以从 PQ 中删除一个,然后增加键到第二个。

一个困难是PQ应该支持删除任意元素(按索引),所以你不能使用std::priority_queue,但我认为实现起来并不难。

【讨论】:

  • 如果我理解正确,这个答案是针对 contiguous 子序列的,这是一个更容易的问题。
  • 是的,我试图解析那个答案,它看起来像它。 “第三个你需要优先级队列来存储数组中所有间隔的长度。所以,increasing() 函数可以用 O(1) 时间完成。”你是什么意思?据我了解,间隔代表序列(如您之前所写),因此在优先级队列之上,将有最长序列的长度,这不是这个问题的意义所在。或者也许 O(1) 你的意思不是 ExtractMax() 操作,如果我错了,请纠正我。
  • 是的,很抱歉回答不清楚。我解决了连续子序列问题,可能是因为我不清楚问题。 O(1) 我的意思是 TakeMax() 因为我们不想提取 Max(从队列中删除元素)
【解决方案3】:

LIS 可以用树来解决,但是还有另一种动态规划的实现,它比递归树更快。 这是 C++ 中的一个简单实现。

class LIS {
    private vector<int> seq ;
    public LIS(vector<int> _seq) {seq = _seq ;}
    public int increasing() {
        int i, j ;
        vector<int> lengths ;
        lengths.resize(seq.size()) ;
        for(i=0;i<seq.size();i++) lengths[i] = 1 ;

        for(i=1;i<seq.size();i++) {
            for(j=0;j<i;j++) {
                if( seq[i] > seq[j] && lengths[i] < lengths[j]+1 ) {
                    lengths[i] = lengths[j] + 1 ;
                }
            }
        }

        int mxx = 0 ;
        for(i=0;i<seq.size();i++)
            mxx = mxx < lengths[i] ? lengths[i] : mxx ;

        return mxx ;
    }

    public void change(i, x) {
        seq[i] += x ;
    }

}

【讨论】:

  • 这看起来不是最有效的解决方案。在理想的解决方案中,当您要求 increasing() 时,您不会每次都从头开始解决问题...
  • 相反,这些信息应该以某种方式轻松获得,使用 change() 每次调用时都会做的非平凡工作。
  • @qiubit 由于你没有指定预处理时间、更新时间和查询时间的约束,现在说这个算法太慢有点不公平。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-14
  • 1970-01-01
  • 1970-01-01
  • 2018-02-19
  • 2012-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多