C++ 数据结构与算法(三)(滑动窗口、模拟)

数组 滑动窗口

209.长度最小的子数组●●

给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr],并返回其长度。
如果不存在符合条件的子数组,返回 0 。

1、暴力解法

逐个寻找以 n u m s [ i ] nums[i] nums[i] ( i ++ ) 开头 且满足条件的数组,比较长度。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int ans = n + 1;
        for (int i = 0; i < n; i++){
            int sum = nums[i];
            if (sum >= target){					// 存在大于或等于 target 的数字,则得到最小长度为 1
                return 1;
            }
            for (int j = i+1; j < n; j++){		// 逐个寻找以 nums[i] 开头 且满足条件的数组
                sum += nums[j];
                if (sum >= target){
                    if (j-i+1 < ans){			// 取更小长度
                        ans = j-i+1;
                    }
                    break;
                }
            }
        }
        if (ans > n){		// 未找到满足条件的数组
            ans = 0;	
        }
        return ans;
    }
};
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)
2、滑动窗口

滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出结果。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。

  • 时间复杂度: O ( n ) O(n) O(n) :看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被被操作两次,所以时间复杂度是 2 × n 也就是 O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

在这里插入图片描述

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

904. 水果成篮●●

最多含有两个不同字符的最长子串

思路:(双指针(或者说是 滑动窗口))
右指针遍历数组,判断当前位置是否符合采摘条件,如符合则水果个数加1;如不符合则直接更新当前右指针结尾时的两种水果种类(即 right 与 right -1 ),然后将左指针移到 right - 1 处并往前遍历,进行采摘判断;直到右指针遍历完成。

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        if (n<3) return n;                                      // 少于3直接返回树的棵树
        int left = 0;
        int ans = 2;                                            // 最少 2 个水果
        int a = fruits[0], b;                                   // a,b存放当前的两种水果
        for(int right = 0; right < n; right++){                 // 右指针遍历
            if (fruits[right] == a || fruits[right] == b){      
                if(right-left+1 > ans) ans = right-left+1;      // 如果当前位置的水果符合采摘条件,则直接比较水果个数
            }
            else{                                               // fruits[right]为第三种水果,不符合采摘条件
                a = fruits[right-1];                            // 更新当前位置的水果种类,即为 right 与 right -1 位置的水果
                b = fruits[right];
                left = right - 1;                               // 将左指针移到 right - 1 处,并往前遍历判断
                while (left >= 1 && (fruits[left-1] == a || fruits[left-1] == b)){  
                        left--;                                 // 当左指针前一位符合采摘条件,则左移一位并比较水果个数
                        if(right-left+1 > ans) ans = right-left+1;                   
                }
            }
        }                                                       // 直到右指针遍历完成
        return ans;
    }
};

76. 最小覆盖子串●●●

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


注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量
如果 s 中存在这样的子串,我们保证它是唯一的答案。


输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”

数组 exit 记录 t 中存在的字母,
数组 cnts 表示当前字母的剩余覆盖数量(负数是表示窗口内该字母冗余),
整数 rest 记录当前剩余覆盖的字母数(>= 0)。

class Solution {
public:
    string minWindow(string s, string t) {
        int sl = s.length(), tl = t.length();
        if(sl < tl) return "";
        int rest = tl;
        int cnts[129] = {0};			// 记录字母个数
        bool exit[129] = {0};			// 记录字母是否存在
        for(char ch : t){
            ++cnts[ch];					// 统计字符串 t 中的字母
            exit[ch] = true;
        }
        int minLen = sl + 1;			// 最小长度
        int start = -1;					// 对应的子串开头
        int left = 0;					// 左指针
        for(int right = 0; right < sl; ++right){
            if(exit[s[right]]){			// 如果当前字母存在 t 中,那么维护cnts数组,和剩余覆盖的字符数量 rest
                --cnts[s[right]];
                if(cnts[s[right]] >= 0)--rest;	// 排除某字母cnts为负数的情况
            }
            while(rest == 0){					// 成功覆盖的情况,移动左指针
                if(minLen > right-left+1){		// 判断最小长度,更新子串开头
                    minLen = right-left+1;
                    start = left;
                }
                if(exit[s[left]]){				// 对移出窗口的左指针字母进行统计
                    ++cnts[s[left]];
                    if(cnts[s[left]] > 0) ++rest;
                }
                ++left;
            }
        }
        if(start == -1) return "";
        return s.substr(start, minLen);			// 返回子串
    }
};

模拟

59. 螺旋矩阵 II ●●

给你一个正整数 n ,生成一个包含 1 到 n 2 n^2 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix .

在这里插入图片描述

1、一圈一圈循环填充

坚持循环不变量原则,模拟顺时针画矩阵的过程:

  • 填充行从左到右
  • 填充列从上到下
  • 填充行从右到左
  • 填充列从下到上

按照固定规则来遍历,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

