当前位置: 首页 > news >正文

蓝桥杯刷题 Day2 AC自动机(二次加强版)

蓝桥杯刷题 Day2 AC自动机(二次加强版)


文章目录

  • 蓝桥杯刷题 Day2 AC自动机(二次加强版)
  • 前言
  • 完整代码
  • 一、AC自动机(二次加强版)
    • 1. 解题思路
    • 1.1 问题抽象:
    • 1.2 解题步骤
    • 2. 拆解代码
      • 2.1结构体
      • 2.2 输入
      • 2.3 Trie树的创建
      • 2.4 构建失败指针(BFS层级遍历)
    • 2.5 匹配文本串
    • 2.6 拓扑排序
    • 2.6 输出
    • 3. 题后收获
      • 3.1 知识点


前言

今天写牛客网模板题中的字符串模块


完整代码

import java.util.*;

// 定义结构体,用来构建树
class TrieNode{
    TrieNode[] children = new TrieNode[26]; // 子节点数组
    TrieNode fail; // 失败指针(类似于KMP中的next数组)
    int cnt = 0; // 统计子节点访问的次数
    List<Integer> ids = new ArrayList<>(); // 创建了一个动态数组,专门存储int类型,并通过list接口列表来引用
}

public class Main {
    public static void main(String[] args){
        // 输入
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); // 输入模式串个数
        String[] patterns = new String[n]; // 输入模式串
        for(int i = 0; i < n; i++){
            patterns[i] = scanner.next();
        }
        String s = scanner.next(); // 输入文本串

        // 构建Tire树
        TrieNode root = new TrieNode(); // 创建根节点
        List<TrieNode> nodesOrder = new ArrayList<>(); // 记录所有节点的处理顺序(用于后续拓扑排序)
        TrieNode[] endNodes = new TrieNode[n]; // 记录每个模式串的结束节点

        for(int i = 0; i < n; i++){
            String p = patterns[i]; // 记录每个模式串
            TrieNode curr = root; // 记录节点
            // 将p转换成字符数组并遍历
            for(char c:p.toCharArray()){
                int idx = c - 'a'; // 将字符映射成ASCII值,便于记录
                // 若子节点不存在则创建
                if(curr.children[idx] == null){
                    curr.children[idx] = new TrieNode();
                }
                curr = curr.children[idx];
            }
            curr.ids.add(i); // 将当前模式串索引添加到结束节点的ids列表中
            endNodes[i] = curr; // 保存结束节点?
        }

        // 构建失败指针(BFS层级遍历)

        /*
         * 使用队列管理Trie树节点的处理顺序
         * 队列用于按层或按广度优先顺序(BFS)处理节点
         * Queue是一种数据结构,遵循先进先出(FIFO)的插入顺序
         * */
        // 接口                      具体实现
        Queue<TrieNode> queue = new LinkedList<>();

        root.fail = null;
        // 初始化根节点的子节点
        for (int i = 0; i < 26; i++) {
            if(root.children[i] != null){
                root.children[i].fail = root; // 失败指针指向根
                queue.add(root.children[i]);
            }
        }

        // isEmpty判断为空,poll取出队列头部节点
        while(!queue.isEmpty()){
            TrieNode curr = queue.poll();
            nodesOrder.add(curr); // 记录节点处理顺序
            // 遍历**当前节点**的所有子节点
            for (int i = 0; i < 26; i++) {
                TrieNode child = curr.children[i];
                if(child != null){
                    TrieNode fail = curr.fail;
                    // 若fail存在但fail没有对应字符i的子节点,则沿着失败指针链向上回溯。
                    while(fail != null && fail.children[i] == null){
                        fail = fail.fail;
                    }
                    // 否则,指向fail的字符i子节点,即最长后缀的末尾节点。
                    child.fail = (fail == null) ? root : fail.children[i];
                    queue.add(child); // 子节点入队
                }
            }
        }

