【算法笔记】数组篇-双指针以及滑动窗口

前言

本篇重点练习数组删除相关的例题,主要介绍两种方法双指针法、滑动窗口。

例题

  1. 移除元素

分析:解1 暴力解法:从前往后遍历,遇到相等的就整体往前
后面的挪到前面 位置发生了变化
时间复杂度O(n2);空间复杂度O(1)

 int removeElement(vector& nums, int val) {
    
        int len=nums.size();
        
        for(int i=0;i

解2 双指针法:设两个指针p1 p2,初始值都为0,如果不等,p1 p2同时后移;如果遇到相等的,p1不动,p2后移到不等的位置,然后覆盖掉相等的,p1 p2 同时后移。
最后p1就是去除val后的长度。
时间复杂度O(n);空间复杂度O(1)

int removeElement(vector& nums, int val) {
        int len=nums.size();
        int p1=0,p2=0;
        for(p2=0;p2
  1. 删除排序数组中的重复项

分析:此题和上一题差不多,如果还是采用暴力解法肯定会超时。可以采用双指针法。

 int removeDuplicates(vector& nums) {
        int len=nums.size();
        if(len<=1){
            return len;
        }
        int p1=1;
        int p2=1;
        for(;p2

3. 移动零

分析:暴力解法:直接挪动(效率很低)

void moveZeroes(vector& nums) {
        int len=nums.size();
        for(int i=0;i

双指针 和第一个基本一样,区别在于此题要在末尾填0

 void moveZeroes(vector& nums) {
        int len=nums.size();
        int cur=0;
        int fin;
        for(fin=0;fin

4. 比较含退格的字符串

补充string的相关函数
length获取字符串长度
s.substr(pos, n) 截取s中从pos开始(包括0)的n个字符的子串,并返回

分析:此题也可以用双指针,只是在遇到‘#’时cur指针要回退,其他的不变。

class Solution {
public:
        string swap(string s) {//转换字符串即去掉#
        int len = s.length();
    
        int cur = 0;
        int fin = 0;
        for (; fin < len; fin++) {
            if (s[fin] != '#') {
                s[cur++] = s[fin];
            }
            else {
                if (cur != 0) {
                    cur--;
                }

            }

        }
        s = s.substr(0, cur);//只取前cur个字符
        return s;
    }
    bool backspaceCompare(string s, string t) {
        s = swap(s);
        t = swap(t);
    
        if (s == t) {
            return true;
        }
        else{
            return false;
        }
    
    }
};

5.有序数组的平方

解1:先求平方,然后排序

class Solution {
public:
    vector sortedSquares(vector& nums) {
        int len=nums.size();
        for(int i=0;i

解2:双指针法,设两个指针,平方后的最大值一定在两端,所以可以新设一个与原数组一样大小的数组,从大往小即从后往前求。

class Solution {
public:
    vector sortedSquares(vector& nums) {
        int len=nums.size();
        vector re(len);
        int k=len;
        int p1=0;
        int p2=len-1;
        while(k--){
            if(nums[p2]*nums[p2]>nums[p1]*nums[p1]){
                re[k]=nums[p2]*nums[p2];
                p2--;
            }
            else{
                re[k]=nums[p1]*nums[p1];
                p1++;
            }
        }
        return re;
    }
};

6 长度最小的子数组

分析:解1: 暴力解法 设两个循环,外层循环表示开始的位置,内层循环计算子数列的长度
时间复杂度 O(n2)

class Solution {
public:
    int minSubArrayLen(int target, vector& nums) {
        int len=nums.size();
        int re=1000000;
        int sublen=0;
        int sum=0;
        for(int i=0;i=target){//找到符合的子数列就更新当前的结果
                    re=re

解2 介绍一种新的方法 滑动窗口,其实也不能说是新方法,就是双指针的变形,本质上是一致的。
设两个指针不断调整开始位置与结束位置。
时间复杂度O(n)

class Solution {
public:
    int minSubArrayLen(int target, vector& nums) {
        int len=nums.size();
        int be=0;//开始位置
        int en=0;//结束位置
        int sum=0;//子数列和
        int sublen=0;//子数列长度
        int re=1000000;//最终结果
        for(;en=target){//找到符合要求的,即更新re,同时调整窗口的开始位置(这里一定要用循环来找,因为be后移可能还是符合的)
                sublen=en-be+1;
                re=re

扩张条件:curSum 收缩的条件:curSum>=target
收集结果的时刻:在收缩时

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int l=0;
        int r=0;
        int re=INT_MAX;
        int curSum=0;
        while(r<nums.size()){//左闭右开区间,当累加和小于target就扩张,当累加和大于等于target时就收缩
            curSum+=nums[r++];
            if(curSum<target){
                continue;
            }
            while(curSum>=target){//收缩条件:curSum>=target
                re=re<=r-l?re:r-l;
                curSum-=nums[l++];
            }
          
        }
        if(re!=INT_MAX){
            return re;
        }
        return 0;
    }
};

7. 水果成篮

分析:此题关键就是如何确定窗口的开始位置和结束位置,这里我先找可以放的两个类别,然后通过判断当前类别是否在刚才找的类别中,若在,说明可以放篮子里,en后移;若不在,说明篮子放不下了,因此be后移。然后重新找可以放的两个类别……
扩张的条件:进入窗口的水果是两类水果中的一类
收缩的条件:正好与扩张相反,当前水果不能再放入篮子了
收集结果的时机:由于是求最大值,当然是在扩张时收集结果

class Solution {
public:
    int totalFruit(vector& fruits) {
        int len=fruits.size();
        int be=0;//开始位置,左闭右开区间[be,en)
        int en=0;//结束位置
        int re=0;//最终结果
        int sum=0;//中间结果
        int first;//篮子里第1种水果
        int second;//篮子里第2种水果
        for(;be0 && fruits[be-1]==fruits[be]){//优化1
       			continue;
       		}
            first=fruits[be];//找篮子里可以放的第1种水果
            for(int j=be+1;jsum?re:sum;
                
                en++;
                if(en==len){//en已到末尾,不可能再有大的了,//优化2
                    return re==0?0:re;
                }
            }
        }
        return re;
    }
};

76. 最小覆盖子串
扩张:窗口中还未包含t中所有字符
收缩:与扩张相反

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int>tMap;//t的词频
        unordered_map<char, int>window;//s的词频(仅统计t中出现的字符)
        int l = 0;
        int r = 0;
        int k = 0;//窗口中包含t中字符的个数(>=)
        int change = 0;
        string re = s;
        for (char c : t) {
            tMap[c]++;
        }
        while (r < s.size()) {

            if (k < tMap.size()) {//还未包含t中所有字符
                char c = s[r];
                ++r;
                if (tMap.count(c)) {
                    window[c]++;
                    if (window[c] == tMap[c]) {
                        ++k;
                    }
                }
            }
            
            while (k == tMap.size()) {
                char d = s[l];
                if ((r - l) <= re.size()) {
                    change = 1;
                    string newStr = s.substr(l,r-l);
                    re = newStr;
                }

                if (tMap.count(d)) {
                    window[d]--;
                    if (window[d] < tMap[d]) {
                        --k;
                    }
                }
                ++l;

            }

        }
        return change==1 ? re : "";//如果change==0,说明s中不存在子串
    }
};

总结

双指针和滑动窗口可以大大降低时间复杂度,针对数组的删除问题用双指针会比较快;滑动窗口非常巧妙,通过调整开始与结束位置,难的是如何确定窗口的开始与结束,在纸上模拟一下可能会有妙计。

你可能感兴趣的:(算法,算法,leetcode,c++)