【数据结构与算法】力扣 459. 重复的子字符串

题目描述

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例 1:

复制代码
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。

示例 2:

复制代码
输入: s = "aba"
输出: false

示例 3:

复制代码
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)

提示:

  • 1 <= s.length <= 104
  • s 由小写英文字母组成

分析解答

重复的子串连接成父串?重复?repeat!重复的次数?父串的长度是子串的几倍就是重复几次!

js 复制代码
/**
 * @param {string} s
 * @return {boolean}
 */
var repeatedSubstringPattern = function (s) {
    let subStr = ''
    for (let i = 0; i < s.length - 1; i++) {
        subStr += s[i]
        if (s === subStr.repeat(Math.floor(s.length / subStr.length))) {
            return true
        }
    }
    return false
};

思路拓展

移动匹配

既然是由重复的子串构成的,那么该字符串的首尾子串一定是相等的。所以:首 + 中间部分 + 尾 => 原字符串,那么,以此类推,尾 + 中间部分 + 首 => 原字符串。

所以我们的思路是:将两个字符串拼起来,找这个二倍的字符串是否含有原字符串(去掉首尾,防止两个原字符串的干扰)。

js 复制代码
var repeatedSubstringPattern = function (s) {
    let str = s + s
    str = str.slice(1, str.length - 1)
    if (str.includes(s)) {
        return true
    }
    return false
};

KMP

next 数组是一个辅助数组,用来记录每个位置之前的最长相同前缀后缀的长度。通过计算next数组,就能得到字符串中最长的重复子串的长度。

主要逻辑如下:

  • 首先进行一些边界判断,如果字符串的长度为0,则返回false。
  • 然后定义了一个名为getNext的函数,用来计算next数组。这个函数通过使用双指针遍历字符串,根据不同的情况更新指针及next数组的值。具体来说:
    • 初始化一个next数组和一个指针j。
    • 将j的初始值0存入next数组中。
    • 从1到字符串的长度-1进行遍历,将指针j根据不同情况进行更新,并将更新后的j的值存入next数组中。
    • 最后返回next数组。
  • 接下来,调用getNext函数得到next数组。
  • 最后通过判断条件(最后位置的最长相同前后缀不能为 0 ;而且整个字符串的长度一定是最长相同前后缀的整数倍,也就是模为 0)来比较字符串长度是否是最长重复子串长度的倍数,如果是则返回true,否则返回false。

这段代码主要运用了KMP算法中的next数组计算来判断字符串是否由重复的子串组成。

js 复制代码
/**
 * @param {string} s
 * @return {boolean}
 */
var repeatedSubstringPattern = function (s) {
    if (s.length === 0) {
        return false
    }
    const getNext = (s) => {
        let next = []
        let j = 0
        next.push(j)
        for (let i = 1; i < s.length; i++) {
            while (j > 0 && s[i] !== s[j]) {
                j = next[j - 1]
            }
            if (s[i] === s[j]) {
                j++
            }
            next.push(j)
        }
        return next
    }
    let next = getNext(s)
    if (next[next.length - 1] !== 0 && s.length % (s.length - next[next.length - 1]) === 0) {
        return true
    }
    return false
};
let s = "aba"
console.log(repeatedSubstringPattern(s))

详细讲讲 getNext 这个方法:

当进行字符串匹配时,我们需要找到一个最长的相同前缀后缀。这个相同前缀后缀的长度决定了我们在匹配失败时应该回退多少位。

getNext方法就是用来计算这个最长的相同前缀后缀的长度。它使用了双指针的方法来遍历字符串s,并根据不同的情况更新指针和next数组的值。

具体的实现逻辑如下:

  1. 首先,我们先定义一个数组next用来存储最长相同前缀后缀的长度。
  2. 接着,我们定义两个指针i和j,初始值都为0。指针i表示当前要计算最长相同前缀后缀的后缀的末尾位置,指针j表示当前已经求得的最长相同前缀后缀的长度。
  3. 先将指针j的初始值0存入next数组的第一个位置。
  4. 从字符串s的第二个字符开始,即i从1到s.length-1,进行遍历。
  5. 在遍历过程中,我们通过比较si和sj的值,来更新指针j和next数组的值。具体的操作如下:
    • 如果si和sj相等,说明当前的字符可以加入到已知的最长相同前缀后缀中,所以我们将指针j加1,并将j的值存入next数组的当前位置。
    • 否则,我们需要寻找更短的相同前缀后缀,所以我们将指针j回退到前一个位置,通过nextj-1来获取更短的相同前缀后缀的长度。我们将比较si和sj-1的值,直到找到一个相等的字符或者j回退到0为止。
      • 如果j回退到0仍然没有找到相等的字符,说明当前位置不存在相同的前缀后缀,所以将指针i加1,并将j的值0存入next数组的当前位置。
      • 如果找到了一个相等的字符si和sj-1,说明我们找到了一个更短的相同前缀后缀,所以我们将指针j加1,并将j的值存入next数组的当前位置。
  6. 遍历结束后,我们可以得到一个完整的next数组,其中的每个值都代表了相应位置的最长相同前缀后缀的长度。

通过getNext方法计算得到的next数组,可以用于优化字符串匹配算法,如KMP算法等。在本段代码中,我们利用next数组的最后一个值,来判断字符串是否是由重复的子串组成的。

相关推荐
星期八不上发条5 小时前
智能指针是什么?使用场景,循环引用解决办法,面试回答
开发语言·c++·stl
OKkankan5 小时前
Python 基础进阶(三):从函数、类到 asyncio 与 FastAPI 后端开发实战
开发语言·python
泡海椒6 小时前
jquick-pdf 防止 PDF 内容分页断裂:keepTogether 属性妙用
java·开发语言·pdf
wcy03106 小时前
POST为什么会发送两次请求
javascript·ajax
志尊宝6 小时前
Vue3 零基础每日笔记(047):动态路由与路由参数——:id 传参、query 传参、props 解耦
前端·javascript·vue.js·笔记·html5
传奇开心果编程7 小时前
【Rust入门练中学】 第1课:从零开始
开发语言·学习·rust
Brilliantwxx7 小时前
【STM32】 SPI阻塞式通信与HAL源码(串口读ID实战)
开发语言·stm32·单片机·嵌入式硬件·ecmascript
吠品8 小时前
Nginx负载均衡配置与线上故障排查的一些经验
java·开发语言·数据库
边境悍匪8 小时前
蜗牛学苑 Java 智能体学习 Day44|Vue 前端项目搭建 思维导图复盘
java·开发语言·spring boot·学习·阿里云