双指针和单调栈

双指针

用于解决一类基于子段的统计问题

子段就是:数组中连续的一段

可以用一个闭区间来表示数组中的连续一段

这个方法核心就是优化:两种循环的枚举

  • 也就是枚举左端点l和右端点r的所有可能
  • 优化关键就是:去除枚举中的冗余部分

具体优化策略

  • 固定右端点,看左端点的取值范围

就是根据题意,把[j,i]范围中,j的这层循环去掉(j从0~i)

  • 移动一个端点,观察另一个断点变化

就是滑动窗口,一个端点跟随另一个端点来移动

双指针解决两数之和

sort()函数

sort(first_pointer,first_pointer+n,cmp)

int a[8] = {6, 7, 2, 9, 1, 3, 5, 2};
sort(a, a + 8,less<int>());//less<数据类型>()
//默认从小到大,less<int>()可省略
int a[8] = {6, 7, 2, 9, 1, 3, 5, 2};
sort(a, a + 8,greater<int>());
//这是从大到小排列

注意:less<>()和greater<>()数据类型要写对

float f[5] = {11.2, 76.7, 8.32, 15.12, 2.676};sort(f, f+sizeof(f)/sizeof(float),less<double>());double d[5] = {1.2, 7.7, 8.32, 5.12, 2.66};sort(d, d + 5,greater<double>());

vector一样,而且传参可以传入自带的迭代器

vector<double> f= {11.2, 76.7, 8.32, 15.12, 2.676};sort(f.begin(),f.end(),less<double>());vector<float> d= {1.2, 7.7, 8.32, 5.12, 2.66};sort(d.begin(), d.end(),greater<double>());
自定义函数

sort()第三个参数可以自己自定义,注意函数要用bool返回值,而且传参时不要加()

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
struct student{string name;int age;student(string s,int a):name(s),age(a){}
};
//按姓名升序
bool cmp_name(const student& a,const student& b){return a.name < b.name;
}
//按年龄降序
bool cmp_age(const student& a,const student& b){return a.age > b.age;
}
int main(){student stu[5] = {{"daming", 54},{"xiaozhang", 44},{"amy", 33},{"bob", 12},{"alicc", 77}};sort(stu, stu+5, cmp_name);for(auto [name,age]:stu){cout << name << "," << age << endl;}vector<student> vec(stu, stu + 5);//迭代器初始化sort(vec.begin(), vec.end(), cmp_age);for(auto [name,age]:vec){cout << name << "," << age << endl;}
}

在这里插入图片描述

数组指针初始化

vector vec(stu + 1, stu + 4);


参数的函数无括号

sort(vec.begin(), vec.end(), cmp_age);

已排序数组的两数之和

167. 两数之和 II - 输入有序数组

