滑动窗口
class Solution:
def problemName(self, s: str) -> int:
# Step 1: 定义需要维护的变量们 (对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
x, y = ..., ...
# Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
start = 0
for end in range(len(s)):
# Step 3: 更新需要维护的变量, 有的变量需要一个if语句来维护 (比如最大最小长度)
x = new_x
if condition:
y = new_y
'''
------------- 下面是两种情况,读者请根据题意二选1 -------------
'''
# Step 4 - 情况1
# 如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否达到了限定长度
# 如果达到了,窗口左指针前移一个单位,从而保证下一次右指针右移时,窗口长度保持不变,
# 左指针移动之前, 先更新Step 1定义的(部分或所有)维护变量
if 窗口长度达到了限定长度:
# 更新 (部分或所有) 维护变量
# 窗口左指针前移一个单位保证下一次右指针右移时窗口长度保持不变
# Step 4 - 情况2
# 如果题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
# 如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
# 在左指针移动之前更新Step 1定义的(部分或所有)维护变量
while 不合法:
# 更新 (部分或所有) 维护变量
# 不断移动窗口左指针直到窗口再次合法
# Step 5: 返回答案
return ...
643 子数组最大平均数 滑动窗口
找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数
//输入:nums = [1,12,-5,-6,50,3], k = 4
//输出:12.75
//解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75
public double findMaxAverage(int[] nums, int k) {
double sum = 0;
double max_avg = Integer.MIN_VALUE;
int start = 0;
for(int end = 0; end < nums.length; end++){
sum += nums[end];
if(end-start+1 == k){
max_avg = Math.max(max_avg,sum/(double)k);
}
if(end >= k-1){
sum -= nums[start];
start++;
}
}
return max_avg;
}
C++:
#include "algorithm"
#include <vector>
using namespace std;
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double max_avg = INT_MIN;
double sum = 0;
int start = 0;
for (int end = 0; end < nums.size(); end++) {
sum += nums[end];
if (end - start + 1 == k) {
max_avg = max(max_avg, sum / (double)k);
}
if (end >= k-1) {
sum -= nums[start];
start++;
}
}
return max_avg;
}
};
mid-3 无重复字符的最长子串 滑动窗口
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/yi-ge-mo-ban-miao-sha-10dao-zhong-deng-n-sb0x/
给定一个字符串,找出不含有重复字符的最长子串的长度。
每遇见一个字符,就判断该字符在起点到当前位置是否出现过,如果出现过,则更新子串的长度,然后将起点更新为该字符在起点之后出现的第一个位置。
Java:
class Solution {
public int lengthOfLongestSubstring(String s) {
int maxlen = 0;//滑动窗口的长度
int left = 0,right = 1;
if(s.length() <= 1) return s.length();
for(; right < s.length(); right++){
if(s.indexOf(s.charAt(right),left) < right){ // 表示在右窗口之前存在和右窗口相同的元素
maxlen = Math.max(maxlen, right-left); //对于完全没有重复元素的,maxlen不会更新
left = s.indexOf(s.charAt(right),left) + 1; //左窗口滑动到出现重复字符位置后一位
}
}
return Math.max(maxlen,right-left);
}
}
c++:
#include <vector>
#include <string>
#include <algorithm>
int lengthOfLongestSubstring(string s) {
int len = s.size();
if(len <= 1) return len;
int left = 0;
int maxSubLen = 0;
int right = 1;
while (right < len) {
string substr = s.substr(left);
int findIdx = substr.find(s[right]) + left;
if (findIdx == -1) continue;
if (findIdx < right) {
maxSubLen = max(maxSubLen, right - left);
left = findIdx + 1;
}
right++;
}
return max(maxSubLen, right-left);
}
mid-209 长度最小的子数组 滑动窗口
// 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长
//度。如果不存在符合条件的子数组,返回 0
Java:
public int minSubArrayLen(int target, int[] nums) {
int minlen = Integer.MAX_VALUE;
int sum = 0;
int start = 0;
for(int end = 0; end < nums.length; end++){
sum += nums[end];
if(sum >= target){
minlen = Math.min(minlen,end-start+1);
}
while (sum >= target){
sum -= nums[start];
minlen = Math.min(minlen, end-start+1);
start++;
}
}
if(minlen == Integer.MAX_VALUE) minlen = 0;
return minlen;
}
c++:
int minSubArrayLen(int target, vector<int>& nums) {
int min_len = INT_MAX;
int sum = 0;
int start = 0;
for (int end = 0; end < nums.size(); end ++) {
sum += nums[end];
if (sum >= target) {
min_len = min(min_len, end-start+1);
}
while (sum >= target) {
sum -= nums[start];
min_len = min(min_len, end-start+1);
start++;
}
}
return min_len == INT_MAX ? 0 : min_len;
}
mid-1695 删除子数组的最大得分 滑动窗口
// 给你一个正整数数组 nums ,请你从中删除一个含有 若干不同元素 的子数组(子数组是连续子序列)。删除子数组的 得分 就是子数组各元素之 和 。
// 返回 只删除一个 子数组可获得的 最大得分 。
Java:
public int maximumUniqueSubarray(int[] nums) {
int max_sum = 0;
int sum = 0;
int start = 0;
for(int end = 0; end < nums.length; end++){
sum += nums[end];
if(!contain(nums,nums[end],start,end)){
max_sum = Math.max(max_sum, sum);
}
while (contain(nums,nums[end],start,end)){
sum -= nums[start];
start++;
}
}
return max_sum;
}
public boolean contain(int[] arr, int num, int begin, int end){
for(int i = begin; i < end; i++){
if(arr[i] == num) return true;
}
return false;
}
c++用这个方法会在用例1~10000时运行超时
int maximumUniqueSubarray(vector<int>& nums) {
int sum = 0;
int max_sum = 0;
int start = 0;
vector<bool> exist(10001);
for (int end = 0; end < nums.size(); end++) {
while (exist[nums[end]]) {
sum -= nums[start];
exist[nums[start]] = false;
start++;
}
sum += nums[end];
exist[nums[end]] = true;
max_sum = max(max_sum, sum);
}
return max_sum;
}
mid-438 找到字符串中所有字母异位词
// 给定两个小写字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
// 异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res = new ArrayList<Integer>();
//用来统计每个字母出现的次数,如果出现次数相同,就是异位词
//对于小写或全大写可用26大小数组
int[] count1 = new int[26];
int[] count2 = new int[26];
for(int i = 0; i < p.length(); i++){
count1[p.charAt(i) - 'a'] ++;
}
int start = 0;
for(int end = 0; end < s.length(); end++){
count2[s.charAt(end) -'a'] ++;
if(end > p.length()){
count2[s.charAt(start)-'a']--;
start++;
}
if(check(count1,count2)){
res.add(start);
}
}
return res;
}
public boolean check(int[] arr1, int[] arr2){
for(int j = 0; j < arr1.length; j++){
if(arr1[j] != arr2[j]) return false;
}
return true;
}
mid-567 字符串的排列
// 给你两个小写字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
public boolean checkInclusion(String s1, String s2) {
int[] count1 = new int[26];
int[] count2 = new int[26];
int start = 0;
for(int i = 0; i < s1.length(); i++){
count1[s1.charAt(i)-'a']++;
}
for(int end = 0; end < s2.length(); end++){
count2[s2.charAt(end)-'a']++;
//先滑动窗口
if(end > s1.length()-1){
count2[s2.charAt(start)-'a']--;
start++;
}
//复用上一题的check
if(check(count1,count2)) return true;
}
return false;
}
mid-1004 最大连续1的个数III
可以翻转最多k个0
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
class Solution {
public int longestOnes(int[] nums, int k) {
int maxlen = 0;
int count = 0;
int start = 0;
for(int end = 0; end < nums.length; end++){
if(nums[end] == 0) count++;
if(count <= k){
maxlen = Math.max(maxlen,end-start+1);
}
while(count > k){
if(nums[start] == 0) count--;
start++;
}
}
return maxlen;
}
}
mid-1208 尽可能使字符串相等
// 输入:s = “abcd”, t = “bcdf”, maxCost = 3
// 输出:3
// 解释:s 中的 “abc” 可以变为 “bcd”。开销为 3,满足≤maxcost,所以输出abc的长度为 3。
//建立开销数组 cost[i] = t[i]-s[i] --> {1,1,1,2} 连续子序列和≤maxcost
// “krrgw” “zjxss” 19
// cost是绝对值
//
public int equalSubstring(String s, String t, int maxCost) {
int[] cost = new int[s.length()];
for(int i = 0 ; i < s.length(); i++){
cost[i] = Math.abs(t.charAt(i) - s.charAt(i));
}
int sum = 0;
int maxlen = 0;
int start = 0;
for(int end = 0; end < s.length(); end++){
sum += cost[end];
if(sum <= maxCost){
maxlen = Math.max(maxlen, end-start+1);
}
while(sum > maxCost){
sum -= cost[start];
start++;
}
}
return maxlen;
}
mid-1423 可获得的最大点数
思路:转化为求最小子序列和,然后总和 - 最小子序列和
public int maxScore(int[] cardPoints, int k) {
int sum = 0;
for(int i = 0; i < cardPoints.length; i++){
sum += cardPoints[i];
}
int minsum = Integer.MAX_VALUE;
int subsum = 0;
int start = 0;
for(int end = 0; end < cardPoints.length; end++){
subsum += cardPoints[end];
if(end > cardPoints.length-k-1){
subsum -= cardPoints[start];
start ++;
}
if(end - start + 1 == cardPoints.length-k){
minsum = Math.min(minsum,subsum);
}
}
if(minsum == Integer.MAX_VALUE) minsum = 0;
return sum-minsum;
}
459 重复的子字符串
public boolean repeatedSubstringPattern(String s) {
String tmp = (s + s).substring(1, s.length() * 2 - 1);
return tmp.contains(s);
}
mid-5 最长回文子串 中心扩散
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。
遍历每个元素,将每个元素作为回文中心并扩展,直到不满足回文条件,得到该元素为中心的回文最大长度。
这道题的关键是要从中间扩展回文串,还要注意回文串的长度为奇数和偶数两种情况,在更新start时要注意。
时间复杂度:O(n^2),其中 n 是字符串的长度。长度为 1 和 2 的回文中心分别有 n 和 n−1 个,每个回文中心最多会向外扩展 O(n) 次。
空间复杂度:O(1)。
public String longestPalindrome(String s) {
if(s.length() <= 1) return s;
int start = 0, end = 0;
for(int i = 0; i < s.length(); i++){
int len = expand(s,i,i); //对于奇数长度的aba
int len2 = expand(s,i,i+1); //对于偶数的abba
int lenmax = Math.max(len,len2);
if(end - start < lenmax){
start = i - (lenmax-1)/2;
//对于偶数的子串,start会定位到前一个,因为i在偏左位置
end = i + lenmax/2;
}
}
return s.substring(start,end+1); //右边界不包含
}
public int expand(String str, int left, int right){
while (left >= 0 && right < str.length()
&& str.charAt(left) == str.charAt(right)){
left--;
right++;
}
return right-left-1;
}