leetcode之滑动窗口刷题总结1
1-无重复字符的最长子串
题目链接:题目链接戳这里!!!
思路:这题是标准的滑动窗口,不过测试用例比较刁钻,需要考虑很多细节。
右侧窗口边界向右滑动,将字符串的值依次存入哈希表,如果出现重复的,则更新最大窗口和左侧窗口边界,若没出现重复,则只更新最大窗口。
AC代码如下:
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.equals("")){
return 0 ;
}
if(s.length()==1){
return 1 ;
}
int left=0, right = 0;
int max = 0 ;
Map<Character,Integer> map = new HashMap<>() ;
for(; right<s.length(); right++){
if(map.containsKey(s.charAt(right)) && map.get(s.charAt(right))>=left){
int pre = map.get(s.charAt(right)) ;
max = Math.max(max, right-left) ;
left=pre+1 ;
}else{
max = Math.max(right-left + 1, max);
}
map.put(s.charAt(right), right) ;
}
return max ;
}
}
2-存在重复元素II
题目链接:题目链接戳这里!!!
思路:滑动窗口+哈希表
右侧窗口依次向后滑动,哈希表存储元素,如果出现重复元素,则更新左侧窗口,同时比较窗口大小是否小于等于k即可。
AC代码如下:
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
int n = nums.length ;
int right = 0 ;
Map<Integer, Integer> map = new HashMap<>() ;
for(; right<n; right++){
if(map.containsKey(nums[right])){
int left = map.get(nums[right]) ;
if(nums[left]==nums[right] && Math.abs(right-left)<=k){
return true ;
}
}
map.put(nums[right], right) ;
}
return false ;
}
}
3-至少有k个重复字符的最长子串
题目链接:题目链接戳这里!!!
思路:
递归最基本的是记住递归函数的含义(务必牢记函数定义):本题的 longestSubstring(s, k) 函数表示的就是题意,即求一个最长的子字符串的长度,该子字符串中每个字符出现的次数都最少为 k。函数入参 s 是表示源字符串;kk是限制条件,即子字符串中每个字符最少出现的次数;函数返回结果是满足题意的最长子字符串长度。
递归的终止条件(能直接写出的最简单 case):如果字符串 ss的长度少于 k,那么一定不存在满足题意的子字符串,返回 0;
调用递归(重点):如果一个字符 c 在 s 中出现的次数少于 k次,那么 s 中所有的包含 c 的子字符串都不能满足题意。所以,应该在 s 的所有不包含 c 的子字符串中继续寻找结果:把 s 按照 c 分割(分割后每个子串都不包含 c),得到很多子字符串 t;下一步要求 t 作为源字符串的时候,它的最长的满足题意的子字符串长度(到现在为止,我们把大问题分割为了小问题(s → t))。此时我们发现,恰好已经定义了函数 longestSubstring(s, k) 就是来解决这个问题的!所以直接把 longestSubstring(s, k) 函数拿来用,于是形成了递归。
未进入递归时的返回结果:如果 s 中的每个字符出现的次数都大于 k 次,那么 s 就是我们要求的字符串,直接返回该字符串的长度。
class Solution {
public int longestSubstring(String s, int k) {
if(s.length()<k){
return 0 ;
}
Map<Character, Integer> count = new HashMap<>() ;
for(int i=0; i<s.length(); i++){
count.put(s.charAt(i), count.getOrDefault(s.charAt(i),0)+1) ;
}
for(char c : count.keySet()){
if(count.get(c)<k){
int res = 0 ;
for(String t : s.split(String.valueOf(c))){
res = Math.max(res, longestSubstring(t,k)) ;
}
return res ;
}
}
return s.length() ;
}
}
4-替换后的最长重复字符
题目链接:题目链接戳这里!!!
注:这一题和上一题都是字节面试原题,这题的思路是滑动窗口+哈希表,每次将字符加入哈希表,并求出字符中出现次数最多的数量maxCnt,若窗口长度减去maxCnt大于k,则替换后不满足要求,需要维护左侧窗口。
class Solution {
public int characterReplacement(String s, int k) {
Map<Character,Integer> map = new HashMap<>() ;
int left = 0, right = 0, maxCnt = 0, res=0 ;
for(;right<s.length(); right++){
map.put(s.charAt(right), map.getOrDefault(s.charAt(right), 0)+1) ; //每次将字符放入
maxCnt = Math.max(maxCnt, map.getOrDefault(s.charAt(right), 0)) ;//找出出现的最多的
if(right-left+1-maxCnt>k){//替换两个不满足要求,调整窗口
map.put(s.charAt(left), map.get(s.charAt(left))-1) ;
left++ ;
}
res = Math.max(res, right-left+1) ;//最大窗口,也就是替换满足要求的字符串长度
}
return res ;
}
}
5-最长重复子数组
题目链接:题目链接戳这里!!!
这题和最长重复子序列很像,可以用动态规划。
思路:找到递推表达式即可解出。
递推表达式如下:
dp[i][j] = dp[i-1][j-1]+1 ,当nums1[i]==nums2[j]
dp[i][j]=0,当nums1[i]!=nums2[j]
动态规划AC代码如下:
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int [][] dp = new int [nums1.length+1][nums2.length+1] ;
int max = 0 ;
for(int i=1; i<dp.length; i++){
for(int j=1; j<dp[0].length; j++){
if(nums1[i-1]==nums2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1 ;
max = Math.max(max,dp[i][j]) ;
}
}
}
return max ;
}
}
6-最长公共子序列
题目链接:题目链接戳这里!!!
为了和最长重复子数组做对比,我们看一下这个最长公共子序列,这个也是动态规划,子序列不要求连续,子数组是要求连续的。
递推表达式:
dp[i][j] = dp[i-1][j-1]+1 ,当nums1[i]==nums2[j]
dp[i][j]=max(dp[i-1][j],dp[i][j-1]),当nums1[i]!=nums2[j]
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length() ;
int n = text2.length() ;
int [][] dp = new int [m+1][n+1] ;
int ans = 0 ;
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(text1.charAt(i)==text2.charAt(j)){
dp[i+1][j+1] = dp[i][j] + 1 ;
}else{
dp[i+1][j+1] = Math.max(dp[i+1][j],dp[i][j+1]) ;
}
ans = Math.max(ans, dp[i+1][j+1]) ;
}
}
return ans ;
}
}
7-滑动窗口最大值
题目链接:题目链接戳这里!!!
先看一个常规的超时解法,785ms都超时,也是没谁了。
代码如下:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length, i= 0 ;
if(k==1){
return nums;
}
int [] res = new int [n-k+1] ;
int left = 0, right = k-1 ;
for(;right<nums.length; right++){
int max = max(nums,left,right) ;
res[i++] = max ;
left++ ;
}
return res ;
}
public int max(int [] nums, int left, int right){
int v = nums[left];
for(int i=left+1; i<=right; i++){
v = (v<=nums[i]) ? nums[i] : v ;
}
return v ;
}
}
下面看一下AC代码:
单调队列解决滑动窗口问题:
我参照的这个,链接如下:单调队列解决解析戳这里!!!
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if(n<2 || nums==null){//数组长度为1或者为空,返回数组本身
return nums ;
}
int [] res = new int [n-k+1] ; //要返回的数组
int idx = 0 ;
LinkedList<Integer> queue = new LinkedList<>() ; //单调队列
for(int i=0; i<n; i++){
if(!queue.isEmpty() && queue.peek()<=i-k){ //队首下标距离i的位置大于等于k,则出队
queue.poll() ;
}
while(!queue.isEmpty() && nums[queue.peekLast()]<=nums[i]){
queue.pollLast(); //对位元素小于当前元素,则出队
}
queue.addLast(i) ;
if(i+1>=k){ //满足返回要求
res[idx++] = nums[queue.peek()] ;
}
}
return res ;
}
}
8-字符串的排列
题目链接:题目链接戳这里!!!
思路:这题可以队s2进行截取s1长度的字符串,然后分别对s1和截取的s2进行排序,对比排序后的截取s2的字符串和字符串s1是否相等即可。
尽管可以通过,但是效率并不高,耗时超过1秒,竟然也能AC,哈哈
class Solution {
public boolean checkInclusion(String s1, String s2) {
int len = s1.length() ;
for(int i=0;i<s2.length(); i++){
if(i+len<=s2.length() && same(s1,s2.substring(i,i+len))){
return true ;
}
}
return false ;
}
public boolean same(String s1, String s2){
char [] a1 = s1.toCharArray() ;
char [] a2 = s2.toCharArray() ;
Arrays.sort(a1) ;
Arrays.sort(a2) ;
for(int i=0; i<a2.length; i++){
if(a2[i] != a1[i]){
return false ;
}
}
return true ;
}
}
使用滑动窗口法,效率会高一些,只需要一层循环即可,如果s1是s2的子串的排列,则s1中各元素的个数等于截取对应长度的s2中各元素的个数相同。
AC代码如下:
class Solution {
public boolean checkInclusion(String s1, String s2) {
//s1排列是s2的子串,则s1对应元素的个数与s2相等
int n = s1.length() ;
int m = s2.length() ;
int [] cnt1 = new int [26] ;
int [] cnt2 = new int [26] ;
if(n>m){
return false ;
}
for(int i=0; i<n; i++){
cnt1[s1.charAt(i)-'a']++ ;
cnt2[s2.charAt(i)-'a']++ ;
}
if(Arrays.equals(cnt1,cnt2)){
return true ;
}
for(int i=n; i<m; i++){
cnt2[s2.charAt(i)-'a']++ ;
cnt2[s2.charAt(i-n)-'a']--;
if(Arrays.equals(cnt1,cnt2)){
return true ;
}
}
return false ;
}
}
9-子数组最大平均数I
题目链接:
思想:前缀和+滑动窗口,就是区间和问题,再求个均值就可以,注意都要转换成double型。
AC代码如下:
class Solution {
public double findMaxAverage(int[] nums, int k) {
int right = k, sum=0;
int [] sums = new int [nums.length] ;
if(k==nums.length){
for(int x : nums){
sum += x ;
}
return 1.0 * sum / k ;
}
sums[0] = nums[0] ;
for(int i=1; i<nums.length; i++){
sums[i] = sums[i-1] + nums[i] ;
}
double max = 1.0 * sums[k-1] / k ;
for(;right<sums.length; right++){
double v = 1.0 * (sums[right] - sums[right-k])/k ;
max = Math.max(max, v) ;
}
return max ;
}
}
10-存在重复元素III
题目链接:题目链接戳这里!!!
思路:这题直接暴力也能过,就是效率有点低。
AC代码如下:
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
int n = nums.length ;
if(k>=10000){
return false ;
}
for(int i=0; i<n; i++){
for(int j=i+1; j<n; j++){
if(Math.abs((long)((long)nums[i]-(long)nums[j]))<=t && Math.abs(i-j)<=k){
return true ;
}
}
}
return false ;
}
}