1.找出字符串第一个匹配项的下标
给你两个字符串 haystack
和 needle
,请你在 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 <= 10^4
haystack
和needle
仅由小写英文字符组成
解法一:BF算法
class Solution {
public int strStr(String haystack, String needle) {
char[] origin = haystack.toCharArray(); // 原始
char[] pattern = needle.toCharArray(); // 模式
int i = 0, j = 0;
while (i <= origin.length - pattern.length) {
for (j = 0; j < pattern.length; j++) {
if (pattern[j] != origin[i + j]) {
break;
}
}
if (j == pattern.length) {
return i;
}
i++;
}
return -1;
}
}
解法二:KMP算法
class Solution {
public int strStr(String haystack, String needle) {
char[] origin = haystack.toCharArray(); // 原始
char[] pattern = needle.toCharArray(); // 模式
int n = origin.length;
int m = pattern.length;
int[] lps = lps(pattern); // 最长前后缀数组
/*
* 1. 匹配成功,i++, j++,直到j==模式字符串长度
* 2. 匹配失败
* j != 0 跳过最长前后缀字符,继续匹配
* j == 0 则 i++
*/
int i = 0;
int j = 0;
while ((n - i) >= (m - j)) {
if (origin[i] == pattern[j]) {
// 匹配成功
i++;
j++;
} else if (j == 0) {
// 匹配失败 j == 0
i++;
} else {
// 匹配失败
j = lps[j - 1];
}
if (j == m) {
// 找到解
return i - j;
}
}
return -1;
}
/**
* 最长前后缀数组:只跟模式字符串相关
* 1. 索引:使用了模式字符串前j个字符串 -1
* 2. 值:最长前后缀的长度(恰好是匹配失败时j要跳转的位置)
*
* @param pattern
* @return
*/
private static int[] lps(char[] pattern) {
int[] lps = new int[pattern.length];
int i = 1;
int j = 0;
while (i < pattern.length) {
// 遇到相同字符,记录共同前后缀(模式字符串的前缀,原始字符串的后缀)长度,长度即为j+1,长度记录至数组i索引处,然后i++ j++
if (pattern[i] == pattern[j]) {
j++;
lps[i] = j;
i++;
} else if (j != 0) {
// 遇到不同字符,前面有共同部分,j向回找,无需比对的地方可以跳过
j = lps[j - 1];
} else {
// 遇到不同字符,前面没有共同部分(j==0),只需要i++
i++;
}
}
return lps;
}
}
投机取巧:
public int strStr(String haystack, String needle) {
return haystack.indexOf(needle);
}
2. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入:strs = ["flower","flow","flight"] 输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"] 输出:"" 解释:输入不存在公共前缀。
提示:
1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i]
仅由小写英文字母组成
解法一:执行耗时2ms
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs.length == 0) {
return "";
}
if (strs.length == 1) {
return strs[0];
}
int minLen = strs[0].length();
char[] chars = strs[0].toCharArray();
for (int i = 1; i < strs.length; i++) {
int j = 0;
for (; j < minLen && j < strs[i].length(); j++) {
if (chars[j] != strs[i].charAt(j)) {
break;
}
}
if (j == 0) {
return "";
}
chars = Arrays.copyOfRange(chars, 0, j);
minLen = j;
}
return new String(chars);
}
}
解法二:优化。执行耗时1ms
class Solution {
/**
* 情况1:比较某一列时,遇到不同字符,i之前的字符就是解
* 情况2:比较某一列时,遇到字符串长度不够,i之前的字符就是解
* 情况3:i循环自然结束,此时第一个字符串就算解
*
* @param strs
* @return
*/
public String longestCommonPrefix(String[] strs) {
char[] first = strs[0].toCharArray();
for (int i = 0; i < first.length; i++) {
char ch = first[i];
for (int j = 1; j < strs.length; j++) {
if (i == strs[j].length() || ch != strs[j].charAt(i)) {
return new String(first, 0, i);
}
}
}
return strs[0];
}
}
3. 最长回文子串
给你一个字符串 s
,找到 s
中最长的 回文子串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
解法一:
class Solution {
public String longestPalindrome(String s) {
left = 0;
right = 0;
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length - 1; i++) {
extend(chars, i, i); // 中心点为1个字符
extend(chars, i, i + 1); // 2个字符作为中心点
}
return new String(chars, left, right - left + 1);
}
// 记录最长回文子串的范围
static int left; // i
static int right; // j
/**
* 计算最长回文子串的左右边界
*
* @param chars
* @param i
* @param j
*/
private static void extend(char[] chars, int i, int j) {
// 如果是回文,扩大回文范围
while (i >= 0 && j < chars.length && chars[i] == chars[j]) {
i--;
j++;
}
// 不是回文
i++;
j--;
if (j - i > right - left) {
// 最长回文子串的位置
left = i;
right = j;
}
}
}
4. 最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入:s = "a", t = "a" 输出:"a" 解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.length
n == t.length
1 <= m, n <= 10^5
s
和t
由英文字母组成
解法一:
class Solution {
static class Result {
int i;
int j;
public Result(int i, int j) {
this.i = i;
this.j = j;
}
}
/**
* 1. 统计目标串需要各种字符个数,统计原始串i~j范围内各种字符个数
* 2. 如果原始串i~j范围内不满足条件,j++扩大范围,直到满足条件j停下来
* 3. 一旦满足条件, i++缩小范围,直到再次不满足条件
* 4. 重复2、3两步直到原始串末尾
*
* @param s
* @param t
* @return
*/
public static String minWindow(String s, String t) {
char[] source = s.toCharArray();
char[] target = t.toCharArray();
int[] sourceCount = new int[128];
int[] targetCount = new int[128];
// 1. 统计目标串需要各种字符个数
for (char c : target) {
targetCount[c]++;
}
int passTotal = 0; // 条件总数
for (int count : targetCount) {
if (count > 0) {
passTotal++;
}
}
int passed = 0; // 已经通过的条件数
int i = 0, j = 0;
Result result = null;
while (j < source.length) {
// 扩大j的范围,更新范围内字符计数和通过条件数
char right = source[j];
sourceCount[right]++;
if (sourceCount[right] == targetCount[right]) {
passed++;
}
// 一旦满足条件, i++缩小范围,直到再次不满足条件
while (i <= j && passed == passTotal) {
if (result == null) {
result = new Result(i, j);
} else {
if (j - i < result.j - result.i) {
result = new Result(i, j);
}
}
// 更新范围内字符计数和通过条件数
char left = source[i];
sourceCount[left]--;
if (sourceCount[left] < targetCount[left]) {
passed--;
}
i++;
}
j++;
}
return result == null ? "" : s.substring(result.i, result.j + 1);
}
}