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

LeetCode3045.统计前后缀下标对II

统计字符串数组中的前缀-后缀对 —— 利用字符串哈希优化

在处理字符串相关的问题时,哈希技术常常能够提供高效的解决方案。本文将介绍一个具体的应用场景:给定一个字符串数组,统计满足特定前缀和后缀条件的字符串对的数量。我们将深入解析这个问题,并通过代码示例展示如何利用字符串哈希技术实现高效的解决方案。

问题描述

给定一个下标从 0 开始的字符串数组 words,定义一个布尔函数 isPrefixAndSuffix,它接受两个字符串参数 str1str2

  • str1 同时是 str2 的前缀(prefix)和后缀(suffix)时,isPrefixAndSuffix(str1, str2) 返回 true
  • 否则,返回 false

例如:

  • isPrefixAndSuffix("aba", "ababa") 返回 true,因为 "aba" 既是 "ababa" 的前缀,也是 "ababa" 的后缀;
  • isPrefixAndSuffix("abc", "abcd") 返回 false

我们的目标是以整数形式返回满足 i < jisPrefixAndSuffix(words[i], words[j])true 的下标对 (i, j) 的数量。

示例

示例 1

输入: words = ["a","aba","ababa","aa"]
输出: 4
解释: 在本示例中,计数的下标对包括:

  • i = 0j = 1,因为 isPrefixAndSuffix("a", "aba")true
  • i = 0j = 2,因为 isPrefixAndSuffix("a", "ababa")true
  • i = 0j = 3,因为 isPrefixAndSuffix("a", "aa")true
  • i = 1j = 2,因为 isPrefixAndSuffix("aba", "ababa")true

因此,答案是 4

示例 2

输入: words = ["pa","papa","ma","mama"]
输出: 2
解释: 在本示例中,计数的下标对包括:

  • i = 0j = 1,因为 isPrefixAndSuffix("pa", "papa")true
  • i = 2j = 3,因为 isPrefixAndSuffix("ma", "mama")true

因此,答案是 2

示例 3

输入: words = ["abab","ab"]
输出: 0
解释: 在本示例中,唯一有效的下标对是 i = 0j = 1,但是 isPrefixAndSuffix("abab", "ab")false。因此,答案是 0

提示

  • 1 <= words.length <= 10^5
  • 1 <= words[i].length <= 10^5
  • words[i] 仅由小写英文字母组成。
  • 所有 words[i] 的长度之和不超过 5 * 10^5

解决思路

这道题目考察了字符串哈希的应用,特别是如何高效地统计满足前缀和后缀条件的字符串对。我们可以通过以下步骤来解决:

  1. 字符串哈希基础: 将每个字符串视为一个基于某个素数(如 P = 131)的多项式表达式,并对其进行模运算(如 MOD = 10^9 + 7)以计算哈希值。这样可以将字符串转换为一个唯一的数值表示,便于快速比较。
  2. 前缀和后缀哈希: 对于每个字符串,我们需要分别计算其所有可能的前缀和后缀的哈希值。前缀哈希可以通过从左到右累积计算,而后缀哈希则需要从右到左累积计算。
  3. 哈希值存储与计数: 使用一个哈希表(如 unordered_map)来存储已经计算过的前缀哈希值及其出现次数。当遍历到新的字符串时,检查其每一个前缀哈希值是否在哈希表中出现过,从而统计满足条件的字符串对。

关键代码解析

以下是实现上述思路的关键代码片段,特别是 leftright 哈希值的计算:

typedef long long LL;
class Solution {
public:

