简体   繁体   English

如何找到最长约束的子序列

[英]How to find longest constrained subsequence

Given an array which contains N different integers, find the longest subsequence which satisfies: 给定一个包含N个不同整数的数组,找到满足以下条件的最长子序列:

  1. the start element of the subsequence is the smallest of the subsequence. 子序列的start元素是子序列中最小的。
  2. the end element of the subsequence is the largest of the subsequence. 子序列的结束元素是子序列中最大的元素。

Eg: 8,1,9,4,7. 例如:8,1,9,4,7。 The answer is 1,4,7. 答案是1,4,7。

2,6,5,4,9,8. 2,6,5,4,9,8。 The answer is 2,6,5,4,9 or 2,6,5,4,8. 答案是2,6,5,4,9或2,6,5,4,8。

Here is a O(N^2) algorithm: 这是一个O(N^2)算法:

  • Let X be the array of numbers. X是数字数组。
  • Iterate over X . 迭代X Suppose we are at index i . 假设我们在索引i Let Y be the array where Y[j] is the number of elements in (j, i] which are smaller than X[j]. Let z be the number of elements in [j, i] which are smaller than X[i]. If X[j] is smaller than X[i], we can get a subsequence of length zY[j] which satisfies the constrains. Y为其中Y [j]是(j, i]中小于X [j]的元素数的数组。令z[j, i]中小于X [i [j, i]的元素数如果X [j]小于X [i],我们可以得到满足约束的长度为zY [j]的子序列。
  • Set z to 1 . z设置为1 Loop j from i-1 down to 0 . 循环ji-1下降到0

    if X[j] < X[i]: z++; ans = max(ans, z - Y[j]); else Y[j]++;

Can we do better? 我们可以做得更好吗? I think there should be an O(NlogN) algorithm to find the max length. 我认为应该有一个O(NlogN)算法来找到最大长度。

Let me redo the explanation of this O(n log n) algorithm. 让我重做这个O(n log n)算法的解释。

Interpret the elements of the input sequence as points in 2D, where the x-coordinate is the index, and the y-coordinate is the value. 将输入序列的元素解释为2D中的点,其中x坐标是索引,y坐标是值。 We're looking for the rectangle containing the most input points, subject to the constraint that the lower left corner and the upper right corner be input points. 我们正在寻找包含最多输入点的矩形,受左下角和右上角为输入点的约束。 Under the usual component-wise partial order, the lower left corner of the optimum rectangle is minimal, and the upper right corner is maximal. 在通常的分量部分顺序下,最佳矩形的左下角是最小的,右上角是最大的。

Make two linear sweeps to find the minimal and maximal points. 进行两次线性扫描以找到最小和最大点。 Create an integer-valued segment tree keyed by the former, with operations that (i) accept an interval of keys and increment/decrement the associated values and that (ii) compute the maximum value. 创建由前者键控的整数值段树,其操作为(i)接受键的间隔并递增/递减相关值,并且(ii)计算最大值。 The algorithm is to iterate left to right through the maximal points, using the segment tree to track how many input points lie between (with respect to the partial order) each minimal point and the current maximal point. 该算法是通过最大点从左到右迭代,使用分段树来跟踪每个最小点和当前最大点之间(相对于部分顺序)有多少输入点。

Both minimal points and maximal points go down as we move left to right. 当我们从左向右移动时,最小点和最大点都会下降。 Suppose, then, that we're moving from a maximal point (x, y) to the next maximal point (x', y'). 那么假设我们正在从最大点(x,y)移动到下一个最大点(x',y')。 We have x < x' and y' < y. 我们有x <x'和y'<y。 How do the values in the segment tree change? 段树中的值如何变化? Since x < x', the points with x-coordinate in ]x, x'] do not belong to rectangles with upper right (x, y) but may belong to rectangles with upper right (x', y'). 由于x <x',x,x']中x坐标的点不属于右上角(x,y)的矩形,但可能属于右上角(x',y')的矩形。 Conversely, since y' < y, the points with y-coordinate in ]y', y] may belong to rectangles with upper right (x, y) but do not belong to rectangles with upper right (x', y'). 相反,由于y'<y,y坐标为y',y]的点可以属于右上角(x,y)的矩形,但不属于右上角(x',y')的矩形。 All other points are unaffected. 所有其他点都不受影响。