按照左闭右开的原则画一圈:
在这里插入图片描述

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度:O(1)
class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> ans(n,vector<int>(n,0));// 二维矩阵
        int times = n / 2;							//循环的圈数
        int value = 1;								// 赋值
        for(int i = 0; i < times; i++){				// 第 i 圈模拟,【左闭右开,注意边界条件】,起点即为(i, i)
            for(int j = i; j < n-i-1; j++){			// 上行 从左到右
                ans[i][j] = value++;
            }
            for(int j = i; j < n-i-1; j++){			// 右列 从上到下
                ans[j][n-i-1] = value++;
            }
            for(int j = n-i-1; j > i; j--){			//下行 从右到左
                ans[n-i-1][j] = value++;
            }
            for(int j= n-i-1; j > i; j--){			// 左列 从下到上
                ans[j][i] = value++;
            }
        }
        if(n%2>0) ans[n/2][n/2] = n*n;				// 奇数时 中点赋值
        return ans;
    }
};
2、四条边顺序填充、移动边界、判断总数
  • 生成一个 n×n 空矩阵 ans,随后模拟整个向内环绕的填入过程:
  • 定义当前左右上下边界 l,r,t,b,初始值 num = 1,迭代终止值 numsize = n * n;
  • 当 num <= numsize 时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环,每次填入后:
  • 执行 num += 1:得到下一个需要填入的数字;
  • 更新边界:例如从左到右填完后,上边界 t += 1,相当于上边界向内缩 1。
  • 使用 num <= numsize 而不是 l < r || t < b 作为迭代条件,是为了解决当 n 为奇数时,矩阵中心数字无法在迭代过程中被填充的问题
  • 最终返回 ans 即可。

在这里插入图片描述

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        int top = 0, right = n-1, left = 0, buttom = n-1;
        int numsize = n*n;
        int num = 1;
        vector<vector<int>> ans(n, vector<int>(n));
        while(true){
            for(int i = left; i <= right; i++){
                ans[top][i] = num++;
            }
            top++;
            if(num>numsize) break;		// 填充 判断总数

            for(int i = top; i <= buttom; i++){
                ans[i][right] = num++;
            }
            right--;
            if(num>numsize) break;

            for(int i = right; i >= left;  i--){
                ans[buttom][i] = num++;
            }
            buttom--;
            if(num>numsize) break;

            for(int i = buttom; i >= top; i--){
                ans[i][left] = num++;
            }
            left++;
            if(num>numsize) break;
        }
        return ans;
    }
};

54. 螺旋矩阵 ●●

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

四条边按顺序遍历,移动、判断边界。

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        int m = matrix.size();          // m 行
        int n = matrix[0].size();       // n 列
        int top = 0, right = n-1, left = 0, buttom = m-1; //边界索引值
        int numsize = m*n;
        int num = 0;
        vector<int> ans(numsize);
        while(true){
            for(int i = left; i <= right; i++){
                ans[num++] = matrix[top][i];
            }
            top++;
            if(top>buttom) break;			// 遍历  判断边界

            for(int i = top; i <= buttom; i++){
                ans[num++] = matrix[i][right];
            }
            right--;
            if(left>right) break;

            for(int i = right; i >= left;  i--){
                ans[num++] = matrix[buttom][i];
            }
            buttom--;
            if(top>buttom) break;

            for(int i = buttom; i >= top; i--){
                ans[num++] = matrix[i][left];
            }
            left++;
            if(left>right) break;
        }
        return ans;
    }
};

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

  • 暴力:
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0;
        int right = 0;
        int ans = 0;
        int n = s.length();
        int i;
        while(right < n){
            for(i = left; i < right; i++){	// 循环遍历判断当前子串是否存在重复字符
                if(s[right] == s[i]){		// 可用哈希集合来判断,从而减少时间复杂度
                    left = i + 1;
                    break;
                }
            }
            if(i >= right - 1){
                ans = max((right - left + 1), ans);
                right++;
            } 
        }
        return ans;
    }
};
  • 哈希集合判断是否重复,减少时间复杂度
  • 时间复杂度:O(N),其中 N 是字符串的长度。左指针和右指针分别会遍历整个字符串一次。
  • 空间复杂度:O(∣Σ∣),其中 Σ 表示字符集(即字符串中可以出现的字符),∣Σ∣ 表示字符集的大小。在本题中没有明确说明字符集,因此可以默认为所有 ASCII 码在 [0,128) 内的字符,即 ∣Σ∣=128。
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0;
        int right = -1;                     // 确保right从零遍历
        int ans = 0;
        int n = s.length();
        unordered_set<char> occ;
        int i;
        while(right < n-1){       
            if (occ.count(s[right+1])){     // 右移会出现重复字符
                occ.erase(s[left]);         // 哈希集合中移除left
                left++;                     // left 右移
            }
            else{
                right++;                    // 右移不会出现重复字符
                occ.insert(s[right]);
                ans = max((right - left + 1), ans);
            }
        }
        return ans;
    }
};
  • 哈希数组
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        bool cnts[128] = {false};
        int ret = 0, left = 0;
        for(int right = 0; right < s.length(); ++right){
            while(cnts[s[right]] == true){
                cnts[s[left++]] = false;
            }
            cnts[s[right]] = true;
            ret = max(ret, right-left+1);
        }
        return ret;
    }
};
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值