代码随想录算法训练营第九天| 28. 找出字符串中第一个匹配项的下标、459. 重复的子字符串

[LeetCode] 28. 找出字符串中第一个匹配项的下标

[LeetCode] 28. 找出字符串中第一个匹配项的下标 文章解释

[LeetCode] 28. 找出字符串中第一个匹配项的下标 视频解释

题目:

给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

  • 1 <= haystack.length, needle.length <= 104
  • haystackneedle 仅由小写英文字符组成

自己看到题目的第一想法

    1. 双层 for 循环, 第一层指针从长字符串的左边往右遍历, 找到和待匹配字符串第一个字符相同的位置, 从这个位置开始往右循环, 如果发现接下来的连续字符串, 和待匹配的字符串一致, 则认为存在, 否则第一层指针下移一位.

    2. leetcode 绝大概率不会让人写这样的暴力算法, 所以一定有其他的解决办法. 可以考虑双指针吗? 可不可以这样呢: 当第二层循环遇到相异的字符串时, 可以从第一层循环的当前索引的下一位 (i + 1) 开始, 找到第一个和待匹配字符串第一个字符相同的位置, 这时第一层的索引 i 也跟新到对应的位置. 但是这样好像和双指针的想法并无关联, 因为右侧的指针实际上并没有意义, 除非能判断i到右侧指针之间的字符串, 和待匹配字符串的头几位是相同的. 然而怎么判断呢?

    3. 好吧, 第一次看到题目之前我其实没有第 2 点那么多的想法. 因为文章解释里说了, 这是 kmp 算法, kmp 很难, 别想一次搞懂. 先看视频和文章, 知道个大概, 二刷时再来真正掌握吧!

看完代码随想录之后的想法

    1. 真的挺难的, 很多细节一直搞不明白

    2. 我理解的 kmp 算法:

        如果长的字符串中, 存在 n 个连续的字符串, 和待匹配的字符串的头几位相同. 那么当长字符串中这 n 个连续的字符串后一位和待匹配的字符串的下一位不匹配, 例如长字符串头几位为 aabaabaaac 而待匹配字符串为 aabaaac, 当待匹配到待匹配字符串的c(aabaab=>aabaaa)时, 这时候字符串 c 前面的 aabaa 还是相同的. 于是要找到 aabaa 的从尾字符a开始往左以及从头字符a开始往右的连续字符串中, 完全相同的字符串里, 最长的那一串(假设长度为 n). 找到这个串之后, 表示长字符串中当前索引的前 n 位, 和待匹配字符串从头开始的前 n 位是一样的. 因此只需要将长字符串当前索引的字符串, 和待匹配字符串从头开始的第 n + 1 位匹配就可以. 对于 aabaab =》 aabaaa 这样在最后一位 b 和 a 不相同的情况时, 就跳过了 aabaab 中的 第一个 ab 的位置. 提高了匹配的效率.

    3. kmp 算法还是挺绕的, 没打算想的太明白, 因为是在耗费了我太多时间和精力了T_T

// 解法一: leetcode 的 1 ms 区
class Solution {
    public int strStr(String haystack, String needle) {
        if (haystack == null || needle == null 
            || haystack == "" || needle == ""
            || haystack.length() < needle.length()) {
            return -1;
        }

        int[] next = getNext(needle);
        int currentPos = 0;
        for (int i = 0; i < haystack.length(); i++) {
            while (currentPos > 0 && haystack.charAt(i) != needle.charAt(currentPos)) {
                currentPos = next[currentPos - 1];
            }
            if (haystack.charAt(i) == needle.charAt(currentPos)) {
                if (currentPos == needle.length() - 1) {
                    return i - needle.length() + 1;
                }
                currentPos++;
            }
        }
        return -1;
    }
    private int[] getNext(String pattern) {
        if (pattern == null || pattern == "") {
            return new int[0];
        }
        int[] next = new int[pattern.length()];
        next[0] = 0;
        int prefixIndex = 0;
        for (int i = 1; i < pattern.length(); i++) {
            while (prefixIndex > 0 && pattern.charAt(i) != pattern.charAt(prefixIndex)) {
                prefixIndex = next[prefixIndex - 1];
            }
            if (pattern.charAt(i) == pattern.charAt(prefixIndex)) {
                prefixIndex++;
            }
            next[i] = prefixIndex;
        }
        return next;
    }
}
// 解法一微调版, 可以进入 leetcode 的 0ms 区
class Solution {
   private int[] next;

