news 2026/9/3 8:57:50

Leetcode:常用数据结构(枚举技巧,差分数组,栈,单调队列, 堆)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Leetcode:常用数据结构(枚举技巧,差分数组,栈,单调队列, 堆)

枚举右,维护左

1512.好数对的数目

Q:
给出整数数组nums,统计满足 i<j 且 nums[i]=nums[j] 的数对个数。

我的思路:
hash数组记录每个数字出现的次数,对于出现次数elem大于1的,将1+2+3+…+(i-1)的结果加到ans中,得到ans

classSolution{public:intnumIdenticalPairs(vector<int>&nums){vector<int>hash(101);for(constauto&elem:nums){++hash.at(elem);}intans=0;for(constauto&elem:hash){if(elem>1){for(inti=1;i<elem;i++){ans+=i;}}}returnans;}};

改进:

  1. 用unordered_map代替vector实现hash数组
  2. 思想从第一个元素开始遍历,并维护一个hash数组,记录每个元素出现的次数;遍历到元素i时,hash数组记录了前面出现过多少次元素i,这些元素i能与当前元素构成数对,将hash[i]加到结果ans中
classSolution{public:intnumIdenticalPairs(vector<int>&nums){intans=0;unordered_map<int,int>hash;for(constauto&elem:nums){ans+=hash[elem];++hash[elem];}returnans;}};

3805. 统计凯撒加密对数目

Q:
给出vector<string> words,每个字符串仅包含小写英文字母,统计满足下列条件的下标对(i, j)的数量:
1.i < j
2.words[i]中的每个字母替换为字母表中下一个字母(循环替换),最终与words[j]相等
我的思路:
用vector<int> hash(elem.size())记录每个字符串相邻两个字符之间的ASCII差值
map<vector<int>, int>记录每个vector对应的字符串出现的次数
枚举右维护左,统计答案

classSolution{public:longlongcountPairs(vector<string>&words){map<vector<int>,int>hash;longlongans=0;for(constauto&elem:words){vector<int>temp(elem.size());for(inti=1;i<elem.size();i++){temp[i]=(elem[i]-elem[i-1]+26)%26;}ans+=1LL*hash[temp];++hash[temp];}returnans;}};

改进:
1.遍历每个字符串时,将 用vector统计相邻两个字符的差 改为 用字符串本身存储每个字符相对于第一个字符的偏移量,如字符串"abc"记为"012",unordered_map<string, int>记录每个字符串的出现次数
2.优化结果的更新次数时,先记录words中原字符串的出现个数,然后将每组相同的原字符串统一化(即统一为每个字符相对于第一个字符偏移量如“012”的形式),统一化后,当前新字符串(个数为c)与已经记录的新字符串能形成下标对,当前新字符串彼此之间能形成C c 2 C_c^2Cc2个下标对,更新进答案

classSolution{public:longlongcountPairs(vector<string>&words){unordered_map<string,int>cnt_words;for(constauto&elem:words){++cnt_words[elem];}longlongans=0;unordered_map<string,int>hash;for(auto&[s,c]:cnt_words){string temp=s;charbase=temp[0];for(char&ch:temp){ch=(ch-base+26)%26;}ans+=1LL*hash[temp]*c+1LL*c*(c-1)/2;hash[temp]+=c;}returnans;}};

这里对于string temp = s,灵神的解答给的是string temp = std::move(s),将s转化为右值后赋值给temp.
需要注意的是unordered_map的元素为pair<const K, V>,即这里的s是const string,这对于后面我们要更改temp的值来说是不符合const correctness的,由于string的move constructor接受的是std::string&&类型,故实际上会调用copy constructor,将const std::string&&隐性转化为const std::string&,最终temp的类型是std::string

负数的取模

在C++/Java等语言中,负数取模的结果为负数或零

-3 % 2 = -1 -2 % 2 = 0 a % b = a - (a / b) * b //C++取模公式向零取整

枚举中间

i < j < k 类型,枚举j

2506. 统计相似字符串对的数目

