滑动窗口与前缀和算法总结和例题

今天遇到两个题,其中一个用滑动窗口,另一个用前缀和,但是自己一时弄混了,对前缀和也还不熟练,在这里再总结一下。

滑动窗口

  1. 右指针平稳移动,左指针视情况移动。右先行,左视定。
  2. 多数情况下可以先对数组进行排序处理;
  3. 循环条件以及边界条件一定要处理好。
    该算法的⼤致逻辑如下:
int left = 0, right = 0;
while (right < s.size()) {`
// 增⼤窗⼝
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩⼩窗⼝
window.remove(s[left]);
left++;
}
}

其中尤其注意window needs shrink的情况,要根据题目要求来进行判断。
算法框架:

void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;

int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移⼊窗⼝的字符
char c = s[right];
// 右移窗⼝
right++;
// 进⾏窗⼝内数据的⼀系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗⼝是否要收缩
while (window needs shrink) {
// d 是将移出窗⼝的字符
char d = s[left];
// 左移窗⼝
left++;
// 进⾏窗⼝内数据的⼀系列更新
...
}
}
}

76. 最小覆盖子串

难度困难1794

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

题解:

class Solution {
    public String minWindow(String s, String t) {
        if(s.length()<t.length() || s.length()==0 || s ==null || t == null || t.length() == 0)
            return "";

        //模拟哈希表,存储目标字符串的各个字符的个数
        int[] map = new int[255];
        for(int i=0;i<t.length();i++){
            map[t.charAt(i)]++;
        }

        //双指针遍历源字符串s
        int begin = 0,end = 0;
        //最小字符串的起点
        int minBegin = 0;
        //最小字符串的长度
        int res = Integer.MAX_VALUE;
        //用来记录匹配到字符的个数,如果count == t.length()意味着找到一个匹配的字串
        int count = 0;

        //遍历
        while(end < s.length()){
            //这里可理解为缺失字符的个数,==0时则表示 这个字符匹配够了,==1则表示仍需要再匹配一个该字符
            if(map[s.charAt(end)]>0)
                count ++;
            //不需要匹配的字符,其值此时小于0,这里不需要匹配的字符一直都不会出现大于零的情况
            map[s.charAt(end)]--;
            //尾指针右移
            end++;
            //匹配到一个字串
            while(count == t.length()){
                //比较字串长度,更新字串信息
                if(end - begin < res){
                    res = end - begin;
                    minBegin = begin;
                }
                //如果首指针对应字符是目标字符之一,则跳出循环
                if(map[s.charAt(begin)] == 0)
                    count--;
                //首指针对应字符的哈希值还原
                map[s.charAt(begin)]++;
                //首指针右移
                begin++;
            }
        }

        return res == Integer.MAX_VALUE? "" :s.substring(minBegin,minBegin + res);
    }
} 

[567. 字符串的排列]判断s2是否包含s1的排列

难度中等564

给你两个字符串 s1s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false

换句话说,s1 的排列之一是 s2子串

示例 1:

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

题解:

 public boolean checkInclusion(String s1, String s2) {
int n=s1.length(),m = s2.length();
int[]arr1 = new int[26];//每个字母出现的次数
int[]arr2 = new int[26];//只要第一个数组中每个字母出现的次数等于第二个数组中每个字母出现的次数即可
if(n >m) return false;
for(int i=0;i < n;i++)
{
arr1[s1.charAt(i) - 'a']++;
arr2[s2.charAt(i)-'a']++;
}
if(Arrays.equals(arr1,arr2)) return true;//注意这个判断数组相同的函数,之前没用过
for(int i=n;i <m;i++)//必须要匹配s1的全部字符串,因此滑动窗口是固定大小的n
{
    arr2[s2.charAt(i)-'a']++;
    arr2[s2.charAt(i-n)-'a']--;
    if(Arrays.equals(arr1,arr2)) return true;
}
return false;
    }

前缀和思想

举个例子:当前数组为[1,2,3,4],那么前缀和为[1,3,6,10]。

例1 计算中心下标

例题2:
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果没有中心下标则返回-1,如果有多个则返回最左侧一个下标。如果下标在最左侧,则左侧元素和为0;下标在最右侧同理。

示例 1:
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

class Solution {
    public int pivotIndex(int[] nums) {

        for(int i=1;i<nums.length;i++){
            nums[i]+=nums[i-1];
        }

        if(nums[nums.length-1]-nums[0]==0){
            return 0;
        }

        for(int i=1;i<nums.length;i++){
            if(nums[i-1]==nums[nums.length-1]-nums[i]){
                return i;
            }
        }
        return -1;
    }
}

例2.前缀和+哈希表: [和可被 K 整除的子数组]

(https://leetcode.cn/problems/subarray-sums-divisible-by-k/)

难度中等374

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的(连续、非空) 子数组 的数目。

子数组 是数组的 连续 部分。

思路:

当循环到 i 时,往往需要前 i-1项 的和来处理。
多数结合hashmap ,不要重复就hashset。
hashmap.getOrDefault(key,0);
hashmap中有对应key值返回对应value ,否则返回0,0可以随便改,类型符合就ok。

输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

   public int subarraysDivByK(int[] nums, int k) {
        //我们可以考虑对数组进行遍历,在遍历同时统计答案。当我们遍历到第 i 个元素时,我们统计以 i 结尾的符合条件的子数组个数。我们可以维护一个以前缀和模 k 的值为键,出现次数为值的哈希表 record,在遍历的同时进行更新。这样在计算以 ii 结尾的符合条件的子数组个数时,根据上面的分析,答案即为 [0..i-1]中前缀和模 k 也为 P[i] mod k 的位置个数,即record[P[i]modk]。

int ans = 0,left=0,right=0,total = 0;
Map<Integer,Integer>map = new HashMap<>();
map.put(0,1);
for(int i = 0;i<nums.length;i++){
    total+=nums[i];
// 注意 Java 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正,这里试了一下,如果total都是正数,则用total%k也可以得出正确结果,否则要用下面的写法
  int modans = (total %k+k)%k;
  int same = map.getOrDefault(modans,0);
  ans +=same;
  map.put(modans,same+1);
  }
 
return ans;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值