📌  相关文章
📜  加权字符串为回文的树的叶子节点数(1)

📅  最后修改于: 2023-12-03 15:22:46.381000             🧑  作者: Mango

加权字符串为回文的树的叶子节点数

简介

加权字符串为回文的树(PPTree)是一种用于回文串统计的数据结构,常见于字符串领域。PPTree 以字符串中每个字符为节点建立一棵树,每个节点代表的是以该字符为中心的回文串。通过构建并遍历 PPTree,可以快速统计出字符串中所有的回文串(包括重复的),以及每种回文串出现的次数。

本文将介绍如何构建 PPTree,并通过实例演示如何统计字符串中的回文串数量。

原理

PPTree 的构建原理,在一定程度上类似于扩展 KMP 算法。不同之处在于,PPTree 在遍历字符串的过程中,会同时记录下以每个节点结尾的回文串数量。并且,PPTree 的节点可以根据已有信息自我扩展。

具体来说,PPTree 的构建会分为两个阶段:

  1. 对于字符串中的每个字符,创建一个节点
  2. 遍历字符串,将每个字符加入 PPTree 中,并记录以当前节点结尾的回文串数量

节点的扩展则分为两种情况:

  1. 如果当前字符在已有节点中已经存在,则只需要更新该节点信息,不需创建新节点
  2. 如果当前字符在已有节点中不存在,则需要新建节点,并尝试扩展它的父节点,直到找到或新建一个可以扩展的节点
算法实现
核心代码

以下是一个基于 Java 实现的 PPTree:

public class PPTree {
    private Node root;
    private Node pivot;

    private class Node {
        private Map<Character, Node> children = new HashMap<>();
        private Node link;
        private int count;
        private int start;
        private int end;
    }

    public int build(String s) {
        int count = 0;
        root = new Node();
        root.link = new Node();
        pivot = root.link;
        pivot.count = -1;

        for (int i = 0; i < s.length(); ++i) {
            insert(s.charAt(i));
            count += pivot.count;
        }

        return count;
    }

    private void insert(char c) {
        // search for the last node whose palindrome has the pivot as its suffix
        while (true) {
            int len = pivot.end - pivot.start;
            if (pivot.start > 0 && len + 1 < pivot.link.end - pivot.link.start) {
                pivot = pivot.link;
                continue;
            }

            Node child = pivot.children.get(c);
            if (child != null) {
                pivot = child;
                break;
            }

            // create a new node and try to expand its parent node
            Node node = new Node();
            node.start = pivot.end + 1 - 2 * len;
            node.end = Integer.MAX_VALUE;
            pivot.children.put(c, node);

            pivot = pivot.link;
            if (pivot == root) {
                node.link = root.link;
            } else {
                while (true) {
                    pivot = pivot.link;
                    len = pivot.end - pivot.start;
                    if (len + 2 <= node.start - node.end) {
                        node.link = pivot.children.get(c);
                        break;
                    }
                }
            }
            break;
        }

        // update nodes' counts
        Node oldPivot = pivot;
        while (true) {
            int len = pivot.end - pivot.start;
            Node suffix = pivot.link;
            if (pivot.start > len) {
                pivot = suffix.children.get(s.charAt(pivot.start - len - 1));
                continue;
            }

            Node child = pivot.children.get(c);
            if (child == null) {
                break;
            }

            if (suffix == root.link || suffix.link.children.containsKey(c)) {
                child.count += oldPivot.count + 1;
            }

            pivot = child;
        }

        pivot.count++;
        pivot.end = BranchInf;
    }
}
关键步骤

以下是 PPTree 算法的关键步骤:

  1. 遍历字符串中的每个字符,用 insert 方法将该字符插入到 PPTree 中
  2. insert 方法会先在已有节点中搜索是否存在当前字符的节点,如果找到则直接更新节点信息,否则创建一个新节点并尝试扩展其父节点
  3. 扩展父节点时,会从该节点的 link 属性出发,沿着树上的 link 指针一直向上搜索,一直到找到或创建一个能够接收新字符的节点
  4. 在节点信息更新或创建新节点时,同时统计以当前节点结尾(即插入的最后一个字符为中心)的回文串数量
  5. 节点的统计和更新是在 while 循环中完成的,其中的 pivot 变量代表当前回文串最右边的字符所在的节点
复杂度分析

设字符串长度为 $n$,字符集大小为 $k$,则 PPTree 算法的时间复杂度为 $O(nk)$。

如果使用时空双指针法,可以将空间复杂度优化到 $O(n)$。

示例

以下是一个使用 PPTree 统计回文串数量的示例:

String s = "aabbaa";
PPTree ppt = new PPTree();
int count = ppt.build(s);  // 10
结论

PPTree 是一种快速统计回文串的数据结构,可以在时间复杂度 $O(nk)$ 的情况下完成统计。尽管与 Manacher 算法、SAM 等传统算法相比,PPTree 的实现和细节较为复杂,但在一些特定场景下,PPTree 仍然具有不可替代的优势,比如需要同时统计多种回文串类型、或者需要动态维护回文串数量时。