本系列文章记录labuladong的算法小抄中剑指offer题目
【剑指offer刷题记录 java版】数组双指针 之 滑动窗口
- 剑指 Offer 48. 最长不含重复字符的子字符串
- 剑指 Offer II 014. 字符串中的变位词
- 剑指 Offer II 015. 字符串中的所有变位词
- 剑指 Offer II 016. 不含重复字符的最长子字符串
- 剑指 Offer II 017. 含有所有字符的最短字符串(难)
- 剑指 Offer 30. 包含min函数的栈
- 剑指 Offer 59 - I. 滑动窗口的最大值(难)
- 总结
剑指 Offer 48. 最长不含重复字符的子字符串
题目链接:https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap();
int left=0,right=0;
int length=0;
while(right<s.length()){
char c = s.charAt(right);
map.put(c,map.getOrDefault(c,0)+1);// 进行窗口内数据的一系列更新
right++;
while(map.get(c)>1){// 判断左侧窗口是否要收缩
char temp = s.charAt(left);
map.put(temp, map.get(temp)-1);
left++;
}
length = Math.max(length,right-left);
}
return length;
}
}
剑指 Offer II 014. 字符串中的变位词
题目链接:https://leetcode.cn/problems/MPnaiL/
class Solution {
public boolean checkInclusion(String s1, String s2) {
//map记录s1中包含的字母及数量
Map<Character,Integer> map = new HashMap<>();
for(int i=0;i<s1.length();i++){
map.put(s1.charAt(i),map.getOrDefault(s1.charAt(i),0)+1);
}
//map2记录s2中,曾出现在s1中的字母及数量
Map<Character,Integer> map2 = new HashMap<>();
int left=0, right=0;
int valid=0;//valid记录数量相同的字母的个数
while(right<s2.length()){
char c = s2.charAt(right);
if(map.containsKey(c)){
map2.put(c,map2.getOrDefault(c,0)+1);
if (map2.get(c).equals(map.get(c)))
valid++;
}
right++;
while(right-left==s1.length()){
// 在这里判断是否找到了合法的子串
if (valid == map.size())
return true;
char d = s2.charAt(left);
// 进行窗口内数据的一系列更新
if (map.containsKey(d)) {
if (map2.get(d).equals(map.get(d)))
valid--;
map2.put(d, map2.get(d) - 1);
}
left++;
}
}
return false;
}
}
//因为只有26个小写英文字母,可以使用数组记录
class Solution {
public boolean checkInclusion(String s1, String s2) {
int n = s1.length(), m = s2.length();
if (n > m) {
return false;
}
int[] cnt = new int[26];
for (int i = 0; i < n; ++i) {
--cnt[s1.charAt(i) - 'a'];
++cnt[s2.charAt(i) - 'a'];
}
int diff = 0;
for (int c : cnt) {
if (c != 0) {
++diff;
}
}
if (diff == 0) {
return true;
}
for (int i = n; i < m; ++i) {
int x = s2.charAt(i) - 'a', y = s2.charAt(i - n) - 'a';
// 滑窗右边扩张左边收缩
if (x == y) continue;
if (cnt[x] == 0) {++diff;}
++cnt[x];
if (cnt[y] == 0) {++diff;}
--cnt[y];
// 根据现状判断有几处不同
if (cnt[x] == 0) {--diff;}
if (cnt[y] == 0) {--diff;}
if (diff == 0) {return true;}
}
return false;
}
}
剑指 Offer II 015. 字符串中的所有变位词
题目链接:https://leetcode.cn/problems/VabMRr/
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int m=s.length(), n=p.length();
if(m<n){
return new ArrayList<Integer>();
}
List<Integer> res = new ArrayList<>();
int[] count = new int[26];
for(int i=0;i<p.length();i++){
count[p.charAt(i)-'a']--;
count[s.charAt(i)-'a']++;
}
int diff=0;
for(int num: count){
if(num!=0) diff++;
}
if(diff==0){
res.add(0);
}
for(int i=n; i<m;i++){
//窗口下标范围为 [i-n, i)
int index1 = s.charAt(i)-'a';//右边元素
int index2 = s.charAt(i-n)-'a';//左边元素
if(count[index1]==0) diff++;
count[index1]++;
if(count[index1]==0) diff--;
if(count[index2]==0) diff++;
count[index2]--;
if(count[index2]==0) diff--;
if(diff==0){
res.add(i-n+1);
}
}
return res;
}
}
剑指 Offer II 016. 不含重复字符的最长子字符串
题目链接:https://leetcode.cn/problems/wtcaE1/
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s=="")return 0;
int left=0,right=0;
int length=0;
// 使用哈希表记录窗口内字符出现次数
Map<Character, Integer> map = new HashMap<>();
while(right<s.length()){
char c = s.charAt(right);
right++;
map.put(c, map.getOrDefault(c,0)+1);
//如果窗口中包含c
while(map.get(c)>=2){
char temp = s.charAt(left);
map.put(temp, map.get(temp)-1);
left++;
}
length=Math.max(length,right-left);
}
return length;
}
}
剑指 Offer II 017. 含有所有字符的最短字符串(难)
题目链接:https://leetcode.cn/problems/M1oyTv/
进阶:你能设计一个在 o(n)
时间内解决此问题的算法吗?
class Solution {
Map<Character, Integer> ori = new HashMap<Character, Integer>();
Map<Character, Integer> cnt = new HashMap<Character, Integer>();
public String minWindow(String s, String t) {
int tLen = t.length();
for (int i = 0; i < tLen; i++) {
char c = t.charAt(i);
ori.put(c, ori.getOrDefault(c, 0) + 1);
}
int l = 0, r = -1;
int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
int sLen = s.length();
while (r < sLen) {
++r;
if (r < sLen && ori.containsKey(s.charAt(r))) {
cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
}
while (check() && l <= r) {
if (r - l + 1 < len) {
len = r - l + 1;
ansL = l;
ansR = l + len;
}
if (ori.containsKey(s.charAt(l))) {
cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
}
++l;
}
}
return ansL == -1 ? "" : s.substring(ansL, ansR);
}
public boolean check() {
Iterator iter = ori.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Character key = (Character) entry.getKey();
Integer val = (Integer) entry.getValue();
if (cnt.getOrDefault(key, 0) < val) {
return false;
}
}
return true;
}
}
剑指 Offer 30. 包含min函数的栈
题目链接:https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/
class MinStack {
Deque<Integer> dq;
Deque<Integer> minDq;
/** initialize your data structure here. */
public MinStack() {
dq = new LinkedList<>();
minDq = new LinkedList();
minDq.offerLast(Integer.MAX_VALUE);//注意为了防止空栈,要在栈底添加最大元素
}
public void push(int x) {
dq.offerLast(x);
minDq.offerLast(Math.min(x,minDq.peekLast()));
}
public void pop() {
dq.pollLast();
minDq.pollLast();
}
public int top() {
return dq.peekLast();
}
public int min() {
return minDq.peekLast();
}
}
剑指 Offer 59 - I. 滑动窗口的最大值(难)
题目链接:https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/
单调队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
Deque<Integer> deque = new LinkedList<Integer>();
for (int i = 0; i < k; ++i) {
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offerLast(i);
}
int[] ans = new int[n - k + 1];
ans[0] = nums[deque.peekFirst()];
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();
}
ans[i - k + 1] = nums[deque.peekFirst()];
}
return ans;
}
}
总结
滑动窗口要先扩张后收缩,即先移动右边指针,再移动左边指针。
固定大小的窗口使用for循环遍历,窗口大小不固定时使用while遍历。