        // 匹配文本串,统计cnt
        TrieNode curr = root;
        for(char c:s.toCharArray()){
            int idx = c - 'a';
            while(curr != root && curr.children[idx] == null){
                curr = curr.fail;
            }
            if(curr.children[idx] != null){
                curr = curr.children[idx];
            }
            curr.cnt++;
        }

        // 拓扑排序优化(逆序累加)
        for (int i = nodesOrder.size() - 1; i >= 0 ; i--) {
            TrieNode node = nodesOrder.get(i);
            if(node.fail != null){
                // 将当前节点的cnt累加到失败指针节点的cnt中
                node.fail.cnt += node.cnt;
            }
        }
        
        // 输出
        for (int i = 0; i < n; i++) {
            System.out.println(endNodes[i].cnt);
            
        }
    }

}


一、AC自动机(二次加强版)

原题地址: AC自动机(二次加强版)

1. 解题思路

1.1 问题抽象:

在一篇文章(文本串)中查找多个单词(模式串)对应的位置
核心思想:Trie树和失败指针

1.2 解题步骤

  1. Tire树构建:一种树形结构,用来存放多个单词
  2. 失败指针:查找失败后回退
  3. 文本匹配与统计:用文本串s在Trie树上走
  4. 拓扑排序:全局统计,从底向上统计

2. 拆解代码

2.1结构体

// 定义结构体,用来构建树
class TrieNode{
    TrieNode[] children = new TrieNode[26]; // 子节点数组
    TrieNode fail; // 失败指针(类似于KMP中的next数组)
    int cnt = 0; // 统计子节点访问的次数
    List<Integer> ids = new ArrayList<>(); // 创建了一个动态数组,专门存储int类型,并通过list接口列表来引用
}

2.2 输入

 // 输入
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); // 输入模式串个数
        String[] patterns = new String[n]; // 输入模式串
        for(int i = 0; i < n; i++){
            patterns[i] = scanner.next();
        }
        String s = scanner.next(); // 输入文本串

2.3 Trie树的创建

  1. 将模式串分为根节点和子节点,为了输出模式串T在文本串S中出现的次数,需要记录模式串的结束结点和节点的处理顺序
 // 构建Tire树
        TrieNode root = new TrieNode(); // 创建根节点
        List<TrieNode> nodesOrder = new ArrayList<>(); // 记录所有节点的处理顺序(用于后续拓扑排序)
        TrieNode[] endNodes = new TrieNode[n]; // 记录每个模式串的结束节点

        for(int i = 0; i < n; i++){
            String p = patterns[i]; // 记录每个模式串
            TrieNode curr = root; // 记录节点
            // 将p转换成字符数组并遍历
            for(char c:p.toCharArray()){
                int idx = c - 'a'; // 将字符映射成ASCII值,便于记录
                // 若子节点不存在则创建
                if(curr.children[idx] == null){
                    curr.children[idx] = new TrieNode();
                }
                curr = curr.children[idx];
            }
            curr.ids.add(i); // 将当前模式串索引添加到结束节点的ids列表中
            endNodes[i] = curr; // 保存结束节点?
        }

2.4 构建失败指针(BFS层级遍历)

// 构建失败指针(BFS层级遍历)

        /*
         * 使用队列管理Trie树节点的处理顺序
         * 队列用于按层或按广度优先顺序(BFS)处理节点
         * Queue是一种数据结构,遵循先进先出(FIFO)的插入顺序
         * */
        // 接口                      具体实现
        Queue<TrieNode> queue = new LinkedList<>();

        root.fail = null;
        // 初始化根节点的子节点
        for (int i = 0; i < 26; i++) {
            if(root.children[i] != null){
                root.children[i].fail = root; // 失败指针指向根
                queue.add(root.children[i]);
            }
        }

        // isEmpty判断为空,poll取出队列头部节点
        while(!queue.isEmpty()){
            TrieNode curr = queue.poll();
            nodesOrder.add(curr); // 记录节点处理顺序
            // 遍历**当前节点**的所有子节点
            for (int i = 0; i < 26; i++) {
                TrieNode child = curr.children[i];
                if(child != null){
                    TrieNode fail = curr.fail;
                    // 若fail存在但fail没有对应字符i的子节点,则沿着失败指针链向上回溯。
                    while(fail != null && fail.children[i] == null){
                        fail = fail.fail;
                    }
                    // 否则,指向fail的字符i子节点,即最长后缀的末尾节点。
                    child.fail = (fail == null) ? root : fail.children[i];
                    queue.add(child); // 子节点入队
                }
            }
        }

