当前位置: 首页 > news >正文

【入门算法】枚举:有序穷举,分步排查

目录

前言

枚举基础题目

1. 两数之和

2441. 与对应负数同时存在的最大正整数

1512. 好数对的数目

2001. 可互换矩形的组数

1128. 等价多米诺骨牌对的数量

121. 买卖股票的最佳时机

219. 存在重复元素 II

2260. 必须拿起的最小连续卡牌数

2815. 数组中的最大数对和

2342. 数位和相等数对的最大和

1679. K 和数对的最大数目

面试题 16.24. 数对和

3371. 识别数组中的最大异常值

624. 数组列表中的最大距离

2364. 统计坏数对的数目

1014. 最佳观光组合

1814. 统计一个数组中好对子的数目

2905. 找出满足差值条件的下标 II

枚举进阶题目

1010. 总持续时间可被 60 整除的歌曲

3185. 构成整天的下标对数目 II

2748. 美丽下标对的数目

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

2874. 有序三元组中的最大值 II

454. 四数相加 II

总结


前言

对于 双变量问题,例如两数之和x1+x2=target,可以将双变量问题转化为单变量问题,即枚举x1查找是否存在target-x2,这可以用哈希表维护。把这个技巧称作 枚举右,维护左

很多涉及到「两个变量」的题目,都可以枚举其中一个变量,把它当成常量看待,从而转换成「一个变量」的问题。

如果一个题可以使用枚举右,维护左其大概率也能使用排序+双指针进行解决,但是排序的时候可能要带着下标进行排序。

PS:本篇博客中的所有题目均来自于灵茶山艾府 - 力扣(LeetCode)分享的题单。 

枚举基础题目

1. 两数之和

leetcode第一题相信是大多数人们开始的地方,此题你可能使用了两个循环进行解决的,时间复杂度是O(N^2),那能不能进行优化呢???

题解一:枚举右,维护左。从前往后一次遍历数组,一边遍历一边将数据放入到哈希表中;每次循环到一个位置都在哈希表中查找是否有合适的数能组成target,如果有就直接返回即可,如果没有就将该位置插入到哈希表中。

class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {int n=nums.size();  //数组长度unordered_map<int,int> m;  //存储已经出现的数据for(int i=0;i<n;i++){int need=target-nums[i];if(m.count(need)) return {m[need],i};  //左边存在满足条件的数据,直接进行返回m[nums[i]]=i;  //没有满足条件的数据,将该数据进行插入}return {-1,-1};}
};

题解二:排序+相向双指针。可以先对数据进行排序(带着下标进行排序),在使用相向双指针来控制一段区间[left,right],区间内是可能存在满足条件的数据,区间外是没有满足条件的数据。当最小值+最大值<target即tmp[left]+tmp[right]<target时就将最小值增大left++,然后判断是否等于==,如果等于就直接返回;如果nums[left]+nums[right]>target时就需要将最大值进行缩小right--。

class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {int n=nums.size();vector<pair<int,int>> tmp;for(int i=0;i<n;i++)tmp.push_back({nums[i],i});  //存储数据+下标sort(tmp.begin(),tmp.end());  //排序int left=0,right=n-1;while(left<right) {while(left<right&&tmp[left].first+tmp[right].first<target) left++;  //left+right<targetif(tmp[left].first+tmp[right].first==target) return {tmp[left].second,tmp[right].second};  //等于直接返回right--;  //大于情况,将最大值减小}return {-1,-1};}
};

2441. 与对应负数同时存在的最大正整数

题解:枚举右,维护左。遍历数组,在当前位置的左边进行查找判断是否存在与当前位置相反的数据,如果有再与最大值进行比较即可。

