算法练习:同向双指针(滑动窗口)

news2024/10/1 21:42:54

目录

  • 1. 长度最小的数组
  • 2. 无重复字符的最长字符串
  • 3. 最大连续1的个数
  • 4. 将x减到0的最小操作数
  • 5. 水果成篮
  • 6. 找到字符串中所有字母异位词
  • 7. 串联所有单词的子串
  • 8. 最小覆盖子串

1. 长度最小的数组

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接: 长度最小的数组
  3. 思路:
    <1> 暴力枚举法:时间复杂度O( n 2 n^2 n2)
    <2> 同向双指针:时间复杂度O(n)

同向双指针:

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

        return len;
    }
};

优化:

class Solution 
{
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int count = INT_MAX;
        int sum = 0;
        for(int right = 0, left = 0; right < nums.size(); right++)
        {
            sum += nums[right];

            while(target <= sum)
            {
                if(count > right - left + 1)
                {
                    count = right - left + 1;
                }

                sum -= nums[left++];
            }

        }

        return count == INT_MAX ? 0 : count;
    }
};

2. 无重复字符的最长字符串

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    无重复字符的最长字符串
  3. 思路:(哈希表)+ 滑动窗口
class Solution 
{
public:
    int lengthOfLongestSubstring(string s) 
    {
        //简单代替哈希表
        int hash[128] = {0};
        int left = 0;
        int right = 0;
        int s_len = 0;
        int len = 0;
        int size = s.length();
        while(right < size)
        {
            hash[s[right]]++;
            len++;
            while(hash[s[right]] >= 2)
            {
                hash[s[left]]--;
                left++;
                len--;
            }

            if(s_len < len)
            {
                s_len = len;
            }

            right++;
        }

        return s_len;
    }
};

3. 最大连续1的个数

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    最大连续1的个数
  3. 思路:zero计数器
    循环中的逻辑顺序流程:判断 > 调整 > 记录 > 下一轮
class Solution 
{
public:
    int longestOnes(vector<int>& nums, int k) 
    {
        int zero = 0;
        int ret = 0;
        //逻辑执行顺序
        //判断,++,计算
        //哪里是窗口
        for(int right = 0, left = 0; right < nums.size(); right++)
        {
           if (nums[right] == 0)
			{
				zero++;
			}

			while (zero > k)
			{
				if (nums[left] == 0)
				{
					zero--;
				}
				left++;
			}
			
            ret = max(right - left + 1, ret);
        }

        return ret;
    }
};

4. 将x减到0的最小操作数

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    将x减到0的最小操作数
  3. 思路:
    <1> (正难则反)此题正向思路非常复杂,我们这里不求从两侧如何得到x,我们反过来求一段连续区间怎样等于sum(数组所有元素之和)- x。因为要得到从两侧出发的最小路径,相反的我们要求的就是最大的连续区间
    <2> (暴力枚举法的优化)将所有等于sum - x的连续区间求出,同向双指针。

图示:
在这里插入图片描述

class Solution 
{
public:
    int minOperations(vector<int>& nums, int x) 
    {
        //正难则反
        //暴力枚举法的优化
        int sum = 0;
        for(auto e : nums)
        {
            sum += e;
        }
        //负数
        int target = sum - x;
        int reverse_sum = 0;
        int count = 0;

        if(target < 0)
        {
            return -1;
        }

        if(target == 0)
        {
            return nums.size();
        }

        for(int left = 0, right = 0; right < nums.size(); right++)
        {
            reverse_sum += nums[right];

            while(reverse_sum > target)
            {
                reverse_sum -= nums[left];
                left++;
            }

            //区间,左闭右闭
            //找最大
            if(reverse_sum == target && right - left + 1 > count)
            {
                cout << right << ' ' << left << endl;
                count = right - left + 1;
            }
        }

        return count == 0 ? -1 : nums.size() - count;
    }
};

注:循环逻辑顺序链条,滑动窗口区间(开闭)

5. 水果成篮

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    水果成篮
  3. 思路:滑动窗口 + hash表(暴力求解的优化)
class Solution 
{
public:
    int totalFruit(vector<int>& fruits) 
    {
        //遍历双指针
        //哈希表,种类
        int size = fruits.size();
        int* hash = (int*)malloc(size * sizeof(int));
        memset(hash, 0, size * sizeof(int));
        int kind = 0;
        int len = 0;
        for(int right = 0, left = 0; right < fruits.size(); right++)
        {
            //进窗口
            if(hash[fruits[right]] == 0)
            {
                kind++;
            }
            hash[fruits[right]]++;

            //出窗口
            while(kind > 2)
            {
                hash[fruits[left]]--;
                if(hash[fruits[left]] == 0)
                {
                    kind--;
                }

                left++;
            }

            //判断
            if(right - left + 1 > len)
            {
                len = right - left + 1;
            }
        }

        return len;
    }
};

