代码随想录算法训练营第九天| 151. 反转字符串中的单词、55. 右旋字符串(第八期模拟笔试)、28. 找出字符串中第一个匹配项的下标、459. 重复的子字符串

[LeetCode] 151. 反转字符串中的单词

[KamaCoder] 151. 反转字符串中的单词 文章解释

[LeetCode] 151. 反转字符串中的单词

核心思想: 先翻转字符串,然后利用双指针将符合规则的字符, 移动到数组的前面, 同时当遇到空格时, 翻转字符串. 这里翻转字符串的时候需要用到当前合规字符串的长度, 因此在 fast 指针往前走的时候, 需要记录一下非空字符串的长度. 同时需要注意, 因为翻转字符串是以空格为判断条件的, 因此如果最后一个字符串不是空格的话, 需要额外翻转一下. 也就是说, 当判断完当前字符非空格的时候, 需要判断一下当前字符是否是最后一个字符, 是的话需要单独翻转一下.

public class Solution {
    public String reverseWords(String s) {
        if (s == null) {
            return null;
        }
        char[] chars = s.toCharArray();
        int left = 0;
        int right = chars.length - 1;
        while (left < right) {
            char temp = chars[left];
            chars[left++] = chars[right];
            chars[right--] = temp;
        }

        int charIndex = 0;
        int wordCount = 0;
        for (right = 0; right < chars.length; right++) {
            if (chars[right] == ' ') {
                if (wordCount == 0) {
                    continue;
                } else {
                    reverseChars(chars, charIndex - wordCount, charIndex - 1);
                    chars[charIndex++] = chars[right];
                    wordCount = 0;
                }
            } else {
                chars[charIndex++] = chars[right];
                wordCount++;
                if (right == chars.length - 1) {
                    reverseChars(chars, charIndex - wordCount, charIndex - 1);
                }
            }
        }

        if (chars[charIndex - 1] != ' ') {
            // 如果最后一个字符不是空的话, 需要保留
            return new String(chars, 0, charIndex);
        } else {
            // 如果最后一个字符是空的话, 需要删除
            return new String(chars, 0, charIndex - 1);
        }
    }
    
    private void reverseChars(char[] chars, int start, int end) {
        while (start < end) {
            char temp = chars[start];
            chars[start++] = chars[end];
            chars[end--] = temp;
        }
    }
}

[KamaCoder] 55. 右旋字符串(第八期模拟笔试)

[KamaCoder] 55. 右旋字符串(第八期模拟笔试) 文章解释

[KamaCoder]  55. 右旋字符串(第八期模拟笔试)

核心思想: 先翻转全部的字符串, 再翻转 0 ~ n - 1 个字符串, 再翻转 n ~ word.length() - 1 个字符串.

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int count = Integer.parseInt(scanner.nextLine().trim());
            String word = scanner.nextLine();

            if (word == null || count > word.length()) {
                return;
            }
            char[] wordChars = word.toCharArray();
            reverseChars(wordChars, 0, wordChars.length - 1);
            reverseChars(wordChars, 0, count - 1);
            reverseChars(wordChars, count, wordChars.length - 1);
            System.out.println(new String(wordChars));
        }
    }
    
    // 翻转字符串
    private static void reverseChars(char[] wordChars, int start, int end) {
        char temp;
        while (start < end) {
            temp = wordChars[start];
            wordChars[start] = wordChars[end];
            wordChars[end] = temp;
            start++;
            end--;
        }
    }
}

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

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

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

嗯, KMP 算法有点绕, 模拟执行的时候似乎总是有神秘地带我没有探索到. 以至于第二次我还是需要看一下讲解才能写出来, 甚至不是写出来而是背出来. 特别是生成前缀表的时候, 回退前缀长度要用 while 这个我能理解, 但是其实用 if 再 leetcode 上也是能 AC. 但是谁又能保证不是样本的问题呢? 用 while 至少逻辑上是可以融洽和理解的, 所以就接受它.

这一题是 KMP 算法的应用. KMP 算法的思想是, 在字符串 A 中查找是否存在 B 字符串. 最直观的暴力解法就是, 遍历 A 中的每一个字符, 当 A 中的某个字符和 B 中的第一个字符相同时, 则开始遍历 A 中当前字符往后的字符,  如果接下来的字符都和 B 中接下来的字符相同, 则知道 A 中存在一个 B 字符串. 如果 A 接下来的字符中, 有某个字符和 B 中的某个字符不相同, 这时候就要跳过 A 中和 B 第一个字符相同的那个字符, 继续往下匹配.