2.5 匹配文本串

// 匹配文本串,统计cnt
        TrieNode curr = root;
        for(char c:s.toCharArray()){
            int idx = c - 'a';
            while(curr != root && curr.children[idx] == null){
                curr = curr.fail;
            }
            if(curr.children[idx] != null){
                curr = curr.children[idx];
            }
            curr.cnt++;
        }

2.6 拓扑排序

  // 拓扑排序优化(逆序累加)
        for (int i = nodesOrder.size() - 1; i >= 0 ; i--) {
            TrieNode node = nodesOrder.get(i);
            if(node.fail != null){
                // 将当前节点的cnt累加到失败指针节点的cnt中
                node.fail.cnt += node.cnt;
            }
        }

2.6 输出

        // 输出
        for (int i = 0; i < n; i++) {
            System.out.println(endNodes[i].cnt);
            
        }

3. 题后收获

3.1 知识点

  1. 创建一个列表,用于按顺序存储指定类型的对象:List nodesOrder = new ArrayList<>();
  2. 将p转换成字符数组并遍历:for(char c:p.toCharArray())
  3. 将当前模式串索引添加到结束节点的ids列表中:curr.ids.add(i)
  4. 将字符映射成ASCII值,便于记录:int idx = c - ‘a’
  5. 用于队列的数据结构:Queue queue = new LinkedList<>()
  6. isEmpty()判断为空,poll()取出队列头部节点

相关文章:

  • linux 命令 vim
  • 若依前端框架增删改查
  • c++领域展开第十七幕——STL(vector容器的模拟实现以及迭代器失效问题)超详细!!!!
  • 三个线程按顺序交替打印 A B C
  • Power Apps 技术分享:使用控件的相对布局
  • 组态王Kingview配置为OPCUA服务器的一些问题处理
  • [快乐学坊management_1] With Cursor | Mysql设计 | 服务接口设计与开发
  • PyTorch 深度学习实战(17):Asynchronous Advantage Actor-Critic (A3C) 算法与并行训练
  • ABeam 德硕 | 在华外企ESG议题选择指南(5)—— 国际与国内ESG议题选择研究:SASB可持续会计准则解读
  • Unity 云渲染本地部署方案
  • 检查 YAML 文件格式是否正确的命令 yamllint
  • Vala编程语言教程-数据类型
  • 【iOS】SwiftUI 路由管理(NavigationStack)
  • 深入理解 Linux 的 top 命令:实时监控系统性能
  • Unity 解决TMP_Text 文字显示异常的问题
  • 手势调控屏幕亮度:Python + OpenCV + Mediapipe 打造智能交互体验
  • 记事本(基于JAVAGUI界面)
  • 一次模拟Windows挖矿病毒应急响应的流程及思路
  • Linux系统管理与编程05:网络管理番外篇
  • 一篇最全Python 爬虫超详细讲解(零基础入门,适合小白)
  • 南方降水频繁暴雨连连,北方高温再起或现40°C酷热天气
  • 习近平复信中国丹麦商会负责人
  • 【社论】公平有序竞争,外卖行业才能多赢
  • 加强战略矿产出口全链条管控将重点开展哪些工作?商务部答问
  • 古巴外长谴责美国再次将古列为“反恐行动不合作国家”
  • 特朗普开启第二任期首次外访:中东行主打做生意,不去以色列