• LeetCode / Scala - 无重复字符最长子串 ,最长回文子串


    一.引言

    LeetCode 里有一类字符子串问题,这里主要分析无重复字符的最长子串最长回文子串,总结相关方法。

    二.无重复字符最长子串

    1.题目要求

     给定字符串 s,要求找出字符内无重复的最长子串,注意是子串不是子序列。

    2.滑动指针法

    A.思路分析

    通过 start 与 end 双指针指定最长子串范围,通过 HashMap 记录每一个 char 字符的索引并实现去重。

    -> 遍历字符串所有 char 字符

    -> 判断当前字符是否在 HashMap 中,并更新当前 start

    -> end += 1,并记录当前 char 对应的索引

    -> length = end - start,比较 length 与 maxLength 并更新最大长度

    B.代码实现

    1. def solution(s: String): Int = {
    2. // 双指针与索引Map
    3. var maxLength = 0
    4. var start = 0
    5. var end = 0
    6. val indexMap = mutable.HashMap[Char, Int]()
    7. // 转换为char
    8. val arr = s.toCharArray
    9. s.toCharArray.foreach(char => {
    10. if (indexMap.contains(char)) {
    11. start = math.max(indexMap(char), start) // 更新 start
    12. }
    13. end += 1 // 更新 end
    14. indexMap(char) = end
    15. maxLength = math.max(end - start, maxLength) // 更新 maxLength
    16. println(s"char: $char start: $start end: $end max: $maxLength arr: ${s.slice(start, end).mkString("")}")
    17. })
    18. maxLength
    19. }

    为了直观,我们加入了日志打印,以 s = "abba" 为例进行测试:

    1. def main(args: Array[String]): Unit = {
    2. val s = "abba"
    3. println(solution(s))
    4. }

    可以结合执行过程理解双指针 start 和 end 的变化。 

    C.执行效率

    时间复杂度 O(n),其中 n 为字符串的长度,即 char 的数量,空间复杂度为 O(|∑|),其中 ∑ 为当前字符串的去重集合,正常情况可以近似为 O(n),极端情况例如 s="11111" 则退化为 O(1)。

    三.最长回文子串

    1.题目要求

    给定字符串 s,寻找其最长回文子串,这里解释下什么是回文串,即正反一样的字符串,例如 aabbaa,aba,ccccc 均为回文子串,注意最长无重复子串返回字符串长度而本例返回字符串。

    2.中心扩展法

    A.思路分析

    首先清晰回文字符的转移条件,假设 P(i,j) 为回文子串,如果 i-1,j+1 对应位置元素相同,则 P(i-1, j+1) 也是回文子串,如此类推

    -> 初始化 maxLeft,maxRight 记录最长回文串的区间,初始化 maxLength 记录最长长度

    -> 遍历数组数组索引,向左侧探索,当前索引为 mid,则左侧为 mid - 1,如果 s(mid) = s(left) 则 P(left, mid) 构成回文子串,left -=1 继续向左探索 

    -> 向右侧探索,方法同上,right = mid + 1,判断 s(mid) 与 s(right)

    -> 向两侧扩展,单向扩展主要寻找与 mid 元素相同的元素,例如 a -> aaa,双侧则主要是寻找两侧相同元素,例如 aaa -> baaab,判断 s(left) = s(right)

    -> 更新 maxLeft,maxRight 记录当前最长回文串范围

    B.代码实现

    1. // 中心扩展算法
    2. def solution(s: String): String = {
    3. var length = 1
    4. // 从每个 mid 开始向两边扩散
    5. var maxLeft = 0 // 起点
    6. var maxRight = 0 // 终点
    7. var maxLength = 0 // 最长回文串长度
    8. val arr = s.toCharArray
    9. arr.indices.foreach(mid => {
    10. var left = mid - 1
    11. var right = mid + 1
    12. // 向左侧扩展
    13. while (left >= 0 && arr(left).equals(arr(mid))) {
    14. left -= 1
    15. length += 1
    16. }
    17. // 向右侧扩展
    18. while (right <= arr.length - 1 && arr(right) == arr(mid)) {
    19. right += 1
    20. length += 1
    21. }
    22. // 向两侧扩展
    23. while (left >= 0 && right <= arr.length - 1 && arr(left) == arr(right)) {
    24. left -= 1
    25. right += 1
    26. length += 2
    27. }
    28. // 更新
    29. if (length > maxLength) {
    30. maxLeft = left
    31. maxRight = right
    32. maxLength = length
    33. }
    34. length = 1
    35. })
    36. println(maxLeft, maxRight, maxLength)
    37. s.substring(maxLeft + 1, maxRight)
    38. }

    分别向左,右,两侧扩展,最后更新区间,可以参照上面思路分析,由于判断相等后,继续 left -= 1 寻找下一个 left,所以最后满足的左边界为 realLeft = maxLeft + 1,maxRight 同理,maxRight = realRight + 1,所以最终结果 subString(maxLeft + 1,maxRight)。

    C.执行效率

    欧了一把,不知道这次提交怎么跑这么快,算法时间复杂度 O(n^2),因为遍历 n 个字符,每个字符左右扩展,空间复杂度 O(1)。

    四.总结

    可以看到很多算法都使用了 双指针、HashMap,因此要理解它们常用的用法与边界条件,其次回文串由于 P(i,j) 向 P(i-1,j+1) 的转移状态,也适用于动态规划,后面有时间整理一下动态规划相关。

  • 相关阅读:
    CSS的伪类选择器:nth-child()
    性能问题从发现到优化一般思路
    python中装饰器的使用教程详解
    uniapp开发h5,解决项目启动时,Network: unavailable问题
    MFC Windows 程序设计[131]之控件组合集
    每日练习------有n个整数,使其前面各数顺序向后移m个位置,最后m个数变成最前面的m个数
    AttributeError: ‘Conv‘ object has no attribute ‘fuseforward‘
    智能指针 之 unique_ptr shared_ptr weak_ptr
    window mysql-8.0.34 zip解压包安装
    R语言ggplot2可视化:使用ggpubr包的ggbarplot函数可视化水平柱状图(条形图)、使用orientation参数设置柱状图转置为条形图
  • 原文地址:https://blog.csdn.net/BIT_666/article/details/126047572