    long long h[100010];
    long long countPrefixSuffixPairs(vector<string>& words) {
        unordered_map<LL,LL> mp;
        int n = words.size(), P = 131, MOD = 1e9 + 7;
        h[0] = 1;
        long long res = 0;
        for(int i = 1; i <= 100000;i ++)h[i] = (h[i-1] * P) %MOD;
        for(int i = 0; i < n; i ++)
        {
            
            string s1 = words[i];
            int len = s1.size();
            LL left = 0, right = 0;
            for(int j = 0, k = len - 1;j < len; j ++ , k --)
            {
                left = ( (left * P) % MOD + s1[j] ) % MOD;
                right = ((s1[k] * h[j]) %MOD + right) % MOD;
                if(left == right && mp[left])
                {
                    res += mp[left];
                }
            }
            mp[left] = mp[left] + 1;

        }
        return res;
    }
};
left 的计算
left = ( (left * P) % MOD + s1[j] ) % MOD;
  • 目的: 计算当前字符串 s1 的前缀哈希值。
  • 过程:
    1. 初始化 left = 0
    2. 对于字符串中的每一个字符 s1[j],将当前的 left 乘以基数 P,然后加上当前字符的 ASCII 值。
    3. 对结果进行模运算,确保哈希值不会过大。

示例:
对于字符串 "abc"

  • j=0: left = ('a')
  • j=1: left = ('a' * 131 + 'b')
  • j=2: left = (('a' * 131 + 'b') * 131 + 'c')
right 的计算
right = ((s1[k] * h[j]) % MOD + right) % MOD;
  • 目的: 计算当前字符串 s1 的后缀哈希值。
  • 过程:
    1. 初始化 right = 0
    2. 从字符串的末尾开始,逐个字符向前遍历(k = len - 10)。
    3. 对于每一个字符 s1[k],将其乘以预计算的 h[j](即 P^j % MOD,其中 j 是当前字符在后缀中的位置),然后加上之前的 right
    4. 对结果进行模运算,确保哈希值不会过大。

示例:
对于字符串 "abc"

  • j=0: right = ('c' * 1 + 0) = 'c'
  • j=1: right = ('b' * 131 + 'c')
  • j=2: right = ('a' * 131^2 + 'b' * 131 + 'c')

为什么 right 的计算方式这样

  • 对称性: left 是从前向后累加哈希,而 right 是从后向前累加哈希。这样可以确保在任何时刻,前缀和后缀的哈希值能够对应起来进行比较。

  • 幂次的使用: h[j] = P^j % MOD 确保每个字符在不同位置的贡献是不同的,从而减少哈希冲突。

  • 模运算: 通过对 MOD 取模,确保哈希值不会因为字符串过长而溢出,同时也使得哈希值分布更均匀,减少冲突。

总结

通过利用字符串哈希技术,我们能够高效地计算并比较字符串的前缀和后缀,从而快速统计满足条件的字符串对的数量。关键在于如何巧妙地计算前缀和后缀的哈希值,并通过哈希表进行快速查找和计数。这种方法不仅适用于本题,实际上在许多需要快速字符串比较的问题中,都能发挥出巨大的作用。


http://www.kler.cn/a/461273.html

相关文章:

  • 全国城市经纬度--包括省会(直辖市)、地级市
  • 禁用div的写法(自定义disabled)Vue3
  • js的一些处理
  • C# 服务应用研究
  • log4j2的Strategy、log4j2的DefaultRolloverStrategy、删除过期文件
  • Flume的安装和使用
  • 003:如何理解 CNN 中的 RGB 图像和通道?
  • C++:单例模式
  • DevOps与自动化运维的深度结合实践
  • mybatis 和 mybatisPlus 兼容性问题
  • 探索SYNBO协议基于社区基金池的社区代理人模式——Alpha Broker
  • 破解 JVM 上的第三方 API
  • 如何在 Vue 2 中使用 Swiper 5.4.5 处理静态与后端数据不能切换问题
  • 【循环神经网络】RNN介绍
  • Linux命令复习
  • 逆袭之路(11)——python网络爬虫:原理、应用、风险与应对策略
  • Jupyter占用内存高问题排查解决
  • c#接口和抽象方法
  • 2025.01.15python商业数据分析
  • 从AI远见到中国速度:Scaling Law发现者为何引全球热议?
  • windows系统安装完Anaconda之后怎么激活自己的虚拟环境并打开jupyter
  • 区块链安全常见的攻击分析——Unprotected callback - ERC721 SafeMint reentrancy【8】
  • 鸿蒙开发:自定义一个车牌字母键盘
  • 混合并行训练框架性能对比
  • 未来20年在大语言模型相关研究方向--大语言模型的优化与改进
  • C语言优化技巧--达夫设备(Duff‘s Device)解析