class Solution {
public:int findMaxK(vector<int>& nums) {int n=nums.size();  //数组的长度unordered_set<int> st; //存储左边的数据int ret=-1;  //存储满足条件的最大值for(int i=0;i<n;i++){if(st.count(-nums[i])) ret=max(ret,abs(nums[i]));  //判断前面是否存在满足条件的数据,若存在判断是否需要进行更新st.insert(nums[i]);   //将该位置进行存储}return ret;}
};

此题也可以使用排序+相向双指针;left从最小的位置向后走,right从最大的位置向前走,实现起来也不是很难,此处就直接贴代码了,可以看看。

class Solution {
public:int findMaxK(vector<int>& nums) {int n=nums.size();sort(nums.begin(),nums.end()) ;  //排序int left=0,right=n-1;while(nums[left]<0&&nums[right]>0){if(-nums[left]>nums[right]) left++;  //负数的绝对值更大,找更小的负数匹配else if(-nums[left]<nums[right]) right--;      //负数的绝对值更小,找更小的正数匹配else return nums[right];        //绝对值相等,必定是满足条件中最大的的整数}return -1;}
};

1512. 好数对的数目

题解:枚举右,维护左;在左边找相同的值的个数,遍历数组找有多少个满足条件的数组对。

class Solution {
public:int numIdenticalPairs(vector<int>& nums) {int n=nums.size();unordered_map<int,int> m;  //存储左边的数据int ret=0;for(int i=0;i<n;i++){if(m.count(nums[i])) ret+=m[nums[i]];  //判断左边是否有满足条件的数据m[nums[i]]++;  //将当前位置存储到哈希表中}return ret;}
};

此题也可以使用排序+相向双指针,但是边界情况不好处理,所以不建议。

2001. 可互换矩形的组数

题解:枚举右边,维护左边;将左边的所有宽高比存储到哈希表中,枚举右边的数据在左边进行查找看是否有慢速条件的数据。细节:此题的除法是实数除法而不是整数除法。

class Solution {
public:long long interchangeableRectangles(vector<vector<int>>& rectangles) {int n=rectangles.size();unordered_map<double ,int> m; //存储左边的数据long long ret=0;for(int i=0;i<n;i++){double x=(double)rectangles[i][0]/rectangles[i][1];if(m.count(x)) ret+=m[x];  //判断左边是否有满足条件的数据m[x]++;  //将当前位置数据存储到哈希表中}return ret;}
};

1128. 等价多米诺骨牌对的数量

题解:枚举右,维护左;将左边的所有骨牌对放入哈希表中,再遍历右边在哈希表中查找是否存在满足条件的数据。

class Solution {
public:int numEquivDominoPairs(vector<vector<int>>& dominoes) {int n=dominoes.size();map<pair<int,int>,int> m; //存储左边所有多米诺骨牌int ret=0;for(int i=0;i<n;i++){int x=dominoes[i][0],y=dominoes[i][1];pair<int,int> p={min(x,y),max(x,y)};   //将骨牌进行排序,小的在前,大的在后if(m.count(p)) ret+=m[p];   //查找m[p]++;}return ret;}
};

121. 买卖股票的最佳时机

题解:枚举右,维护左。维护左边的最小值,判断枚举右边的所有值,找到最大利润,更新左边的最小值。

class Solution {
public:int maxProfit(vector<int>& prices) {int n=prices.size();int less=prices[0];  //存储左边最小的股票价格int ret=0;for(int i=0;i<n;i++){if(less<prices[i]) ret=max(ret,prices[i]-less);   //如果当前位置比左边最小值大,判断是否有更大的利润less=min(less,prices[i]);   //对less进行更新}        return ret;}
};

219. 存在重复元素 II

题解:枚举右,维护左。将左边的所有数据(带下标)存储到哈希表中,枚举右边的数据,判断哈希表中是否存在且距离是否小于k。

class Solution {
public:bool containsNearbyDuplicate(vector<int>& nums, int k) {int n=nums.size();unordered_map<int,int> m; //存储左边的数据for(int i=0;i<n;i++){if(m.count(nums[i])&&i-m[nums[i]]<=k) return true;//判断是否相等,如果相等再判断距离是否小于km[nums[i]]=i;//将当前位置插入带哈希表中}return false;}
};

2260. 必须拿起的最小连续卡牌数

题解:枚举右,遍历左。将左边所有卡牌值和位置存储到哈希表中,枚举右边的所有卡牌值,在哈希表中查找有没有相同的,如果右再判断间距右多长。

class Solution {
public:int minimumCardPickup(vector<int>& cards) {int n=cards.size();unordered_map<int,int> m; //存储左边的卡牌值和位置int ret=INT_MAX;for(int i=0;i<n;i++){if(m.count(cards[i])) ret=min(ret,i-m[cards[i]]+1);  m[cards[i]]=i;}return ret==INT_MAX?-1:ret;}
};

2815. 数组中的最大数对和

题解:枚举右,维护左。将左边每个数位对应的最大值存储到一个长度为10的数组中,枚举右边到哈希表中找是否有对应的数位,如果有组成的和是否更大。

class Solution {
public:int maxSum(vector<int>& nums) {int n=nums.size();int m[10]={0};  //存储左边数中每个数位对应的最大数//获得最大数位auto getmax=[](int x){int ret=0;while(x){ret=max(ret,x%10);x/=10;}return ret;};int ret=-1;for(int i=0;i<n;i++){int a=getmax(nums[i]);if(m[a]!=0) ret=max(ret,m[a]+nums[i]);if(m[a]==0||m[a]<nums[i]) m[a]=nums[i];}return ret;}
};

2342. 数位和相等数对的最大和

题解:枚举右,维护左。存储左边所有数对和的最大值到哈希表中,枚举右边的数据在哈希表中找是否右对应的数据,如果有其两者的和是否更大。

class Solution {
public:int maximumSum(vector<int>& nums) {int n=nums.size();unordered_map<int,int> m; //存储每一个数对和的最大值//获得数位和auto getsum=[](int x){int sum=0;while(x){sum+=x%10;x/=10;}return sum;};int ret=-1;for(int i=0;i<n;i++){int y=getsum(nums[i]);if(m.count(y)) ret=max(ret,m[y]+nums[i]);  //对结果进行更新if(m.count(y)==0||m[y]<nums[i]) m[y]=nums[i];  //更新数位和的最大值}return ret;}
};

1679. K 和数对的最大数目

题解:枚举右,维护左。将左边的所有数据存储起来,枚举右边的数据在左边找是否有配对的。

class Solution {
public:int maxOperations(vector<int>& nums, int k) {int n=nums.size();unordered_map<int,int> m;int ret=0;for(int i=0;i<n;i++){int x=k-nums[i];if(m.count(x))   //左边有配对的{ret++;if(--m[x]==0) m.erase(x);  //个数为0,将其移除哈希表}else m[nums[i]]++;  //将该位置加入哈希表}return ret;}
};

此题使用排序+同向双指针也比较简单,对数组进行排序,维护一段区间[left,right],left和right外都是已经完成了统计,通过最大值和最小值的和判断区间如何缩小。

  • nums[left]+nums[right]>k,如果和大于k说明大了,让right--;
  • nums[left]+nums[right]<k,如果和小于k说明小了,让left++;
  • nums[left]+nums[right]==k,说明能组成一对,让left++,right--,ret++;
class Solution {
public:int maxOperations(vector<int>& nums, int k) {int n=nums.size();sort(nums.begin(),nums.end());int left=0,right=n-1,ret=0;while(left<right){if(nums[left]+nums[right]>k) right--;else if(nums[left]+nums[right]<k) left++;else right--,left++,ret++;}return ret;}
};

面试题 16.24. 数对和

题解:枚举右,维护左。此题与K和数对的最大数目类似。只不过此题需要将数对和为K的存储到数组中进行返回。

class Solution {
public:vector<vector<int>> pairSums(vector<int>& nums, int target) {//与判断数对和个数题目类似,只不过此题需要将结果放入到数组中去int n=nums.size();unordered_map<int,int> m;  //存储左边的所有数据及个数vector<vector<int>> ret;  //存储结果for(int i=0;i<n;i++){int need=target-nums[i];if(m.count(need))          //判断左边是否存在需要的数{ret.push_back({need,nums[i]});if(--m[need]==0) m.erase(need);}else m[nums[i]]++;   //将该位置放入到哈希表中}return ret;}
};

3371. 识别数组中的最大异常值

题解:此题有一些特殊,但是依旧是枚举,只不过枚举的方式有所不同。向统计数组总和为all,再枚举数组每个位置作为特殊数字和nums[i],此时满足等式:all=特殊数字和(nums[i])+剩下两个元素(特殊数字和(nums[i])+异常值)。根据等式求得异常值yc=all-2*nums[i],判断数组中是否存在该异常值即可。

class Solution {
public:int getLargestOutlier(vector<int>& nums) {int all=0;  //存储所有数据之和int n=nums.size();unordered_map<int,int> m; for(auto e:nums) m[e]++,all+=e;int ret=INT_MIN;for(int i=0;i<n;i++)  //枚举右边的所有位置作为特殊数字的和{//all=nums[i]+异常值+特殊和(nums[i])int yc=all-2*nums[i];if(m.count(yc)&&(yc!=nums[i]||m[yc]>=2)) ret=max(ret,yc);}return ret;}
};

624. 数组列表中的最大距离

题解:枚举右,维护左。记录左边所有数据中的最大值和最小值,枚举右边的所有数组,将数组中的最大值-左边最小值,左边最大值-数组最小值来得到最大距离。

class Solution {
public:int maxDistance(vector<vector<int>>& arrays) {int n=arrays.size();int less=arrays[0][0],more=arrays[0].back();  //存储左边的最大值和最小值int ret=0;for(int i=1;i<n;i++){auto& tmp=arrays[i];int x=tmp[0],y=tmp.back();  //x是当前数组的最小值,y是当前数组的最大值ret=max(ret,max(more-x,y-less));  //更新最大距离less=min(less,x);   //更新左边的最小值more=max(more,y);    //更新左边的最大值}return ret;}
};

2364. 统计坏数对的数目

题解:枚举右,维护左。此题求坏数对的个数,给定条件:j - i != nums[j] - nums[i]直接处理不好搞,那就正难则反:求非坏数对的个数即j - i == nums[j] - nums[i]即nums[j] - j == nums [i] - i的个数。此时就可以套公式,将左边所有的nums[i]-i的值都记录下来,枚举右边的数据,在左边找有多少相同的。

class Solution {
public:long long countBadPairs(vector<int>& nums) {//正难则反//直接写j-i==nums[j]-nums[i]不好处理//但是如果求j-i==nums[j]-nums[i],即nums[j]-j=nums[i]-i此时就比较号处理了int n=nums.size();unordered_map<int,int> m; //存储左边所有数据的nums[i]-i的结果long long ret=0;for(int i=0;i<n;i++){int diff=nums[i]-i;ret+=i;             //加上前面所有数据的个数if(m.count(diff)) ret-=m[diff] ;  //减去前面非坏数对的个数m[diff]++;}return ret;}
};

1014. 最佳观光组合

题解:遍历右,维护左。根据观光组合得分要求,记录左边的value[i]+i的最大值即可。

class Solution {
public:int maxScoreSightseeingPair(vector<int>& values) {//枚举右,维护左//将左边的value[i]+i的最大值维护起来int n=values.size();int ret=0,before=values[0]+0;  //before记录左边value[i]-i的最大值for(int i=1;i<n;i++){int now=values[i]-i;ret=max(ret,now+before);   //更新最高分before=max(before,values[i]+i);}return ret;}
};

1814. 统计一个数组中好对子的数目

题解:枚举右,维护左。与前两题类似都是将j和i移到同一边。

根据题目nums[i] + rev(nums[j]) == nums[j] + rev(nums[i]),将i和j移到同一侧即可:rev(nums[i]) - nums[i]  == nums[j] - rev(nums[j])。

class Solution {#define MOD 1000000007
public:int countNicePairs(vector<int>& nums) {//根据题目nums[i] + rev(nums[j]) == nums[j] + rev(nums[i])//将i和j移到同一侧即可:rev(nums[i]) - nums[i]  == nums[j] - rev(nums[j])int n=nums.size();unordered_map<int,int> m; //存储左边所有的rev(nums[i])-nums[i]的值//rev反转函数auto rev=[](int x){int ret=0;while(x){ret=ret*10+x%10;x/=10;}return ret;};long long ans=0;for(int i=0;i<n;i++){int now=rev(nums[i])-nums[i];if(m.count(now)) ans+=m[now];m[now]++;}return ans%MOD;}
};

2905. 找出满足差值条件的下标 II

题解:枚举右,维护左。此题的左有所不同,根据abs(i-j)>=indexdifference可以将区间分成三个部分。

所以在枚举右的时候是从indexdifference开始的。维护[0,j-indexdifference]区间内的最大值和最小值即可。

class Solution {
public:vector<int> findIndices(vector<int>& nums, int indexDifference, int valueDifference) {//枚举右,维护左//根据abs(i-j)>=indexDiffer,维护与当前位置相差indexDiffer个单位长度位置的左边数据int n=nums.size();if(indexDifference>=n) return {-1,-1};  //数组长度不够直接返回pair<int,int> prev_min={INT_MAX,-1},prev_max={INT_MIN,-1};  //存储左边的最大值和最小值for(int i=indexDifference;i<n;i++){int inser=i-indexDifference;if(nums[inser]<prev_min.first) prev_min={nums[inser],inser};if(nums[inser]>prev_max.first) prev_max={nums[inser],inser};   //更新最大值和最小值if(abs(prev_max.first-nums[i])>=valueDifference) return {prev_max.second,i};  //判断左边是否存在满足条件的数据if(abs(prev_min.first-nums[i])>=valueDifference) return {prev_min.second,i};}return {-1,-1};}
};

枚举进阶题目

1010. 总持续时间可被 60 整除的歌曲

题解:枚举右,维护左。统计左边左右歌曲的时间(只统计小于60的部分),枚举右边的所有数据,在左边找是否有配对的。

class Solution {
public:int numPairsDivisibleBy60(vector<int>& time) {int n=time.size();int m[60]={0}; //存储左边的所有数据,小于60秒的时间int ret=0;for(int i=0;i<n;i++){int need=(60-time[i]%60)%60;           //计算还需要多少秒,需要%60两次,防止刚好被60整除的情况ret+=m[need];             //在左边找有没有人配对的m[time[i]%60]++;        //将当前位置加入到哈希表中}return ret;}
};

3185. 构成整天的下标对数目 II

题解:此题与上一题一样,题解见上。

class Solution {
public:long long countCompleteDayPairs(vector<int>& hours) {int n=hours.size();int m[24]={0};long long ret=0;for(int i=0;i<n;i++){int need=(24-hours[i]%24)%24;ret+=m[need];m[hours[i]%24]++;}return ret;}
};

2748. 美丽下标对的数目

题解:枚举有,维护左。将左边所有数的第一位进行统计,枚举右边所有数据的最后一位,在左边找有多少个互质的数。

class Solution {
public:int countBeautifulPairs(vector<int>& nums) {int n=nums.size();int m[10]={0};   //统计第一位数字的个数auto head=[](int x)  //求出第一位的数字{while(x>=10) x/=10;return x;};int ret=0;for(int i=0;i<n;i++){int tail=nums[i]%10;   for(int i=1;i<10;i+=1)   //循环,在1-9之间找互质的个数if(gcd(i,tail)==1) ret+=m[i];m[head(nums[i])]++;}return ret;}
};

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

题解:枚举右,遍历左。此题需要对字符串中字符的种类进行统计,可以使用一个整数完成字符的统计。对1-26个英文字母可以使用26个比特位进行标记,也就是可以用一个整形(32个比特位)来表示一个字符串中字符的种类。

class Solution {
public:int similarPairs(vector<string>& words) {//对1-26个英文字母可以使用26个比特位进行标记,也就是可以用一个整形来表示一个字符串中字符的种类int n=words.size();unordered_map<int,int> m; //存储左右字符串包含的字符种类auto build=[](string& s){int x=0;for(auto& e:s){int pos=e-'a';x|=(1<<pos);   //将每个字符的情况映射到比特位上}return x;};int ret=0;for(int i=0;i<n;i++){int need=build(words[i]);if(m.count(need)) ret+=m[need];m[need]++;}return ret;}
};

2874. 有序三元组中的最大值 II

题解:进行两次枚举右,维护左。第一次向确定每个位置nums[i]-nums[j]的情况,枚举j的位置,维护其左边的最大值。在枚举k找出(nums[i]-nums[j])*nums[k]最大的情况。

class Solution {
public:long long maximumTripletValue(vector<int>& nums) {int n=nums.size();int prev_max=nums[0];vector<int> div(n);for(int i=1;i<n;i++)   //先预处理,枚举每一个j,维护左边最大值使得prev_max-nums[i]可以最大化{div[i]=prev_max-nums[i];if(nums[i]>prev_max) prev_max=nums[i];}long long the_div=div[1];long long ret=0;for(int i=2;i<n;i++)   //以及得到了nums[i]-nums[j]的情况,只需要在枚举nums[k]即可{ret=max(ret,the_div*nums[i]);if(div[i]>the_div) the_div=div[i];}return ret;}
};

454. 四数相加 II

题解:枚举右,维护左。先将nums1+nums2的所有情进行统计,再遍历-nums3-nums4看有多少匹配的。

class Solution {
public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {//枚举右,维护左//先将nums1+nums2的所有情况进行统计int n=nums1.size();unordered_map<int,int> m;for(int i=0;i<n;i++){for(int j=0;j<n;j++)m[nums1[i]+nums2[j]]++;}int ret=0;for(int i=0;i<n;i++){for(int j=0;j<n;j++){int need=-nums3[i]-nums4[j];if(m.count(need)) ret+=m[need];}}return ret;}
};

总结

枚举技巧经常使用在:有两个变量时,通过枚举可以实现将两个变量的问题转化为单变量的问题。在一些难题中,可能枚举的不再是变量,而是区间。总而言之如果出现双变量或多变量都可以思考枚举这一算法。

相关文章:

  • 【音视频】PJSIP库——pjsua命令使用详解
  • 嵌入式自学第四十二天
  • Java八股文——计算机网络「应用层篇」
  • 京东618带火四大消费 即时零售和生活服务迎来爆发
  • 一个.Net开发的功能强大、易于使用的流媒体服务器和管理系统
  • Redis 五种数据结构
  • keil新建工程文件结构和每个文件的作用解析(标准库版本)
  • 零知开源——STM32F4实现ILI9486显示屏UI界面系列教程(一):电子书阅读器功能
  • uniapp实现聊天中的接发消息自动滚动、消息定位和回到底部
  • QTableView为例:Qt模型视图委托(MVD)(Model-View-Delegate)
  • C# CSharpScript 的原理与应用
  • python校园拼团系统
  • Uniapp 中根据不同离开页面方式处理 `onHide` 的方法
  • uniapp的video遮盖了popup
  • Web安全性测试--超详细用例CASE整理总结
  • linux unix socket 通信demo
  • 理解RocketMQ顺序消息的全局有序消息和分区有序消息、延迟消息、事务消息
  • 英一真题阅读单词笔记 13年
  • JS数据类型检测方法总结
  • 大数据学习(140)-数仓概述分析
  • 淄博网站制作设计公司/关键词在线试听
  • 易云巢做网站公司/百度识图以图搜图
  • 南宁信息建设网站/关键词排名怎么做上首页
  • 全国做网站的大公司/网站域名查询ip地址
  • 如何做微信电子书下载网站/做网站多少钱
  • 哪个新闻网站做的好/电脑培训学校哪家最好