而 KMP 算法的想法是, 如果在 A 中找到和 B 第一个字符相同的字符, 则继续往后遍历, 当 A 中存在和 B 中不一样的字符的时候, 不再从 A 中和 B 第一个字符相同的字符的下一个字符开始比对, 而是看看当前不一样的字符以前的字符中, 和从 B 开始的字符中相等的字符串的最大长度是多少, 如果长度大于 0 的话, 就将 A 的当前字符和 B[sameWordLength] 去做对比, 直到 sameWordLength 为 0, 则开始从 B 的第一个字符开始比对.

而获取 sameWordLength 就需要用到前缀表, 这也是 KMP 算法的核心.

class Solution {
    public int strStr(String haystack, String needle) {
        if (haystack == null || needle == null || haystack.length() < needle.length()) {
            return -1;
        }
        int[] next = getNext(needle);
        for (int i = 0, length = 0; i < haystack.length(); i++) {
            while (length > 0 && needle.charAt(length) != haystack.charAt(i)) {
                length = next[length - 1];
            }
            if (haystack.charAt(i) == needle.charAt(length)) {
                length++;
            }
            if (length == needle.length()) {
                return i - needle.length() + 1;
            }
        }
        return -1;
    }
    private int[] getNext(String needle) {
        int[] next = new int[needle.length()];
        next[0] = 0;
        int prefixLength = 0;
        for (int i = 1; i < needle.length(); i++) {
            while (prefixLength > 0 && needle.charAt(i) != needle.charAt(prefixLength)) {
                // 这里 prefixLength 大于 0, 说明从 needle 的第一个字符开始接下来的 
                //     prefixLength - 2 个字符, 和 i 之前的 prefixLength - 1 字符是相等的.
                // 因此 需要借助 next 前缀表, 看看当前字符需要和从 needle 开始的 prefixLength - 1 个字符中的哪一个字符开始继续比较
                // 以更新最大相等前后缀的长度, 如果 prefixLength 为 0
                // 则说明当前字符之前的连续的字符组成的字符串, 没有能和 needle 从头往后的一致
                // 因此当前字符需要从 needle 的第一个字符开始重新比较
                prefixLength = next[prefixLength - 1];
            }
            if (needle.charAt(prefixLength) == needle.charAt(i)) {
                prefixLength++;
            }
            next[i] = prefixLength;
        }
        return next;
    }
}

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

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

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

这一题更像是完全算法的数学题~ 核心思想是: 如果 s 是由长度为 x 的子串 s1 重复 n 次组成的, 那么 s.length 就等于 n * x, 此时根据最长相等前缀和相等后缀可以知道, 前缀和后缀不相交的部分, 就是重复子串 s1 的长度, 并且 s.length() - next[s.length() - 1] == s1.length() == x, 因此 s.length() % (s.length() - next[s.length() - 1]) == 0.

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if (s == null) {
            return true;
        }
        int[] next = getNext(s);
        return  next[next.length - 1] > 0 && s.length() % (s.length() - next[next.length - 1]) == 0;
    }
    private int[] getNext(String needle) {
        int[] next = new int[needle.length()];
        next[0] = 0;
        int prefixLength = 0;
        for (int i = 1; i < needle.length(); i++) {
            while (prefixLength > 0 && needle.charAt(i) != needle.charAt(prefixLength)) {
                // 这里 prefixLength 大于 0, 说明从 needle 的第一个字符开始接下来的 
                //     prefixLength - 2 个字符, 和 i 之前的 prefixLength - 1 字符是相等的.
                // 因此 需要借助 next 前缀表, 看看当前字符需要和从 needle 开始的 prefixLength - 1 个字符中的哪一个字符开始继续比较
                // 以更新最大相等前后缀的长度, 如果 prefixLength 为 0
                // 则说明当前字符之前的连续的字符组成的字符串, 没有能和 needle 从头往后的一致
                // 因此当前字符需要从 needle 的第一个字符开始重新比较
                prefixLength = next[prefixLength - 1];
            }
            if (needle.charAt(prefixLength) == needle.charAt(i)) {
                prefixLength++;
            }
            next[i] = prefixLength;
        }
        return next;
    }
}
class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if (s == null || s.length() == 0) {
            return true;
        }
        String ss = s + s;
        ss = new String (ss.toCharArray(), 1, s.length() * 2 - 2);
        return ss.contains(s);
    }
}
  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值