LeetCode 热题 100——滑动窗口——找到字符串中所有字母异位词
8. 找到字符串中所有字母异位词
题目描述
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。
示例 2:
输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。
提示:
1 <= s.length, p.length <= 3 * 104
s 和 p 仅包含小写字母
求解
(1)遍历求解
var isAnagram = function(a, b) {const a1 = a.split('').sort();const b1 = b.split('').sort();return a1.join('') === b1.join('')
}
var findAnagrams = function(s, p) {if (s.length < p.length) return []let ans = []// 思路:遍历s,按照p的长度进行滑动进行判断for (let i = 0; i < s.length - p.length + 1; i++) {let item = s.slice(i, i + p.length)if (isAnagram(item, p)) {ans.push(i)}}return ans
};
暴力求解性能太差,超出时间限制了。学一学其他做法:
(2)滑动窗口
异位词的关键是:所含的字母的数量是一致的,一个大小为26的数组就可以将 所有字母包含:97-122(小写),所以定义一个数组,初始全为0,按照p的大小进行滑动,在判断数组是否一致就可以,我上面的方法有点类似,就是判断的方法有点太粗糙了。
代码如下:
var findAnagrams = function(s, p) {const s_l = s.length, p_l = p.lengthif (s_l < p_l) return []let ans = [] // 存储答案// 分别存储 p 个字母 出现的次数let s_count = new Array(26).fill(0)let p_count = new Array(26).fill(0)// 遍历p长度,初始化数组for (let i = 0; i < p_l; i++) {s_count[s.charCodeAt(i) - 97]++p_count[p.charCodeAt(i) - 97]++}// 判断第一个子串和p是否为异位词if (s_count.toString() === p_count.toString()) ans.push(0)// 判断其他子串for (let i = 0; i < s_l - p_l; i++) {// 去掉子串第一个字符s_count[s.charCodeAt(i) - 97]--// 加上下一个子串的最后一个字符s_count[s.charCodeAt(i + p_l) - 97]++// 判断是否为异位词if (s_count.toString() === p_count.toString()) ans.push(i + 1)}return ans
}
(3)继续优化
不再分别统计两个数组中包含字符的数量,而是 统计滑动窗口和字符串 p 中每种字母数量的差。定义两个变量:
- count[26]: 0 表示和p 中对应的字符数量一致;1表示多一个,-1表示多一个
- differ:统计字符数量不一致的 数量
判断的时候只需要判断 differ 是否为0即可。
var findAnagrams = function(s, p) {const s_l = s.length, p_l = p.lengthif (s_l < p_l) return []let ans = [] // 存储答案let count = new Array(26).fill(0)// 初始化数组,看for (let i = 0; i < p_l; i++) {count[s.charCodeAt(i) - 97]++count[p.charCodeAt(i) - 97]--}let differ = 0 // 表示的是和P不一致的个数// 初始化differfor (let i = 0; i < 26; i++) {if (count[i] !== 0) differ++}if (differ === 0) {ans.push(0)}// 滑动窗口,判断其他子串for (let i = 0; i < s_l - p_l; i++) {// 判断要移除的s[i]// 判断 要移除的 是不是原本就有的,所以减去之后就是0了。该字符就与p一致了if (count[s.charCodeAt(i) - 97] === 1) {differ-- // 与p不一致的少了一个} else if(count[s.charCodeAt(i) - 97] === 0) {// 要移除的,原本就没有,那么和p不一致的多了一个differ++}// 移除s[i]count[s.charCodeAt(i) - 97]--// 判断要加入的s[i + p_l]if (count[s.charCodeAt(i + p_l) - 97] === -1) {// 判断要加入的字符之前没有 → 说明和 p一致的多了一个differ--} else if (count[s.charCodeAt(i + p_l) - 97] === 0) {// 判断要加入的字符 已经存在了 → 说明 与p不一致的多了一个differ++}// 加入s[i + p_l]count[s.charCodeAt(i + p_l) - 97]++// 判断是否符合if (differ === 0) {ans.push(i + 1)}}return ans
};
补充
charCodeAt
charCodeAt 是 JavaScript 中 String 类型 的一个方法,它的作用是 返回字符串中指定索引位置的字符的 Unicode 编码。
语法:
str.charCodeAt(index)
index:必需,字符串中某个字符的索引(从 0 开始)。
返回值:指定字符的 Unicode 编码(一个 0-65535 之间的整数)。
举个栗子:
const str = "abc";console.log(str.charCodeAt(0)); // 输出 97 (字符 "a" 的 Unicode 编码)
console.log(str.charCodeAt(1)); // 输出 98 (字符 "b" 的 Unicode 编码)
console.log(str.charCodeAt(2)); // 输出 99 (字符 "c" 的 Unicode 编码)
常见用途
-
字符比较:通过比较 Unicode 编码来判断字符的大小或顺序。
const a = "a"; const b = "b";console.log(a.charCodeAt(0) < b.charCodeAt(0)); // 输出 true -
字符转换:
小写字母转大写字母:charCodeAt(0) - 32
大写字母转小写字母:charCodeAt(0) + 32const lowerCase = "a"; const upperCase = String.fromCharCode(lowerCase.charCodeAt(0) - 32);console.log(upperCase); // 输出 "A" -
判断字符类型:
判断是否为大写字母:charCodeAt(0) >= 65 && charCodeAt(0) <= 90
判断是否为小写字母:charCodeAt(0) >= 97 && charCodeAt(0) <= 122
判断是否为数字:charCodeAt(0) >= 48 && charCodeAt(0) <= 57const char = "5";if (char.charCodeAt(0) >= 48 && char.charCodeAt(0) <= 57) {console.log("是数字"); } else {console.log("不是数字"); } -
注意:
如果 index 超出了字符串的范围,charCodeAt 会返回 NaN。const str = "abc"; console.log(str.charCodeAt(10)); // 输出 NaNcharCodeAt 返回的是 Unicode 编码,而不是字符本身。如果想根据编码获取字符,可以使用
String.fromCharCode()方法。console.log(String.fromCharCode(97)); // 输出 "a"