利用排序特点,一端指向最小,一段指向最大。那么相加多了,大的减小,少了,小的增加。

  vector<int> twoSum(vector<int>& numbers, int target) {//左右指针向中间移动int i=0;int j=numbers.size()-1;while(i<j){if(numbers[i]+numbers[j]>target){j--;}else if(numbers[i]+numbers[j]<target){i++;}else{//numbers[i]+numbers[j]==targetreturn {i+1,j+1};}}return {};}

法二:固定一端,考虑另一端的变化情况

vector<int> twoSum(vector<int>& numbers, int target) {//左右指针向中间移动int j=numbers.size()-1;for(int i=0;i<numbers.size();i++){//固定左端i,考虑j//只要大了,就一直移动jwhile(i<j && numbers[i]+numbers[j]>target) j--;if(i<j && numbers[i]+numbers[j]==target){return {i+1,j+1};}//这里左端自己移动,所以小了不做考虑}return {};}

这里{i+1,j+1}是题目要求从1开始

只有:i<j这个下标才有意义,所以要加入判断

或者在代码写:if(i>=j) break;

未排序数组两数之和

1. 两数之和

采用自定义类型来存储原数组下标,然后排序

注意自定义类型的访问名称是类型的变量名称

vec[i].val vec[i].index

  • 这里cmp函数需要加上static关键字
class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {int n = nums.size();vector<item> vec;for (int i = 0; i < n; i++) {//用vec存储排序前的下标vec.push_back(item(nums[i], i));}sort(vec.begin(),vec.end(),cmp);int j = n - 1;for (int i = 0; i < n; i++) {while (i < j && vec[i].val + vec[j].val > target)j--;if (i < j && vec[i].val + vec[j].val == target) {return {vec[i].index, vec[j].index};}}return {};}private:struct item {int val;int index;item(int v, int i) : val(v), index(i) {}};static bool cmp(const item& a, const item& b) { return a.val < b.val; }
};

或者直接用现成的pair,注意first和second是pair的成员

vector<int> twoSum(vector<int>& nums, int target) {//用pair来存储vector<pair<int, int>> vec;for(int i=0;i<nums.size();i++){vec.push_back(make_pair(nums[i],i));}sort(vec.begin(),vec.end());//默认排序k,v中kint j=nums.size()-1;//注意,这里访问的是已排序的vecfor(int i=0;i<vec.size();i++){while(i<j && vec[i].first+vec[j].first>target){j--;}if(i<j && vec[i].first+vec[j].first==target){return {vec[i].second,vec[j].second};}}return {};}

三数之和

15. 三数之和

双指针法的优势就是可以找到所有可能解,因为排序后的数组只要一大一小向中间移动,就会找到多个解

vector<vector<int>> colTwoSum(vector<int> &nums, int target){vector<vector<int>> res;int k = nums.size() - 1;for (int j=0; j < nums.size(); j++){while (j < k && nums[j] + nums[k] > target)k--;if (j < k && nums[j] + nums[k] == target){//只存不return,会找到所有结果res.push_back({nums[j], nums[k]});}}return res;
}
int main(){vector<int> nums = {1, 3, 4, 5, 6, 7, 8};auto res = colTwoSum(nums, 9);for(auto v:res){cout << v[0] << " " << v[1] << endl;}
}
/*
1 8
3 6
4 5*/

三数之和就是在两数之和基础上改进,关键:nums[j]+nums[k]==-nums[i],所以就是遍历i然后利用双指针的两数之和找到所有满足的j,k,然后把当前i并入结果即可

  • 注意j从i+1开始,这样i,j,k就都不相同,且:i<j<k
  • 注意:排序后的值:[1,1,2,2,3,3],i如果是一个数的话,同一个j,k会重复,所以相同的i值只判断一个
  • 同理:j,k也要去重,j只选择不同的值。
  • 最后数组下标一定要有意义,所以合法性检查不可少
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {//题目要求返回值,所以可以直接排序sort(nums.begin(),nums.end());//遍历nums[i],向后找:nums[j]和nums[k]//即:nums[j]+nums[k]==-nums[i](保证i,j,k不同)vector<vector<int>> ans;for(int i=0;i<nums.size();i++){//去重if(i>=1 && nums[i]==nums[i-1]) continue;auto res=colTwoSum(nums,i+1,-nums[i]);//所有的res结果并入nums[i]for(auto v:res){ans.push_back({nums[i],v[0],v[1]});}}return ans;}
private:vector<vector<int>> colTwoSum(vector<int>& nums,int j,int target){vector<vector<int>> res;int k=nums.size()-1;int tmp=j;for(;j<nums.size();j++){if(j>tmp && nums[j]==nums[j-1]) continue;while(j<k && nums[j]+nums[k]>target) k--;if(j<k && nums[j]+nums[k]==target){res.push_back({nums[j],nums[k]});}}return res;}
};

也可以不写函数:

vector<vector<int>> threeSum(vector<int>& nums) {//题目要求返回值,所以可以直接排序sort(nums.begin(),nums.end());//遍历nums[i],向后找:nums[j]和nums[k]//即:nums[j]+nums[k]==-nums[i](保证i,j,k不同)vector<vector<int>> ans;for(int i=0;i<nums.size();i++){//去重if(i>=1 && nums[i]==nums[i-1]) continue;int k=nums.size()-1;//类似函数传参,初始值定义在外面int target=-nums[i];for(int j=i+1;j<nums.size();j++){if(j>i+1 && nums[j]==nums[j-1]) continue;while(j<k && nums[j]+nums[k]>target) k--;if(j<k && nums[j]+nums[k]==target){//直接装入ans.push_back({nums[i],nums[j],nums[k]});}}}return ans;}

双指针,容器盛水

11. 盛最多水的容器

解题关键就是意识到:面积是由短板和距离决定,如果距离最长,那面积完全取决于短板

在这里插入图片描述

  • 距离一定,长板不动,移动短板,面积可能变大
  • 距离一定,短板不动,移动长板,因为短板是固定的,面积一定不会增大

所以每次计算面积后,移动最短的那块板

这里i,j两端变化无规律,取决于height[i]和height[j],用while最合适

 int maxArea(vector<int>& height) {int res=0;//双指针int i=0,j=height.size()-1;while(i<j){//不可以相遇res=max(res,min(height[i],height[j])*(j-i));//谁短移动谁if(height[i]<height[j]) i++;else j--;}return res;}
res=height[i]<height[j]?max(res,(j-i)*height[i++]):max(res,(j-i)*height[j--]);

注意:因为自增自减,不可以用:height[i++]*(j-i);

单调栈

单调递增堆栈是指元素从下到上按升序排列的堆栈

在这里插入图片描述

单调递减堆栈是元素从底部到顶部按降序排列的堆栈

在这里插入图片描述

实现单调递增栈

//栈顶元素最大,而且出栈大小依次递减
考虑一个数组 Arr[] = {1, 4, 5, 3, 12, 10}
对于 i = 0: stk = {1}
对于 i = 1: stk = {1, 4}
对于 i = 2: stk = { 1, 4, 5}
对于 i = 3: stk = {1, 3}   [弹出 4 和 5 作为 4 > 3 和 5 > 3]
对于 i = 4: stk = {1, 3, 12}
对于 i = 5 : stk = {1, 3, 10} [将 12 弹出为 12 > 10] 
class monoStack{
public:/*单调递增栈*/void increase_stack(vector<int> vec){//初始化一个栈,存下标stack<int> sta;//访问每一个元素for (int i = 0; i < vec.size();i++){//新元素一定会入栈,但是必须把大于它的值出栈while(!sta.empty()&& vec[i]<vec[sta.top()]){sta.pop();}//入栈sta.push(i);}//把单调栈出栈,把结果存入resres.resize(sta.size());for (int j = sta.size() - 1; j >= 0;j--){res[j] = vec[sta.top()];sta.pop();}}
private:vector<int> res;
};
int main(){vector<int> a{1, 4, 5, 3, 12, 10};monoStack ms;ms.increase_stack(a);
}

递减单调栈(栈底元素最大,栈顶元素最小)

void decrease_stack(vector<int> vec){//递减,栈底元素最大stack<int> sta;for (int i = 0; i < vec.size();i++){//入栈前,必须去掉栈中比自己小的while(!sta.empty()&&sta.top()<vec[i]){sta.pop();}sta.push(vec[i]);//存入值}//把单调栈结果存入resres.resize(sta.size());for (int i = sta.size() - 1; i >= 0;i--){res[i] = sta.top();sta.pop();}}

应用~计算直方图内最大矩形面积

84. 柱状图中最大的矩形

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

题目特点就是:可延伸,是直方图内部的构成的最大面积

如果用暴力解法:

​ + 左右指针初始化为当前柱子i,高度:heights[i]

​ + 只要两端左边或右边比heght[i]小就是可延伸

​ + 也就是:从中心向两端扩散

 int largestRectangleArea(vector<int>& heights) {int n=heights.size();int res=0;for(int i=0;i<n;i++){int left=i;int right=i;int h=heights[i];while(left>=0 && heights[left]>=h) left--;/*for(;left>=0;left--)if(heghts[left]<h) break;*/while(right<n && heights[right]>=h) right++;int w=right-left-1;//延伸宽度:(left,right)res=max(res,w*h);}return res;}

应用单调栈

在这里插入图片描述

特点就是:每次遍历柱子的时候,如果当前柱子高度比他的前一个柱子矮的话(比如:1比0矮)

此时:前一个柱子高度一定不能向后扩展,所以会计算出以它的高度可能向前延申的面积

这里的123是严格递增的,所以此时他们各自高度形成最大面积不能判断,因为可能会向后延申

也就是说,每次我们都把单调不减的柱子放入单调栈,万一当前柱子比栈顶元素高度小,那么就出栈,可以计算出它的面积了

问题关键就是:向前延申的面积的宽度怎么确定?

在这里插入图片描述

就是:当前出栈元素高度height[sta.top()],出栈后,当前访问访问柱子i和单调栈栈顶元素之间的宽度

也就是:i-栈top()位置-1

因为:我们维持一个单调递增的单调栈,如果柱子i比栈顶小,那么前一个一定可以计算出面积。

而它延申的最长距离就是出栈后的单调栈栈顶位置(因为严格单调递增一定延申不到这个位置)

注意到,访问最后一个元素后没有结束,所以需要再加入一个小值,以便把整个栈内元素出栈

 int largestRectangleArea(vector<int>& heights) {int n=heights.size();int res=0;stack<int> sta;for(int i=0;i<=n;i++){//为了让最后一个元素能出栈,不妨让n时直方图高度-1//-1会保证单调栈内所以元素出栈int curHeight=(i==n)?-1:heights[i];/*单调栈模板:先把破坏单调性元素出栈,然后必入栈*/while(!sta.empty() && curHeight<=heights[sta.top()]){//i-1位置的面积可求int h=heights[sta.top()];sta.pop();//计算面积后就不存了//因为sta.top()在栈空时不存在int w;//宽度if(!sta.empty()) w=i-sta.top()-1;else w=i;//0 1 .. 这根柱子(i-1) i---(i-1)-0+1res=max(res,w*h);}sta.push(i);//必入栈}return res;}

curHeight<=heights[sta.top()]

这里考虑了相等柱子不入栈,跟测试用例有关,写<也行

if(!sta.empty()) w=i-sta.top()-1;

else w=i;//0 1 .. 这根柱子(i-1) i---(i-1)-0+1

由于递减形状可能会让栈没元素,注意:sta.top()的存在前提是栈不空

哨兵

单调栈需要考虑两种特殊的情况:

  • 弹栈的时候,栈为空;
  • 遍历完成以后,栈中还有元素;

在输入数组的两端加上两个高度为 0的柱形,可以回避上面这两种分类讨论。

这两个站在两边的柱形有一个很形象的名词,叫做哨兵(Sentinel)

有了这两个柱形:

  1. 左边的柱形(第 1 个柱形)由于它一定比输入数组里任何一个元素小,它肯定不会出栈,因此栈一定不会为空;

  2. 右边的柱形(第 2 个柱形)也正是因为它一定比输入数组里任何一个元素小,它会让所有输入数组里的元素出栈(第 1 个哨兵元素除外)。

 int largestRectangleArea(vector<int>& heights) {//在原来直方图前后两端各加一个0//右端的0会让单调栈全部出栈//左端的0会让单调栈不为空heights.insert(heights.begin(),0);heights.insert(heights.end(),0);int res=0;stack<int> sta;for(int i=0;i<heights.size();i++){while(!sta.empty() && heights[i]<heights[sta.top()]){int h=heights[sta.top()];sta.pop();int w=i-sta.top()-1;res=max(res,h*w);}sta.push(i);}return res;}

在这里插入图片描述

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

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

相关文章

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Span组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Span组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Span组件 鸿蒙&#xff08;HarmonyOS&#xff09;作为Text组件的子组件&#xff0…

C# 委托(delegate)本质理解

目录 代码如下&#xff0c;很简单 运行的结果 反编译程序查看 关注两点&#xff1a; 什么是委托 委托的三个步骤 委托的意义 代码如下&#xff0c;很简单 namespace Delegate { class Program { delegate void SayHi(); void SayHi_1() …

[Java][算法 双指针]Day 02---LeetCode 热题 100---04~07

LeetCode 热题 100---04~07 第一题&#xff1a;移动零 思路 找到每一个为0的元素 然后移到数组的最后 但是需要注意的是 要在给定的数组原地进行修改 并且其他非零元素的相对顺序不能改变 我们采用双指针法 定义两个指针i和j i和j一开始分别都在0索引位置 然后判断j所…

爪哇部落算法组2024新生赛热身赛题解

第一题&#xff08;签到&#xff09;&#xff1a; 1、题意&#xff1a; 2、题解: 我们观察到happynewyear的长度是12个字符&#xff0c;我们直接从前往后遍历0到n - 12的位置&#xff08;这里索引从0开始&#xff09;&#xff0c;使用C的substr()函数找到以i开头的长度为12的字…

【Linux】进程学习(二):进程状态

目录 1.进程状态1.1 阻塞1.2 挂起 2. 进程状态2.1 运行状态-R进一步理解运行状态 2.2 睡眠状态-S2.3 休眠状态-D2.4 暂停状态-T2.5 僵尸状态-Z僵尸进程的危害 2.6 死亡状态-X2.7 孤儿进程 1.进程状态 1.1 阻塞 阻塞&#xff1a;进程因为等待某种条件就绪&#xff0c;而导致的…

leetcode(哈希表)49.字母异位词分组(C++详细解释)DAY5

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 示例 1: 输入: strs [“eat”, “tea”…

【JavaWeb】头条新闻项目实现 基本增删改查 分页查询 登录注册校验 业务功能实现 第二期

文章目录 一、为什么使用token口令二、登录注册功能2.1 登录表单提交后端代码&#xff1a; 2.2 根据token获取完整用户信息代码实现&#xff1a; 2.3 注册时用户名占用校验代码实现&#xff1a; 2.4 注册表单提交代码实现&#xff1a; 三、头条首页功能3.1 查询所有头条分类3.2…

第三讲 多重背包问题①——转化

【题目来源】AcWing 4. 多重背包问题 I 【题意分析】和完全背包问题类似&#xff0c;但是区别在于每一种物品的数量是有限的。 【解决方法】 1.转化为 0 / 1 0/1 0/1 背包问题 因为每一种物品数量有限&#xff0c;所以将每个物品看作单独的种类&#xff0c;可转化为 0 / 1 0/…

掌握Vue,开启你的前端开发之路!

介绍&#xff1a;Vue.js是一个构建数据驱动的Web应用的渐进式框架&#xff0c;它以简洁和轻量级著称。 首先&#xff0c;Vue.js的核心在于其视图层&#xff0c;它允许开发者通过简单的模板语法将数据渲染进DOM&#xff08;文档对象模型&#xff09;。以下是Vue.js的几个重要特点…

Git中为常用指令配置别名

目录 1 前言 2 具体操作 2.1 创建.bashrc文件 2.2 添加指令 2.3 使其生效 2.4 测试 1 前言 在Git中有一些常用指令比较长&#xff0c;当我们直接输入&#xff0c;不仅费时费力&#xff0c;还容易出错。这时候&#xff0c;如果能给其取个简短的别名&#xff0c;那么事情就…

力扣102. 二叉树的层序遍历 (复习vector和queue的常见用法

目录 题目描述 题目解析 题目答案 题目所用知识点 最后 题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术…

K8S之运用节点选择器指定Pod运行的节点

node节点选择器的使用 使用场景实践使用nodeName使用nodeSelectornodeName和nodeSelector混合使用1、设置了nodeName 和 设置 Node上都不存在的标签。看调度情况2、设置nodeName 为node1 和 设置 node2上才有的标签。看调度情况 实践总结 使用场景 默认情况&#xff0c;在创建…

c++二叉树寒假特训题目(2)

hello&#xff0c;我是Joseph&#xff0c;今天推出第二期c二叉树寒假特训题目。 第一期传送门 第一期答案传送门 这期有7题&#xff0c;目录如下。 目录 题目 二叉树结点查找 二叉树是否对称 ​编辑 二叉排序树 层次遍历 根据前序中序求后序 二叉树高度 ​编辑 二…

【通讯录案例-偏好设置 Objective-C语言】

一、刚才,我们plist存储,讲完了,这个plist,我直接,右键,打开 打开 不用xcode,我就用文本文档打开,打开方式:其他 选择:文本编辑 打开 好,这个里边儿啊,就是我们刚才存的一个Key:Value 它本质上,是一个xml 这是一种文件的格式, 等你们讲到网络的时候,实际上,…

MGIE官网体验入口 苹果多模态大语言模型AI图像编辑工具在线使用地址

MGIE是一项由苹果开源的技术&#xff0c;利用多模态大型语言模型&#xff08;MLLMs&#xff09;生成图像编辑指令&#xff0c;通过端到端训练&#xff0c;捕捉视觉想象力并执行图像处理操作&#xff0c;使图像编辑更加智能、直观。 MGIE官网体验入口https://github.com/apple/M…

上市公司人工智能转型指数及55个工具变量汇总数据集(2024.2月更新)

一、“智能化转型”发文趋势和主题分布 二、数据来源 上市公司年报、官网&#xff0c;中国知网及各期刊官网等三、时间跨度 工具变量&#xff1a;2022-2024年&#xff1b; 上市公司人工智能转型指数&#xff1a;2007-2021年四、数据范围 中国A股上市公司五、数据展示 序号…

单片机学习笔记---串口向电脑发送数据电脑通过串口控制LED

目录 串口向电脑发送数据 每隔一秒串口就发送一个递增的数给电脑 电脑通过串口控制LED 波特率的具体计算 HEX模式和文本模式 前两节是本节的理论基础&#xff0c;这节开始代码演示&#xff01; 串口向电脑发送数据 接下来先开始演示一下串口单向发送一个数字给电脑&…

ShardingSphere 5.x 系列【3】分库分表中间件技术选型

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.1.0 本系列ShardingSphere 版本 5.4.0 源码地址:https://gitee.com/pearl-organization/study-sharding-sphere-demo 文章目录 1. 前言2. My Cat3. ShardingSphere4. Dble5. Vitess6. 大厂开源6.1 Cobar6.…

[HTTP协议]应用层的HTTP 协议介绍

目录 1.前言 2.使用fiddler抓包来观察HTTP协议格式 3.HTTP协议的基本格式 2.1请求 2,1.1首行 2.1.2请求头 2.1.3空行 2.2响应 2.2.1首行 2.2.2响应头 键值对 ​编辑2.2.3空行 2.2.4载荷(响应正文) 3.认识URL 3.1关于URL encode 1.前言 我们在前面的博客中,简单的…

火星文:网络时代下的语言

引言 在互联网时代&#xff0c;网络语言的发展日新月异。火星文作为一种特殊的网络表达方式&#xff0c;近年来逐渐兴起并成为了网络文化的一部分。 火星文生成器 | 一个覆盖广泛主题工具的高效在线平台(amd794.com) https://amd794.com/huoxingwen 火星文的兴起时代 火星…