----+                   empty
    |
----+---------+ (x, y)
      removed |
--------------+-------+ (x', y')
              | added |
              |       +----+
              |       |    |

We go through the possibly affected points one by one, updating the segment tree. 我们逐个浏览可能受影响的点,更新段树。 The points are given sorted by x; 点数按x排序; if we make a copy and sort by y during initialization, then we can enumerate the possibly affected points efficiently. 如果我们在初始化期间制作副本并按y排序,那么我们可以有效地枚举可能受影响的点。 Note that, over time, the x-intervals are pairwise disjoint, as are the y-intervals, so we can afford to spend logarithmic time on each possibly affected point. 请注意,随着时间的推移,x区间是成对不相交的,y区间也是如此,因此我们可以在每个可能受影响的点上花费对数时间。 Given a point (x'', y'') such that x'' in ]x, x'] (note that y'' <= y' in this case), we need to increment the segment tree at the minimal points whose x-coordinate lies in ]inf, x''] and whose y-coordinate lies in ]inf, y'']. 给定一个点(x'',y'')使得x''in] x,x'](注意在这种情况下y''<= y'),我们需要在最小点处增加分段树其x坐标位于] inf,x''],其y坐标位于] inf,y'']。 This may not look one-dimensional, but in fact, the ordering on x-coordinates and ordering on y-coordinates are opposite for minimal points, so this set of keys is an interval. 这可能看起来不是一维的,但实际上,x坐标上的排序和y坐标上的排序对于最小点是相反的,因此这组键是间隔。 Similarly, given a point (x''', y''') such that y''' in ]y', y] (note that x''' <= x in this case), we need to decrement the values at an interval of keys. 类似地,给定一个点(x''',y''')y'''in y',y](注意x'''在这种情况下是<= x),我们需要递减这些值按键间隔。

Here's the "magic" segment tree data structure in Java. 这是Java中的“神奇”段树数据结构。

public class SegmentTree {
    private int n;
    private int m;
    private int[] deltaValue;
    private int[] deltaMax;

    private static int nextHighestPowerOfTwoMinusOne(int n) {
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return n;
    }

    public SegmentTree(int n) {
        this.n = n;
        m = nextHighestPowerOfTwoMinusOne(n) + 1;
        deltaValue = new int[m];
        deltaMax = new int[m];
    }

    private static int parent(int i) {
        int lob = i & -i;
        return (i | (lob << 1)) - lob;
    }

    private static int leftChild(int i) {
        int lob = i & -i;
        return i - (lob >>> 1);
    }

    private static int rightChild(int i) {
        int lob = i & -i;
        return i + (lob >>> 1);
    }

    public int get(int i) {
        if (i < 0 || i > n) {
            throw new IllegalArgumentException();
        }
        if (i == 0) {
            return 0;
        }
        int sum = 0;
        do {
            sum += deltaValue[i];
            i = parent(i);
        } while (i < m);
        return sum;
    }

    private int root() {
        return m >>> 1;
    }

    private int getMax(int i) {
        return deltaMax[i] + deltaValue[i];
    }

    public void addToSuffix(int i, int delta) {
        if (i < 1 || i > n + 1) {
            throw new IllegalArgumentException();
        }
        if (i == n + 1) {
            return;
        }
        int j = root();
        outer:
        while (true) {
            while (j < i) {
                int k = rightChild(j);
                if (k == j) {
                    break outer;
                }
                j = k;
            }
            deltaValue[j] += delta;
            do {
                int k = leftChild(j);
                if (k == j) {
                    break outer;
                }
                j = k;
            } while (j >= i);
            deltaValue[j] -= delta;
        }
        while (true) {
            j = parent(j);
            if (j >= m) {
                break;
            }
            deltaMax[j] =
                Math.max(0,
                         Math.max(getMax(leftChild(j)),
                                  getMax(rightChild(j))));
        }
    }

    public int maximum() {
        return getMax(root());
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM