438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = "cbaebabacd", p = "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
第一次写的代码:
class Solution {
public List<Integer> findAnagrams(String s, String p) {
//滑动窗口
/*
while(left<right)
left:从charAt.s(0)开始,看在不在p中,不在则++
right:从left+(p.length-1)开始,看在不在p中,不在则left++,
在则right--,继续判断charAt.s(right--)在不在p中
*/
int left = 0;
int n = p.length();
int right = left + n -1;
if (p == null){
return null;
}
List<Integer> result = new LinkedList();
while (left < right){
if (!p.contains(s.charAt(left))){
left++;
}
else if(!p.contains(s.charAt(right))) {
left++;
}
else{
// result.add(left);
// right--;
for (int i=right; i>left; i--){
if (p.contains(s.charAt(i))){
result.add(left);
}
}
}
}
return result;
}
}
存在以下问题:
-
字符检查方法错误:
p.contains(s.charAt(left))
和p.contains(s.charAt(right))
不能正确检查字符是否在字符串p
中,因为String.contains(CharSequence)
方法用于检查子字符串,而不是单个字符。 -
滑动窗口逻辑不正确: 你的代码尝试使用两个指针
left
和right
,但没有正确实现滑动窗口的概念。滑动窗口的正确实现需要移动窗口内的字符并更新频率计数。 -
没有考虑窗口大小: 滑动窗口的大小应该始终等于字符串
p
的长度,但代码没有保证这一点。 -
循环退出条件错误:
while (left < right)
退出条件不正确,应当使用while (left <= s.length() - p.length())
来保证窗口不会超出字符串s
的边界。
正确思路:
因为字符串 p 的异位词的长度一定与字符串 p的长度相同,所以我们可以在字符串 s 中构造一个长度为与字符串 p的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;
当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词。
注意:
怎么记录每种字母的数量?
for (int i = 0; i < p.length(); i++) {
pCount[p.charAt(i) - 'a']++;
}
-
遍历的过程:
i = 0
时,p.charAt(0)
是'a'
:pCount['a' - 'a']++; // pCount[0]++,=1
i = 1
时,p.charAt(1)
是'b'
:pCount['b' - 'a']++; // pCount[1]++,=1
i = 2
时,p.charAt(2)
是'c'
:pCount['c' - 'a']++; // pCount[2]++,=1
最后,pCount
数组变为:
pCount[0] = 1; // 'a' 的频率
pCount[1] = 1; // 'b' 的频率
pCount[2] = 1; // 'c' 的频率
正确代码:
class Solution {
public List<Integer> findAnagrams(String s, String p) {
//滑动窗口
/*
在字符串 s 中构造一个长度为与字符串 p的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;
当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词。
*/
int plen = p.length();
int slen = s.length();
if (slen < plen){
return new ArrayList<Integer>();
}
List<Integer> result = new LinkedList();
int[] pCount = new int[26];
int[] WindowCount = new int[26];
//记录p中每种字母的数量
for (int i=0; i<plen; i++){
pCount[p.charAt(i)-'a']++;
}
//初始化第一个窗口中每种字母的数量
/*
第一个窗口是字符串 s 的前 p.length() 个字符。
我们需要统计这个窗口内每个字符的频率,以便与字符串 p 的字符频率进行比较。
*/
for (int i=0; i<plen; i++){
WindowCount[s.charAt(i)-'a']++;
}
if (Arrays.equals(pCount,WindowCount)){
result.add(0);
}
for (int left=0; left<slen-plen; left++){
// 更新窗口,添加右边的新字符,移除左边的旧字符
WindowCount[s.charAt(left+plen) - 'a']++;
WindowCount[s.charAt(left) - 'a']--;
// 如果当前窗口是异位词,添加起始索引
if (Arrays.equals(pCount,WindowCount)){
result.add(left+1);
}
}
return result;}
}
注意:
1.若S的长度小于P的长度,不能返回null,应该直接返回一个空数组,因为这个方法要求返回值是List<Integer>
if (slen < plen){
return new ArrayList<Integer>();
}
2.必须从第二个窗口开始,left<slen-plen(不能取等),要不然left+plen会超出索引
//除了第一个窗口,从第二个窗口(left+1)开始遍历
/*
必须从第二个窗口开始,left<slen-plen(不能取等),要不然left+plen会超出索引
*/
for (int left=0; left<slen-plen; left++){
// 更新窗口,添加右边的新字符,移除左边的旧字符
WindowCount[s.charAt(left+plen) - 'a']++;
WindowCount[s.charAt(left) - 'a']--;
// 如果当前窗口是异位词,添加起始索引
if (Arrays.equals(pCount,WindowCount)){
result.add(left+1);
}
3.判断pCount和WindowCount相等,必须用Arrays.equals(pCount,WindowCount),不能直接用pCount==WindowCount(equals,不是equal!!!)
==
用于比较两个对象的引用是否相同。对于数组来说,==
比较的是两个数组对象的内存地址,而不是它们的内容。因此,pCount == WindowCount
判断的是两个数组是否是同一个数组对象,而不是它们的内容是否相同。
567. 字符串的排列 - 力扣(LeetCode)
给你两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。如果是,返回 true
;否则,返回 false
。
换句话说,s1
的排列之一是 s2
的 子串 。
示例 1:
输入:s1 = "ab" s2 = "eidbaooo" 输出:true 解释:s2 包含 s1 的排列之一 ("ba").
示例 2:
输入:s1= "ab" s2 = "eidboaoo" 输出:false
class Solution {
public boolean checkInclusion(String s1, String s2) {
/*
滑动窗口:在s2中滑动,窗口大小为s1.length()
统计s1中字母出现的次数
判断与窗口中字母出现的次数是否相等
*/
int windowLength = s1.length();
if (s2.length() < windowLength){
return false;
}
//统计s1中字母出现的次数
int[] s1Count = new int[26];
for(int i = 0; i < windowLength; i++){
s1Count[s1.charAt(i) - 'a']++;
}
//初始化第一个窗口内容,这样for循环中才好匹配
int[] s2Count = new int[26];
for(int i = 0; i < windowLength; i++){
s2Count[s2.charAt(i) - 'a']++;
}
//在s2中滑动窗口,匹配窗口中的内容与s1是否相等
for(int left = 0; left < s2.length() - windowLength; left++) {
if (Arrays.equals(s1Count,s2Count)){
return true;
}
//更新窗口内容(继续滑动)
//把左边的数删除(对应的字母出现的次数减一)
s2Count[s2.charAt(left) - 'a']--;
//add右边的数(对应的字母出现的次数加一)
s2Count[s2.charAt(left+windowLength)-'a']++;
}
//最后一个窗口在for循环中没扫描上,因此在循环外再匹配一次
for(int left = s2.length() - windowLength; left<=s2.length() - windowLength; left++){
if (Arrays.equals(s1Count,s2Count)){
return true;
}
}
return false;
}
}
判断最后一个窗口的时候可以优化。此时已经循环完了,窗口就在s2的最后了,不需要这个for循环
//最后一个窗口在for循环中没扫描上,因此在循环外再匹配一次
if (Arrays.equals(s1Count,s2Count)){
return true;
}
优化后的正确代码:
class Solution {
public boolean checkInclusion(String s1, String s2) {
/*
滑动窗口:在s2中滑动,窗口大小为s1.length()
统计s1中字母出现的次数
判断与窗口中字母出现的次数是否相等
*/
int windowLength = s1.length();
if (s2.length() < windowLength){
return false;
}
//统计s1中字母出现的次数
int[] s1Count = new int[26];
for(int i = 0; i < windowLength; i++){
s1Count[s1.charAt(i) - 'a']++;
}
//初始化第一个窗口内容,这样for循环中才好匹配
int[] s2Count = new int[26];
for(int i = 0; i < windowLength; i++){
s2Count[s2.charAt(i) - 'a']++;
}
//在s2中滑动窗口,匹配窗口中的内容与s1是否相等
for(int left = 0; left < s2.length() - windowLength; left++) {
if (Arrays.equals(s1Count,s2Count)){
return true;
}
//更新窗口内容(继续滑动)
//把左边的数删除(对应的字母出现的次数减一)
s2Count[s2.charAt(left) - 'a']--;
//add右边的数(对应的字母出现的次数加一)
s2Count[s2.charAt(left+windowLength)-'a']++;
}
//最后一个窗口在for循环中没扫描上,因此在循环外再匹配一次
if (Arrays.equals(s1Count,s2Count)){
return true;
}
return false;
}
}
注意:
Arrays.equals
一定要有s!!!