进行多次子串反转的算法?

时间:2015-10-21 00:39:35

标签: string algorithm substring

假设我有一个长度为N的字符串S,我想执行以下M个操作:

  • 选择1< = L,R< = N并反转子串S [L..R]

我感兴趣的是在所有M操作之后最终的字符串是什么样的。显而易见的方法是进行实际交换,这会导致O(MN)最坏情况的行为。有更快的方法吗?我试图跟踪索引最终的位置,但我找不到减少运行时间的方法(虽然我有一种感觉O(M lg N + N) - 对于操作和最终阅读 - 是可能的。)

4 个答案:

答案 0 :(得分:4)

Splay树可以帮到你,它支持数组中的反向操作,总复杂度为O(mlogn)

答案 1 :(得分:3)

是的,这是可能的。制作像

这样的二叉树结构
struct node {
    struct node *child[2];
    struct node *parent;
    char label;
    bool subtree_flipped;
};

然后你可以为左/右孩子设置一个逻辑的getter / setter:

struct node *get_child(struct node *u, bool right) {
    return u->child[u->subtree_flipped ^ right];
}

void set_child(struct node *u, bool right, struct node *c) {
    u->child[u->subtree_flipped ^ right] = c;
    if (c != NULL) { c->parent = u; }
}

旋转必须保留翻转位:

struct node *detach(struct node *u, bool right) {
    struct node *c = get_child(u, right);
    if (c != NULL) { c->subtree_flipped ^= u->subtree_flipped; }
    return c;
}

void attach(struct node *u, bool right, struct node *c) {
    set_child(u, right, c);
    if (c != NULL) { c->subtree_flipped ^= u->subtree_flipped; }
}

// rotates one of |p|'s child up.
// does not fix up the pointer to |p|.
void rotate(struct node *p, bool right) {
    struct node *u = detach(p, right);
    struct node *c = detach(u, !right);
    attach(p, right, c);
    attach(u, !right, p);
}

使用轮播实施splay。它应该采取一个"警卫"为了展开而被视为NULL父级的指针,以便您可以将一个节点展开到根节点,将另一个节点展开到其右侧子节点。执行此操作然后您可以显示翻转区域的两个端点,然后切换根的翻转位和对应于不受影响的段的两个子树。

Traversal看起来像这样。

void traverse(struct node *u, bool flipped) {
    if (u == NULL) { return; }
    flipped ^= u->subtree_flipped;
    traverse(u->child[flipped], flipped);
    visit(u);
    traverse(u->child[!flipped], flipped);
}

答案 2 :(得分:2)

@F。 Ju是对的,splay树是实现目标的最佳数据结构之一。

但是,如果您不想实施它们,或者 O((N + M)* sqrt(M))的解决方案足够好,您可以执行以下操作:

我们将执行sqrt(M)个连续查询,然后在O(N)时间从头开始重建数组。

为了做到这一点,对于每个查询,我们将存储查询的段[a,b]反转或不反转的信息(如果两次反转某些元素范围,它们将变为未反转)。

此处的关键是在此处维护不相交细分的信息。请注意,由于我们在重建数组之前执行了大多数sqrt(M)查询,因此我们最多只有sqrt(M)个不相交的段,并且我们可以在sqrt(M)时间内对sqrt(M)段执行查询操作。如果您需要详细解释如何“反转”,请告诉我们。这些不相交的部分。

这个技巧在解决类似问题时非常有用,值得了解。

<强>更新

在他们的比赛中,我使用我描述的方法在HackerRank上解决了与你的问题完全对应的问题。

以下是problem

这是C ++中的my solution

以下是the discussion about the problem以及我的方法的简要说明,请在那里查看我的第3条消息。

答案 3 :(得分:1)

  

我正试图跟踪索引最终的位置

如果你只是想跟随起始数组的一个条目,那么很容易在O(M)时间内完成。

我打算只写伪代码,但是不需要挥手,所以我最终得到了可能有效的C ++。

// untested C++, but it does compile to code that looks right.
struct swap {
    int l, r;
    // or make these non-member functions for C
    bool covers(int pos) { return l <= pos && pos <= r; }
    int apply_if_covering(int pos) {
        // startpos - l = r - endpos;
        // endpos = l - startpos + r
        if(covers(pos))
            pos = l - pos + r;
        return pos;
    }
};

int follow_swaps (int pos, int len, struct swap swaps[], int num_swaps)
{
    // pos = starting position of the element we want to track
    // return value = where it will be after all the swaps
    for (int i = 0 ; i < num_swaps ; i++) {
        pos = swaps[i].apply_if_covering(pos);
    }
    return pos;
}

这会编译为very efficient-looking code