LeetCode hot 100 (8-11)
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。
s由英文字母、数字、符号和空格组成
思路
一般出现散列表,都是字符作为键,出现的次数作为值
涉及子串,考虑滑动窗口
要记录下标,然后遇到同样的字符的时候往右划窗口,刚好不包含那个重复的元素
java
//外层循环扩展右边界,内层循环扩展左边界
for (int l = 0, r = 0 ; r < n ; r++) {
//当前考虑的元素
while (l <= r && check()) {//区间[left,right]不符合题意
//扩展左边界
}
//区间[left,right]符合题意,统计相关信息
}
题解
java
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] ss=s.toCharArray();
Set<Character> set=new HashSet<>();
int res=0;
int n=s.length();
for(int l=0,r=0;r<n;r++){
char ch=ss[r];
while(set.contains(ch)){
set.remove(ss[l]);
l++;
}
set.add(ss[r]);
res=Math.max(res,r-l+1);
}
return res;
}
}
438. 找到字符串中所有字母异位词
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
思路
p的长度肯定是s子串的长度,子串的顺序固定了的话,那就从头扫到尾,因为只包含小写字母,维护两个26大小的数组就行
补充:出现次数也要一致,是定长滑动窗口
只需要计数,统计两个数组相同是Arrays.equals(数组1,数组2)
题解
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(),pLen=p.length();
if(sLen<pLen){
return new ArrayList<Integer>();
}
List<Integer> res = new ArrayList<>();
int[] sCount = new int[26];
int[] pCount = new int[26];
for(int i=0;i<pLen;++i){
++sCount[s.charAt(i)-'a'];
++pCount[p.charAt(i)-'a'];
}
if(Arrays.equals(sCount,pCount)){
res.add(0);
}
for(int i=0;i<sLen-pLen;++i){
--sCount[s.charAt(i)-'a'];
++sCount[s.charAt(i+pLen)-'a'];
if(Arrays.equals(sCount,pCount)){
res.add(i+1);
}
}
return res;
}
}
优化思路
Arrays.equals(sCount,pCount)在比较两个数组
优化之后,只用一个数组
count[x] = 当前窗口中字符x的个数 - p中字符x的个数
differ = 有多少个字符的 count[x] != 0
也就是differ == 0时就是异位词
这样每次窗口移动,只需要关心:
出去的那个字符
进来的那个字符
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();
if (sLen < pLen) {
return new ArrayList<Integer>();
}
List<Integer> ans = new ArrayList<Integer>();
int[] count = new int[26];
for (int i = 0; i < pLen; ++i) {
++count[s.charAt(i) - 'a'];
--count[p.charAt(i) - 'a'];
}
int differ = 0;
for (int j = 0; j < 26; ++j) {
if (count[j] != 0) {
++differ;
}
}
if (differ == 0) {
ans.add(0);
}
for (int i = 0; i < sLen - pLen; ++i) {
if (count[s.charAt(i) - 'a'] == 1) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s.charAt(i) - 'a'] == 0) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
--count[s.charAt(i) - 'a'];
if (count[s.charAt(i + pLen) - 'a'] == -1) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
--differ;
} else if (count[s.charAt(i + pLen) - 'a'] == 0) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
++differ;
}
++count[s.charAt(i + pLen) - 'a'];
if (differ == 0) {
ans.add(i + 1);
}
}
return ans;
}
}
560. 和为 K 的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
思路
排序,然后左右同时扫,大了的话就左移动,小了的话就右移动;
判断左是否比总和大,如果大就结束;判断右是否比总和小,小的话也结束;
这是两个元素的,三个元素呢......
上面的思路有问题,不能排序!没有单调的话双指针也失效了
标准做法是:前缀和 + 哈希表
题解
java
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1); // 前缀和为0,出现1次
int sum = 0;
int ans = 0;
for (int num : nums) {
sum += num;
if (map.containsKey(sum - k)) {
ans += map.get(sum - k);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return ans;
}
}
239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
思路
定长滑动窗口,最差的情况就是左右指针for然后比较里面的内容
单调队列
题解
java
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 数组长度
int n =nums.length;
// 定义单调队列,队列里存储的是索引。
Deque<Integer> deque = new LinkedList<>();
// 初始化窗口,也就是完整导入前k个数字
for(int i=0;i<k;++i){
// 队列为不空,且新的数字比队列末尾数字大的时候
while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){
// 弹出末尾元素
deque.pollLast();
}
// 从队尾加新元素上去
deque.offerLast(i);
}
// 定义最后返回数组,大小是n-k+1
int[] ans =new int[n-k+1];
// 第一个元素是队头的元素
ans[0] = nums[deque.peekFirst()];
// 从位置k开始滑动,直到小于n
for(int i=k;i<n;++i){
// 队列不空,且新的数字比队尾大
while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){
// 从队尾弹出
deque.pollLast();
}
// 从队尾加入新元素
deque.offerLast(i);
// 判断队头还在不在窗口内,不在的话就从前面弹出
while(deque.peekFirst()<=i-k){
deque.pollFirst();
}
// 存入当前窗口最大值,索引是deque.peekFirst,也就是队头元素
ans[i-k+1]=nums[deque.peekFirst()];
}
return ans;
}
}