6. 找到字符串中所有字母异位词

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    找到字符串中所有字母异位的字符
  3. 思路:

尝试:

class Solution 
{
public:
    vector<int> findAnagrams(string s, string p) 
    {
        vector<int> count;
        //暴力求解,列出所有长度为字符串p的字串
        //进行匹配,哈希表大小26,下标
        //记录,判断
        int hash[26] = {0};
        //所指定下标处都为1
        //遍历的指针
        for(int right = 0, left = 0; right < s.length(); right++)
        {
            //进窗口
            hash[s[right] - 'a']++;

            //出窗口
            while(right - left + 1 > p.length())
            {
                hash[s[left] - 'a']--;
                left++;
            }

            //判断
            if(right - left + 1 == p.length())
            {
                int flag = 1;
                for(int i = 0; i < p.length(); i++)
                {
                    //相同的字符
                    hash[p[i] - 'a']--;
                }

                for(int i = 0; i < p.length(); i++)
                {
                    if(hash[p[i] - 'a'] != 0)
                    {
                        flag = 0;
                        break;
                    }
                }

                for(int i = 0; i < p.length(); i++)
                {
                    hash[p[i] - 'a']++;
                }

                if(flag)
                {
                    count.push_back(left);
                }
            }
        }

        return count;
    }
};

优化1:(判断方式改为两个哈希表)

class Solution 
{
public:
    vector<int> findAnagrams(string s, string p) 
    {
        vector<int> count;
        int hash1[26] = {0};
        int hash2[26] = {0};
        for(int i = 0; i < p.length(); i++)
        {
            hash2[p[i] - 'a']++;
        }
        
        for(int right = 0, left = 0; right < s.length(); right++)
        {
            //进窗口
            hash1[s[right] - 'a']++;

            //出窗口
            while(right - left + 1 > p.length())
            {
                hash1[s[left] - 'a']--;
                left++;
            }

            //判断
            //两个哈希表
            if(right - left + 1 == p.length())
            {
                int flag = 1;
                for(int i = 0; i < 26; i++)
                {
                    if(hash1[i] !=hash2[i])
                    {
                        flag = 0;
                        break;
                    }
                }

                if(flag)
                {
                    count.push_back(left);
                }
            }
        }

        return count;
    }
};

优化2:(计数:判断优化)

  1. 创建变量count1记录滑动窗口中的有效字符,以等长与有效字符数量相等的判断标准代替,遍历式的判断
  2. 当遍历字符确为有效字符,且不为多余的字符时,count1++或count1–
class Solution 
{
public:
    vector<int> findAnagrams(string s, string p) 
    {
        vector<int> ret;
        int hash1[26] = {0};
        int hash2[26] = {0};
        int count1 = 0;
        int count2 = 0;
        //标记并计算有多少个有效字符
        for(int i = 0; i < p.length(); i++)
        {
            hash2[p[i] - 'a']++;
            count2++;
        }
        
        for(int right = 0, left = 0; right < s.length(); right++)
        {
            //有效字符,且没有多余
            if(hash2[s[right] - 'a'] && hash1[s[right] - 'a'] < hash2[s[right] - 'a'])
            {
                count1++;
            }
            //进窗口
            hash1[s[right] - 'a']++;

            //出窗口
            while(right - left + 1 > p.length())
            {
                //是有效字符,且出窗口的有效字符不是多余
                if(hash2[s[left] - 'a'] && hash1[s[left] - 'a'] <= hash2[s[left] - 'a'])
                {
                    count1--;
                }
                hash1[s[left] - 'a']--;
                left++;
            }

            //判断
            //两个哈希表
            if(right - left + 1 == p.length() && count1 == count2)
            {
                ret.push_back(left);
            }
        }

        return ret;
    }
};