    public int strStr(String haystack, String needle) {
        if (haystack == null || needle == null) {
            return -1;
        }
        if (needle == "") {
            return 0;
        }
        char[] haystackChars = haystack.toCharArray();
        char[] needleChars = needle.toCharArray();
        next = getNext(needleChars);

        int needleCharsIndex = 0;

        for (int i = 0; i < haystackChars.length; i++) {
            while (needleCharsIndex > 0 && haystackChars[i] != needleChars[needleCharsIndex]) {
                needleCharsIndex = next[needleCharsIndex - 1];
            }
            if (haystackChars[i] == needleChars[needleCharsIndex]) {
                if (needleCharsIndex == needleChars.length - 1) {
                    return i - needleChars.length + 1;
                }
                needleCharsIndex++;
            }
        }
        return -1;
    }

    public int[] getNext(char[] wordChars) {
        if (wordChars == null || wordChars.length == 0) {
            return new int[0];
        }
        int[] next = new int[wordChars.length];
        next[0] = 0;

        int j = 0;
        for (int i = 1; i < wordChars.length; i++) {
            if (j > 0 && wordChars[i] != wordChars[j]) {
                j = next[j - 1];
            }

            if (wordChars[i] == wordChars[j]) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

[LeetCode] 459. 重复的子字符串

[LeetCode] 459. 重复的子字符串 文字解释

[LeetCode] 459. 重复的子字符串 视频解释

题目:

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

示例 1:

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

示例 2:

输入: s = "aba"
输出: false

示例 3:

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

提示:

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

自己看到题目的第一想法

    遍历长字符串, 获取以第一个字符串开头的长度小于长字符串长度一半的所有子串, 再判断长字符串能否用这些子串重复组合起来.

    同样, leetcode 一定不会是简单的暴力解法.

看完代码随想录之后的想法

    解法一:

        神奇的想法来了: 如果字符串 s 是由一个子串重复 n 次得到的结果, 那么 s + s 后得到的 ss, 依然可以由子串重复 2n 次得到. 同时 ss 去头去尾后, 还会包含一个完整的 s.

        但是我对于 ss.contains(s) 的解法有一点不理解, s 是由子串重复拼凑而来可以推出 ss 去头去尾包含 s, 但是 ss 去头去尾包含 s 一定可以推出 s 是由子串重复拼凑而来吗?

        这里可以想一下, 当ss掐头去尾后, ss‘ 包含 s 的部分, 一定是在ss 的中间(不包括头尾), 并且新的 和 s 相等的子串, 头在第一个 s 的尾巴左边, 尾巴在第一个 s 的尾巴右边. 我们把子串的头到第一个 s 的尾巴成为 s0, 把第第一个 s 的尾巴到子串的尾巴成为 s1. 这样我们可以知道, 对于原始的 s 串, s = s0 + s1 = s1 + s0; 我们把 s1 看作短的字符串, 这时 s0 的尾巴也是由 s1 构成的, 因为 s = s0 + s1 = s0‘ + 2s1, 因此可以推出 s0 是由两个 s1 结尾的. 因为 s0 也是由 s1 开头的, 并且可以推出 s0同时是由两个 s1 开头, 因此最终会收敛到 s 是由 n 个 s1 构成的. 

    解法二:

        通过解法一最后一段的推理同时可以知道, 如果 s 剔除最长前后缀后剩下的字符 s1 的长度可以整除 s 的长度, 则 s 一定可以由 s1 通过 n 次循环收敛到 s 的中间.(因为这时候最靠近中心的两个 s1 是刚好相交且不会互相重叠的, 不知道如果相交是什么情况, 还没想明白) 因此求出 s 的最后一个字符的最长前后缀长度后, 通过 s.length() % prefix.length() == 0, 就可以知道 s 是否是由子串循环后构成了.

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if (s == null || s.length() < 1) {// 空字符串不能由空字符串组成
            return false;
        }
        // 生成最长前后缀长度表
        int[] next = new int[s.length()];
        int prefixIndex = 0;
        for (int i = 1; i < s.length(); i++) {
            while (prefixIndex > 0 && s.charAt(prefixIndex) != s.charAt(i)) {
                prefixIndex = next[prefixIndex - 1];
            }
            if (s.charAt(prefixIndex) == s.charAt(i)) {
                prefixIndex++;
            }
            next[i] = prefixIndex;
        }
        return next[next.length - 1] > 0 && s.length()%(s.length() - next[next.length - 1]) == 0;
    }
}

 自己实现过程中遇到哪些困难:

    对于最后一个字符没有相同前后缀的时候没有加判断, 导致类似 abac 这样的错误, 因为 "abac".length()%("abac".length() - 0) == 0, 导致错误的认为 abac 也是由子串循环得到了.

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练营主要涵盖了Leetcode题目的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的数组"。 首先是Leetcode 28题,题目要求在给定的字符串找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组找到长度最小的数组,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值