面试经典 150 题 -- 滑动窗口 (总结)

面试经典150题链接

面试经典 150 题 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

209 . 长度最小的子数组

思路 : 

滑动窗口的思想,取i=j=0,向后遍历j,记录前缀和[l,r]为s,如果s>=target,那么左端点向右移动,直到s<target,维护一个[l,r]的滑动窗口,如此循环;

代码

python

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n = len(nums)
        ans = n+1
        l=0
        s=0
        for r,x in enumerate(nums):
            s+=x
            while s>=target:
                ans = min(ans,r-l+1)
                s-=nums[l]
                l+=1
        return ans if ans <= n else 0

c++

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        if(n==0) return 0 ;
        int sum = 0;
        int l = 0 , r = 0 ; 
        int ans = INT_MAX ;
        while(r < n){
            sum += nums[r];
            while(sum >= target){
                ans = min(ans , r - l + 1) ;
                sum -= nums[l++];
            }
            r++ ;
        }
        return ans == INT_MAX ? 0 : ans  ;
    }
};

 

3 . 无重复字符的最长子串

思路 : 

假设在一个无重复元素的字符串后面加上一个字符,如果出现重复元素,那么一定重复的是新加上的那个字符,那么设置一个hash表来统计次数,然后反复将窗口最前面的元素移出窗口,直到将前面与新加元素相同的元素移出时停止;然后循环更新答案即可;

lc题解地址 : 

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

代码 : 

Python

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        l = 0
        cnt = Counter()
        ans = 0
        for r , c in enumerate(s):
            cnt[c]+=1
            while cnt[c]>=2:
                cnt[s[l]]-=1
                l+=1
            ans = max(ans,r-l+1)
        return ans


C++

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
    unordered_map<char,int> hash;
    int ans=0;
    int n = s.size();
    int l=0;
    for(int r=0;r<n;r++){
        hash[s[r]]++;
        while(hash[s[r]] > 1) --hash[s[l++]];
        ans = max(ans,r-l+1);
    }
    return ans;
    }
};


java
 

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int ans = 0 ;
        char[] st = s.toCharArray();
        boolean[] has = new boolean[128] ; 
        int n = s.length();
        int i = 0 ;
        for(int j=0;j<n;j++){
            char c = st[j];
            while(has[c])
                has[st[i++]] = false;
            has[c] = true;
            ans = Math.max(ans, j-i+1);
        }
        return ans;
    }
}

30 . 串联所有单词的子串

思路 : 

滑动窗口,细节看代码

代码 : 

class Solution {
public:
    vector<int> findSubstring(string &s, vector<string> &words) {
        // 一个窗口包含s中的前a个单词;
        // 先删去前 i (i=0∼b−1)个字母后,
        // 将剩下的字母进行划分,如果末尾有不到 b 个字母也删去。
        vector<int> ans ;
        int a = words.size() , b = words[0].size() , n = s.size() ;
        for(int i=0;i<b&&i+a*b<=n;i++){ // 对应b种划分方法
            unordered_map<string,int> mp;
            for(int j=0;j<a;j++){//将s从i开始的a个长度为b的字符串放入哈希表中
                ++mp[s.substr(i+j*b,b)];
            }
            for(string& str : words){//将words中a个的字符串放入哈希表中,放入一个,对应的频次-1
                if(--mp[str]==0){
                    mp.erase(str);
                }
            }
            for(int start=i;start<n-a*b+1;start+=b){
                // 非第一个窗口
                if(start!=i){//因为上面已经考虑过start==i的情况了,这里直接跳过
                    string in = s.substr(start+(a-1)*b,b);//划入新的字符串
                    if(++mp[in]==0){//划入滑动窗口,就++
                        mp.erase(in);
                    }
                    string out = s.substr(start-b,b);
                    if(--mp[out]==0){
                        mp.erase(out);
                    }
                }
                if(mp.empty()){// 若窗口内单词与单词表中单词出现频次差皆为0,则为解
                    ans.push_back(start);
                }
            }
        }
        return ans ;
    }
};

76 . 最小覆盖子串

滑动窗口 : 

定义两个长度为 606060(足够存下所有字母种类)的数组 c1 和 c2,用于存储字符频率。其中 c1 用于记录字符串 t 中字符的频率,c2 用于记录当前滑动窗口内字符的频率。

设定好字母与频率数组下标的映射关系:小写字母 a-z 对应下标 0-25,大写字母 A-Z 对应下标 26-51。

使用变量 tot 来记录还需要匹配的字符种类数,当 tot = 0 代表当前滑动窗口对应的子串能够实现对 t 的覆盖,即任意字符满足 c2[i]≥c1[i]c2[i] \geq c1[i]c2[i]≥c1[i]。

使用双指针 j 和 i 表示滑动窗口的左右边界。从前往后遍历字符串 s,在每个位置上更新字符频率数组 c2。若 c2 中字符的频率达到了 c1 中的字符频率,则将 tot 减 1,表示一个字符已经匹配完成。