7. 串联所有单词的子串

  1. 题目信息:
    在这里插入图片描述

  2. 题目连接:
    串联所有单词的子串

  3. 思路:哈希表 + 滑动窗口(注:遍历方式 + 边界问题

  4. 语法优化:
    <1> 获得子串使用string的substr接口,hash.count(字符)
    <2> 判断hash表中是否有这个字符,对照表中没有,那么记录表中不创建

class Solution
{
public:
	vector<int> findSubstring(string s, vector<string>& words)
	{
		vector<int> ret;

		//right + words[0].length()边界问题
		for (int i = 0; i < words[0].length(); i++)
		{
            map<string, int> m1;
            map<string, int> m2;
            for (auto e : words)
            {
                m1[e]++;
                //cout << m1[e] << endl;
                m2[e] = 0;
            }

            int count1 = 0;
		    int count2 = words.size();
			//后续再遍历,少遍历一步
			for (int right = i, left = i; right + words[0].length() - 1 < s.length(); right += words[0].length())
			{
				//进窗口
				string part1(s.begin() + right, s.begin() + right + words[0].length());
				//cout << part << endl;
				//有效字符
				if (m1[part1] > 0 && m2[part1] < m1[part1])
				{
					count1++;
				}
				m2[part1]++;
				//cout << m2[part1] << endl;

				//出窗口
				int size = words.size() * words[0].length();
				//cout << size << endl;
				while (right + words[0].length() - left > size)
				{
					string part2(s.begin() + left, s.begin() + left + words[0].length());
					//没有发生重复的有效字符
					//只要是有效字符就--
					if (m1[part2] > 0 && m2[part2] <= m1[part2])
					{
						count1--;
					}
					m2[part2]--;
					left += words[0].length();
				}

				//判断
				if (right + words[0].length() - left == words.size() * words[0].length() && count1 == count2)
				{
					count.push_back(left);
				}
			}
		}

		return ret;
	}
};

8. 最小覆盖子串

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    最小覆盖子串
  3. 思路:滑动窗口 + 哈希表
  4. 优化:数组代替map效率更高,记录字符种类

在这里插入图片描述

class Solution {
public:
    string minWindow(string s, string t) 
    {
        //可以用数组代替map时,数组更优
        int hash1[128] = {0};
        int hash2[128] = {0};
        //记录有效字符种类
        int kind1 = 0;
        for(auto e : t)
        {
            if(hash1[e]++ == 0)
            {
                kind1++;
            }
            //hash1[e]++;
        }

        //用有效字符种类的判断方式优于记录有效字符个数的判断方式
        int kind2 = 0;
        //记录子串长度
        int len = INT_MAX;
        //记录子串起始位置
        int begin = -1;
        for(int right = 0, left = 0; right < s.length(); right++)
        {
            //先进窗口再判断是否为有效字符
            //hash2[s[right]]++;
            if(hash1[s[right]] == ++hash2[s[right]])
            {
                kind2++;
            }

            //当遍历字符中包含有效字符时,进行判断,记录与出窗口操作
            while(kind1 == kind2)
            {
                //判断
                if(right - left + 1 < len)
                {
                    begin = left;
                    len = right - left + 1;
                }

                //出窗口
                //先判断再出窗口
                if(hash1[s[left]] && hash1[s[left]] == hash2[s[left]])
                {
                    kind2--;
                }
                hash2[s[left]]--;
                left++;
            }
        }

        //判断有无子串
        if(begin == -1)
        {
            return "";
        }
        else
        {
            //取子串
            return s.substr(begin, len);
        }
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1505514.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

观察主线程退出对子线程的影响

✨主进程以pthread_exit(0)方式退出——线程退出 ✨子线程pthread_cancel方式杀死父线程 ✨主线程以return&#xff0c;exit(0)方式退出——进程退出  &#x1f531;return   &#x1f531;return在主函数   &#x1f536;return在子函数  &#x1f536;exit(0)   &…

数据结构——堆的应用 Topk问题

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

谷歌开源的LLM大模型 Gemma 简介

相关链接&#xff1a; Hugging face模型下载地址&#xff1a;https://huggingface.co/google/gemma-7bGithub地址&#xff1a;https://github.com/google/gemma_pytorch论文地址&#xff1a;https://storage.googleapis.com/deepmind-media/gemma/gemma-report.pdf官方博客&…

多址接入技术 ---FDMA、TDMA、CDMA

1、多址接入技术 所谓多址技术就是把同一个无线信道按照时间、 频率等进行分割, 使不同的用户都能够在不同的分割段中使用这一信道, 而又不会明显地感觉到他人的存在, 就好像自己在专用这一信道一样。 占用不同的分割段就像是拥有了不同的地址, 使用同一信道的多个用户就…

Vue.js过滤器:让数据展示更灵活

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Unity的相机跟随和第三人称视角二

Unity的相机跟随和第三人称视角二 展示介绍第二种相机模式远离代码总结 展示 我录的视频上可能看不太出来&#xff0c;但是其实今天的这个方法比原来写的那个方法更简便并且死角更少一些。 这里我也做了两个人物偏移的视角&#xff0c;按Q是原来的两个相机模式切换&#xff0c…

问题 | 程序员求职宝典

程序员的金三银四求职宝典 随着春天的脚步渐近,对于许多程序员来说,一年中最繁忙、最重要的面试季节也随之而来。金三银四,即三月和四月,被广大程序员视为求职的黄金时期。在这两个月里,各大公司纷纷开放招聘,求职者们则通过一轮又一轮的面试,力争心仪的职位。而如何在…

从零学习Linux操作系统 第三十二部分 Ansible中的变量及加密

一、变量的基本设定及使用方式 变量命名&#xff08;与shell相同&#xff09;&#xff1a; 只能包含数字&#xff0c;下划线&#xff0c;字母 只能用下划线或字母开头 .变量级别&#xff1a; 全局从命令行或配置文件中设定的paly:在play和相关结构中设定的主机:由清单&…

NeRF模型NeRF模型

参考视频&#xff1a;https://www.youtube.com/watch?vHfJpQCBTqZs&ab_channelVision%26GraphicsSeminaratMIT NeRF模型的输入输出: 输入: (x, y, z): 一个三维空间坐标,代表场景中的一个位置点(θ, φ): 视线方向,θ表示与y轴的夹角,φ表示与x轴的夹角,用两个角度可以…

(C语言)strcpy与strcpy详解,与模拟实现

目录 1. strcpy strcpy模拟实现&#xff1a; 实现方法1&#xff1a; 实现方法2&#xff1a; 2. strcat strcat模拟实现&#xff1a; 1. strcpy 作用&#xff1a;完成字符串的复制。 头文件&#xff1a;<string.h> destination是字符串要复制到的地点&#xff0c;s…

图片在div完全显示

效果图&#xff1a; html代码&#xff1a; <div class"container" style" display: flex;width: 550px;height: 180px;"><div class"box" style" color: red; background-color:blue; width: 50%;"></div><div …

FreeRTOS操作系统学习——同步互斥与通信

同步&#xff08;Synchronization&#xff09; 同步是一种机制&#xff0c;用于确保多个任务能够按照特定的顺序协调执行或共享数据。当一个任务需要等待其他任务完成某个操作或满足某个条件时&#xff0c;同步机制可以帮助任务进行协调和等待。 在FreeRTOS中&#xff0c;常见…

【布局:1688,阿里海外的新筹码?】1688重新布局跨境海外市场:第一步开放1688API数据采集接口

2023年底&#xff0c;阿里巴巴“古早”业务1688突然成为“重头戏”&#xff0c;尤其宣布正式布局跨境业务的消息&#xff0c;一度引发电商圈讨论。1688重新布局跨境海外市场&#xff1a;第一步开放1688API数据采集接口 2023年11月中旬&#xff0c;阿里财报分析师电话会上&…

SpringBoot项目如何部署到服务器

文章目录 准备&#xff1a;方式一&#xff1a;Jar包方式&#xff08;推荐&#xff09;部署步骤&#xff1a; 方式二&#xff1a;War包方式部署步骤&#xff1a; 总结 准备&#xff1a; 云服务器&#xff08;阿里云、腾讯云等&#xff09;Linux系统以及运行所需的环境 方式一&a…

数据结构部分

来源地址 一 数据结构 1 堆和树之间的区别 区别就在于树是没有特定顺序的&#xff0c;你需要遍历整个树才能找到特定元素&#xff1b;而堆是有序的&#xff0c;你可以直接找到最大&#xff08;或最小&#xff09;的元素。 堆&#xff1a;假设你正在开发一个任务调度系统&…

Java二叉树 (2)

&#x1f435;本篇文章将对二叉树的一些基础操作进行梳理和讲解 一、操作简述 int size(Node root); // 获取树中节点的个数int getLeafNodeCount(Node root); // 获取叶子节点的个数int getKLevelNodeCount(Node root,int k); // 获取第K层节点的个数int getHeight(Node r…

day06-网路编程

#include <myhead.h>int do_add(sqlite3 *ppDb) {int numb;char name[20];int age;int salary;printf("请输入要插入的信息:");scanf("%d %s %d %d", &numb, name, &age, &salary);char sql[128] "";sprintf(sql, "INSE…

Python算法(顺序查找/二分查找)

一。顺序查找法&#xff1a; 用途&#xff1a;主要用于查找无序的列表的某个元素 时间复杂度为O(n) 拓展&#xff1a;函数index&#xff08;&#xff09;运用的是顺序查找 二。二分查找法&#xff1a; 前提&#xff1a;被查找的列表顺序一定要是顺序的 用途&#xff1a;对…

java数值运算a/0 ;0/b; 0/0;结果

3/0indinity 无穷大 0/40 0/0NaN 不定值

JVM 常用监控工具介绍和使用

一、JPS 进程查看工具 用于列出当前系统中所有正在运行的 Java 进程。它的主要作用是查看 Java 进程的 PID&#xff08;进程标识符&#xff09;和主类名。可以帮助开发人员快速了解系统中正在运行的 Java 进程&#xff0c;对于监控和调试 Java 应用程序非常有用。 用法&#…