[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. 右旋字符串(第八期模拟笔试)
核心思想: 先翻转全部的字符串, 再翻转 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. 找出字符串中第一个匹配项的下标
嗯, 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. 重复的子字符串
这一题更像是完全算法的数学题~ 核心思想是: 如果 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);
}
}