每当右边界往后移动一步之后,滑动窗口会增加一个字符。此时我们检查左边界能否右移,同时不会使得 tot 变大。即每次右边界右移后,我们检查左边界 c2[j]>c1[j]c2[j] > c1[j]c2[j]>c1[j] 是否满足:

若满足:说明当前左边界指向字符并非必须,当前子串 s[j...i]s[j...i]s[j...i] 必然不是最短子串。我们让左边界 j 进行右移,并重复进行左边界 c2[j]>c1[j]c2[j] > c1[j]c2[j]>c1[j] 的检查,直到窗口不能再收缩
若不满足:说明当前窗口没有任何一个后缀字符串能够实现对 t 的覆盖,我们并不能对窗口实现收缩
每次对窗口移动完成后,我们检查当前 tot 是否为 000(对字符串 t 的覆盖是否完成),若为 000 则尝试用当前窗口对应的字符串 s[j...i]s[j...i]s[j...i] 更新 ans。

class Solution {
public:
    int get(char x) {
            return x >= 'A' && x <= 'Z' ? x - 'A' + 26 : x - 'a';
    }
    string minWindow(string s, string t) {
        int n = s.size() , cnt = 0 ;// cnt : 还需要匹配的字符种类数
        // 规定 a-z : 0-25  ,  A-Z : 26-51
        // c1 用于记录字符串 t 中字符的频率,c2 用于记录当前滑动窗口内字符的频率
        vector<int> c1(60),c2(60);
        for(char c : t) if(++c1[get(c)]==1) cnt ++ ;
        string ans = "" ;
        for(int i=0,j=0;i<n;i++){
            int idx1 = get(s[i]);//将s[i]加入窗口
            if(++c2[idx1]==c1[idx1]) cnt --;
            while(j<i){
                int idx2 = get(s[j]);
                // 尝试将s[j]划出窗口
                if(c2[idx2]>c1[idx2] && --c2[idx2]>=0){// 能够滑出窗口
                    //;
                    j++;
                }else{
                    break;//不能够滑出窗口
                }
            }
            if(cnt==0 && (ans.empty() || ans.size()>i-j+1)) ans = s.substr(j,i-j+1);
        }
        return ans ;
    }
};

相似题目 : 

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

  • 36
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一些 Flink 面试及其答案,供您参考: 1. 什么是 Flink? Flink 是一个开源的流处理框架,它提供了高性能、稳定可靠、易于使用的流处理引擎,支持在大规模数据处理时进行实时计算和批处理。 2. Flink 的核心原理是什么? Flink 的核心原理是基于流式数据流处理,它将数据流分成若干个事件流,每个事件流都有自己的处理逻辑,最后将处理后的结果合并成最终结果。 3. Flink 的窗口有哪些类型? Flink 的窗口有以下几种类型: - 时间窗口:按照时间间隔划分数据流。 - 计数窗口:按照数据条数划分数据流。 - 滑动窗口:在时间窗口或计数窗口的基础上增加了一个滑动的步长。 - 会话窗口:按照会话时间间隔划分数据流。 4. Flink 支持哪些语言? Flink 支持 Java 和 Scala 两种语言。 5. Flink 和 Spark Streaming 有什么区别? Flink 和 Spark Streaming 都是实时计算框架,但是它们有以下几个区别: - Flink 更加注重实时计算的性能和稳定性,支持更低的延迟和更高的吞吐量。 - Flink 支持按照事件时间进行窗口计算,而 Spark Streaming 只支持按照处理时间进行计算。 - Flink 支持状态后端的切换,而 Spark Streaming 只支持基于内存的状态存储。 - Flink 支持更多的数据源和 Sink,例如 Apache Kafka、RabbitMQ、Elasticsearch 等,而 Spark Streaming 只支持少量的数据源和 Sink。 6. Flink 的高可用机制是怎样的? Flink 的高可用机制主要包括以下几个方面: - Master 选举:Flink 的 JobManager 采用了 ZooKeeper 进行主从节点的选举,保证当主节点失败时可以快速切换到备用节点。 - 容错机制:Flink 采用了 checkpoint 机制,将流式计算的中间状态保存在分布式文件系统上,当程序出现故障时可以快速恢复。 - 多重副本:Flink 采用了多重副本机制,将数据在多个节点上保存多份,避免因为节点故障导致数据丢失。 7. Flink 的数据流转过程是怎样的? Flink 的数据流转过程包括以下几个阶段: - 数据源:从数据源读取数据。 - 数据转换:对数据进行处理、过滤、聚合等操作。 - 窗口计算:按照窗口对数据进行分组和计算。 - 数据 Sink:将计算结果写入外部存储系统。 8. Flink 的优势是什么? Flink 的优势主要包括以下几个方面: - 支持流式计算和批处理,可以处理大规模的数据流和数据集。 - 支持事件时间窗口计算,保证了计算的准确性。 - 支持多重数据源和 Sink,可以与多种存储系统集成。 - 支持高可用和容错机制,保证计算的稳定性和可靠性。 - 支持灵活的编程模型和 API,易于使用和扩展。 - 支持低延迟和高吞吐量的实时计算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值