Q:
给出字符串数组words,如果两个字符串由相同的字符组成,则这两个字符串相似e.g."abca"与"bbccaa"相似,统计满足字符串words[i]和words[j]相似的下标对(i, j)个数
我的思路:
维护哈希表中map的key时,我选择了std::set<char>作为key,对于本题来说时间消耗与内存消耗均较高
改进:
使用位运算,选择一个26位二进制整数作为key,每位数表示对应字母是否在字符串中出现
e.g. "abca"含字符’a’ ‘b’ ‘c’,则第一二三位设为1,其余位为0

intm=0;//keyfor(constchar&ch:str){m|=1<<(ch-'a');// 1 << (ch-'a')得到ch对应的位为1}

对角线遍历

1329. 将矩阵按对角线排序

Q:
给出m*n矩阵,将矩阵的每个对角线上的元素升序排列(左上到右下),返回排序好的矩阵
A:
按对角线遍历元素时,我们从最右侧的对角线开始遍历。
对于同一条对角线内每个的元素(i, j),i - j 为定值,我们记录k = i - j + n,最右侧的对角线对应的k = 0 - (n-1) + n = 1,最左侧的对角线k = (m-1) - 0 + n = m + n - 1,k的取值范围为 [1, m+n-1]
对于一个确定的k,在其所对应的对角线中,我们遍历纵坐标j,j的最小值当i = 0时取到,且j不为负数,有min_j = max(n-k, 0);j的最大值当i = m-1时取到,且j不超过n-1,有max_j = min(m-1+n-k, n-1)
按对角线遍历的所有元素就被我们转化为先遍历对角线(k: [1, m+n-1] ),再遍历对角线内的元素(j: [min_j, max_j] )

classSolution{public:vector<vector<int>>diagonalSort(vector<vector<int>>&mat){intm=mat.size();intn=mat[0].size();for(intk=1;k<m+n-1;k++){// k = i-j+nintmin_j=max(0,n-k);intmax_j=min(n-1,m+n-k-1);vector<int>temp;for(intj=min_j;j<=max_j;j++){temp.push_back(mat[k-n+j][j]);}sort(temp.begin(),temp.end());for(intj=min_j;j<=max_j;j++){mat[k-n+j][j]=temp[j-min_j];}}returnmat;}};

前缀和

模板: 统计数组vector<int> nums的前缀和vector<int> prev
其中prev[i] 表示数组nums中的前i个数,即nums[0], nums[1], … nums[i-1]

intn=nums.size();vector<int>prev(n+1,0);for(inti=0;i<n;i++){prev[i+1]=prev[i]+nums[i];}

2588.统计美丽子数组数目

Q:
给定整数数组nums,每次操作中可以:
选择两个不同下标的i和j,将nums[i]和nums[j]同时减去2 k 2^k2k(k为非负整数,nums[i]与nums[j]二进制对应位为1)
如果一个子数组在若干次操作内(包括0次)可以变成一个全为0的数组,称它为一个美丽的子数组
返回数组nums中美丽子数组的个数
我的思路:
在维护前缀和哈希表时,用一个数组vector<int> sum记录二进制对应位数相加的和(mod 2)
遍历数组时,将每个数转化为二进制,对应位数加到sum中
若维护的哈希表中出现mod 2结果完全相同的前缀和,说明两前缀和相减对应二进制位上数字之差为2的倍数,可以消除
改进:
二进制对应位数相加,我们要得到结果mod 2的值
使用异或来操作
1^1 = 0 0^0 = 0表示奇数与奇数的差结果为偶数,偶数与偶数的差结果为偶数,满足统计条件
0^1 = 1表示偶数与奇数的差为奇数,不满足统计条件

classSolution{public:longlongbeautifulSubarrays(vector<int>&nums){longlongans=0;unordered_map<int,int>hash={{0,1}};intsum=0;for(inti:nums){sum^=i;if(hash.find(sum)!=hash.end()){ans+=hash[sum];}hash[sum]++;}returnans;}};

差分数组

对于数组a,定义差分数组
d[] = {a[0], a[1]-a[0], a[2]-a[1]...}
d[i] += n 表示将下标 >= i 的数都加上n

二维差分数组

对于二维数组a[i][j],定义差分数组d[i+1][j+1]
其中d[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
d[m][n] += n 表示将下标 i >= m, j >= n 的所有数都加上n
要更新(x1, y1) - (x2, y2)里的所有数都加上n

d[x1][y1]+=n;d[x1][y2+1]-=n;d[x2+1][y1]-=n;d[x2+1]y2+1]+=n;

通过差分数组得到原数组
a[i][j] = d[i][j] +a[i-1][j] + a[i][j-1] - a[i-1][j-1]
边遍历边更新

getline

复习一下getline(istream& is, string& str, char delim)
将输入流is的内容截止到delim之前存储到字符串str中(delim默认值为’\n’)
e.g. 读取Unix文件路径转化为标准路径 “/home/whyteafo/…/”

string str="/home/whyteafo/../";vector<string>stk;isstringstreamis(str);string temp;while(getline(is,temp,'/'){if(temp=='.'||temp.empty()){continue;}if(temp!='..'){stk.push_back(s);}elseif(!stk.empty()){stk.pop_back();}}// stk to ans

1209. 删除字符串中的所有相邻重复项 II

Q:
给定字符串s,整数k,对于字符串s中相邻k个相同的字符将其删除,对剩余字符串继续进行该操作,返回最终的字符串
题目数据保证结果唯一
A:
存储"aasssswwww"这类有连续相同字符的字符串,我们使用vector<pair<char, int>>,将字符串看成一个个相同字符构成的pair,pair.first表示该字符,pair.second为相同的字符个数
我们利用栈解决这道题
当pair.second增加到k时,表示相同的k个字符,我们将这个pair从栈中弹出
当当前字符与上个字符不同时,我们压入一个新的pair

classSolution{public:stringremoveDuplicates(string s,intk){vector<pair<char,int>>stk;for(charc:s){if(!stk.empty()&&stk.back().first==c){if(++stk.back().second==k){stk.pop_back();}}else{stk.push_back(pair(c,1));}}string ans;for(auto&p:stk){intcnt=p.second;while(cnt--){ans+=p.first;}}returnans;}};

1006. 笨阶乘

Q:
给定整数n,1 <= n <= 10000,用* / + -连接从n到1的排列,按四则运算顺序计算结果,除法向下取整
e.g. n = 10 ans =10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1
A:
这里用栈记录结果
若当前运算为加减法,则将当前元素压入栈中(负数则压入当前元素的相反数)
若当前运算为乘除法,对栈顶元素做乘除运算
用一个整数index记录当前的运算种类
index = 1, 2, 3, 4 对应* / + -

classSolution{public:intclumsy(intn){intans=0;intindex=0;// * / + -vector<int>stk;stk.push_back(n);for(inti=n-1;i>0;i--){if(index==0){stk.back()*=i;}elseif(index==1){stk.back()/=i;}elseif(index==2){stk.push_back(-i);}else{stk.push_back(i);}i=(i+1)%4;}ans=reduce(stk.begin(),stk.end(),0);returnans;}};

单调队列

单调队列 = 滑动窗口 + 单调栈
三步:入 / 出 / 更新
顺序根据题意变化
若更新答案要用到新元素,则入-更新;若更新答案不用到新元素,则更新-入

239. 滑动窗口最大值

Q:
给定整数数组nums,大小为k的滑动窗口从数组最左侧移动到最右侧,每次只移动一位
返回每个滑动窗口中元素的最大值组成的数组
A:
维护双端队列deque<int> q,记录更新答案要用的元素的下标,并保证q中元素单调递减
入:遍历数组,在插入每个新元素时,我们检查队列q末尾的元素是否<=新元素,若条件为真,则后续挑选滑动窗口最大值时,只有可能选择新元素作为最大值,队列末尾元素永远不可能被选中,我们将其删除
出:若队列头元素超出滑动窗口范围,删除
更新:由于队列q单调递减,队列头元素一定是当前滑动窗口中的最大元素,我们将其更新为答案
(每次写q.back()等记得想一想q.empty()要不要判断)

classSolution{public:vector<int>maxSlidingWindow(vector<int>&nums,intk){intn=nums.size();vector<int>ans(n-k+1,0);deque<int>q;for(inti=0;i<n;i++){// insert new element:// delete all elements not greater than new element// since they will never serve as the biggest numberwhile(!q.empty()&&nums[q.back()]<=nums[i]){q.pop_back();}q.push_back(i);// delete element out of rangeintleft=i-k+1;if(q.front()<left){q.pop_front();}// update ansif(left>=0){ans[left]=nums[q.front()];}}returnans;}};

介绍c++ <queue>库中的priority queue

template<classT,classContainer=std::vector<T>,classCompare=std::less<typenameContainer::value_type>>classpriority_queue;

优先队列类似于堆的实现

Constructor:默认构造中队列中的元素会进行降序排序,每次取到的队头元素都是队列中的最大元素
使用vector<int> nums中元素的构造:priority_queue<int, vector<int>, greater<>> heap(nums.begin(), nums.end());
priority_queue默认的Container与Compare为vector和less,若要更改需在<>中显式指定

Compare相关:compare(T x, T y)为真,则x在y之前;由于优先队列始终返回最大元素,所以在队列中实际位置上顺序翻转,x在y之后
pop()弹出队头元素,push()向队尾插入元素,top()获取队头元素

703. 数据流中的第 K 大元素

Q:
给定一个数组nums,整数k。每次操作插入一个整数val,返回数组中第k大的整数
A:
用降序排列的优先队列,每次操作需要弹出k-1个数❌
用升序的优先队列,优先队列只记录前k大的数,由于不涉及删除操作,更小的数永远不会被利用
优先队列的队头即第k大的数

classKthLargest{public:intk;priority_queue<int,vector<int>,greater<int>>heap;KthLargest(intk,vector<int>&nums):k(k),heap(priority_queue<int,vector<int>,greater<int>>(nums.begin(),nums.end())){while(heap.size()>k){heap.pop();}}intadd(intval){heap.push(val);if(heap.size()>k){heap.pop();}returnheap.top();}};

264. 丑数 II

Q:
规定丑数为质因数只含2,3,5的数(1也是丑数),求从小到大排列第n个丑数
A:
思路:每个新的丑数都可以表示为一个较小的丑数与2/3/5的积,我们从1开始寻找丑数,并对每个已经记录的丑数尝试乘以2/3/5,来得到新的丑数
代码:用数组res[n]记录丑数,结果第n个丑数即为res[n-1]
用指针pa, pb, pc记录分别乘以2/3/5可能得到新的丑数的旧的丑数的索引
for循环中,对记录的三个旧的丑数分别乘以2/3/5,将三个结果中的最小值作为新丑数添加到res中,并自增对应pa, pb, pc
注意:如果新丑数可以由多个旧丑数乘以不同的数(2/3/5)得到,对应指针都应当自增

classSolution{public:intnthUglyNumber(intn){intres[n];intpa=0,pb=0,pc=0;res[0]=1;for(inti=1;i<n;i++){inta=res[pa]*2,b=res[pb]*3,c=res[pc]*5;intnum=min(a,min(b,c));if(num==a)++pa;if(num==b)++pb;if(num==c)++pc;res[i]=num;}returnres[n-1];}};

科学记数法

1e9指 1 * 10^9,e代表10的幂

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 8:55:48

R语言ComplexHeatmap实战:从原理到代码绘制发表级圆形热图

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 8:53:44

Jalium UI跨平台GPU加速渲染框架入门与实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 8:50:40

51单片机实现人眼生理节律闭环台灯设计

简介&#xff1a;本资源是一套完整的单片机课题设计实践方案&#xff0c;面向电子类专业本科生、单片机初学者及课程设计参与者&#xff0c;聚焦青少年视力保护这一现实需求&#xff0c;提供从硬件搭建到软件实现的全流程参考。方案以STC89C52等51/52系列单片机为核心&#xff…

作者头像 李华