1. 滑动窗口&set——无重复最长子串
示例:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,”pwke” 是一个子序列,不是子串。**
class Solution {public:int lengthOfLongestSubstring(string s) {// 哈希集合,记录每个字符是否出现过unordered_set<char> occ;int n = s.size();// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动int rk = -1, ans = 0;// 枚举左指针的位置,初始值隐性地表示为 -1for (int i = 0; i < n; ++i) {if (i != 0) {// 左指针向右移动一格,移除一个字符occ.erase(s[i - 1]);}while (rk + 1 < n && !occ.count(s[rk + 1])) {// 不断地移动右指针occ.insert(s[rk + 1]);++rk;}// 第 i 到 rk 个字符是一个极长的无重复字符子串ans = max(ans, rk - i + 1);}return ans;}};
扩展:
使用一种数据结构来判断 是否有重复的字符: 哈希集合( C++ 中的 std::unordered_set )
法二: 哈希表维护字符对应的位置,并维护left变量class Solution {public:int lengthOfLongestSubstring(string s) {unordered_map<char,int> ch2pos;//位置范围1...nint maxlen = 0, left = 1;for(int i=0;i<s.size();i++){if(ch2pos[s[i]] >= left){left = ch2pos[s[i]] + 1;}//其他情况;字符从出现过 或 在left位置之前ch2pos[s[i]] = i+1;maxlen = max(maxlen, i+1 - left + 1);}return maxlen;}};
2. 单调栈——高楼数组
例题:小Q在周末的时候和他的小伙伴来到大城市逛街,一条步行街上有很多高楼,共有n座高楼排成一行。
小Q从第一栋一直走到了最后一栋,小Q从来都没有见到这么多的楼,所以他想知道他在每栋楼的位置处能看到多少栋楼呢?(当前面的楼的高度大于等于后面的楼时,后面的楼将被挡住)
输入描述:
输入第一行将包含一个数字n,代表楼的栋数,接下来的一行将包含n个数字w(1<=i<=n),代表每一栋楼的高度。
1<=n<=100000;
1<=w<=100000;
输出描述:
输出一行,包含空格分割的n个数字v,分别代表小Q在第i栋楼时能看到的楼的数量。
示例1
输入
6
5 3 8 3 2 5
输出
3 3 5 4 4 4
说明**
当小Q处于位置3时,他可以向前看到位置2,1处的楼,向后看到位置4,6处的楼,加上第3栋楼,共可看到5栋楼。当小Q处于位置4时,他可以向前看到位置3处的楼,向后看到位置5,6处的楼,加上第4栋楼,共可看到4栋楼。
#include <iostream>#include <vector>#include <stack>#include <algorithm>using namespace std;vector<int> a, b;stack<int> st1, st2;int main() {int n, x[100001];cin >> n;int ans = 0;for (int i = 0; i < n; i++) cin >> x[i];for (int i = 0, j = n - 1; i < n, j >= 0; i++, j--) {a.push_back(st1.size());b.push_back(st2.size());while (!st1.empty() && st1.top() <= x[i]) st1.pop();while (!st2.empty() && st2.top() <= x[j]) st2.pop();st1.push(x[i]);st2.push(x[j]);}reverse(b.begin(), b.end());for (int i = 0; i < n; i++) cout << b[i] + a[i] + 1<< " ";return 0;}
3. 有序集合划分——求两个正序数组的中位数
4. 双指针
三数之和
题目:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。示例 1:输入:nums = [-1,0,1,2,-1,-4]输出:[[-1,-1,2],[-1,0,1]]
思路:利用有序数组中两数和为固定值时,可分别设双指针从首尾往中间查找。
class Solution {public:vector<vector<int>> threeSum(vector<int>& nums) {//先排序sort(nums.begin(),nums.end());vector<vector<int> > res;int n = nums.size();for(int i=0;i<n;i++){if(i>0&&nums[i]==nums[i-1]) continue;//跳过重复值int j=i+1,k=n-1;//固定nums[i]双指针移动j、kwhile(j<k){if(j>i+1&&nums[j]==nums[j-1]){j++;continue;//跳过重复值}if(k<n-1&&nums[k]==nums[k+1]){k--;continue;//跳过重复值}if(nums[i] + nums[j] + nums[k] == 0){res.push_back({nums[i], nums[j], nums[k]});j++;k--;}else if(nums[i] + nums[j] + nums[k] > 0){k--;}else{j++;}}}return res;}};
三个间谍(滑动窗口)
题目:我叫王大锤,是一名特工。我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺。和我一起行动的还有另外两名特工,我提议1. 我们在字节跳动大街的N个建筑中选定3个埋伏地点。2. 为了相互照应,我们决定相距最远的两名特工间的距离不超过D。请听题:给定N(可选作为埋伏点的建筑物数)、D(相距最远的两名特工间的距离的最大值)以及可选建筑的坐标,计算在这次行动中,大锤的小队有多少种埋伏选择。注意:1. 两个特工不能埋伏在同一地点2. 三个特工是等价的:即同样的位置组合(A, B, C) 只算一种埋伏方法,不能因“特工之间互换位置”而重复使用示例1输入:4 31 2 3 4输出:4说明:可选方案 (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)
#include <iostream>#include <vector>using namespace std;long long C(long long n){return (n-1) * n / 2;}int main(){long long n, d, count = 0;cin>> n>> d;vector<long long> v(n);for (int i = 0, j = 0; i < n; i++) {cin>> v[i];while (i >= 2 && (v[i] - v[j]) > d) {j++;}count += C(i - j);}cout << count % 99997867;return 0;}
5. 前缀和/积
左右乘积列表——除自身以外数组的乘积
题目:给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。示例:输入: [1,2,3,4]输出: [24,12,8,6]提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。进阶:你可以在常数空间复杂度内完成这个题目吗?
思路:res[i] = left[i] * right[i]
class Solution {public:vector<int> productExceptSelf(vector<int>& nums) {int n = nums.size();vector<int> res(n,1);int left = 1;for(int i=0;i<n;i++){if(i>0) left *= nums[i-1];res[i] *= left;}int right=1;for(int j=n-1;j>=0;j--){if(j<n-1) right *= nums[j+1];res[j] *= right;}return res;}};
二维前缀和
题目:
给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。
算法:利用二维前缀和![@{ECNQUO(E]}]ZU_RU%R`6A.png](/uploads/projects/kenelmq@algorithm/d55fd7c8781670a4d681e1b7de9d88d3.png)
class NumMatrix {vector<vector<int>> sums;public:NumMatrix(vector<vector<int>>& matrix) {int m=matrix.size(),n=matrix[0].size();if(m>0){sums.resize(m + 1, vector<int>(n + 1));for(int i=0;i<m;i++){for(int j=0;j<n;j++){sums[i+1][j+1] = sums[i+1][j] + sums[i][j+1] - sums[i][j] + matrix[i][j];}}}}int sumRegion(int row1, int col1, int row2, int col2) {return sums[row2+1][col2+1] + sums[row1][col1] - sums[row1][col2+1] - sums[row2+1][col1];}};/*** Your NumMatrix object will be instantiated and called as such:* NumMatrix* obj = new NumMatrix(matrix);* int param_1 = obj->sumRegion(row1,col1,row2,col2);*/
6. 大数加法——字符串相加
思路:从后往前串行相加,数据不够按0相加。
class Solution {public:string addStrings(string num1, string num2) {int i = num1.length() - 1, j = num2.length() - 1, add = 0;string ans = "";while (i >= 0 || j >= 0 || add != 0) {int x = i >= 0 ? num1[i] - '0' : 0; //技巧:首部无值时补零int y = j >= 0 ? num2[j] - '0' : 0;int result = x + y + add;ans.push_back('0' + result % 10);add = result / 10;i -= 1;j -= 1;}// 计算完以后的答案需要翻转过来reverse(ans.begin(), ans.end()); //翻转数组return ans;}};
7. KMP+最大不相交集合—— 寻找子串
题目:
给出m个字符串S1,S2,…,Sm和一个单独的字符串T。请在T中选出尽可能多的子串同时满足:
1)这些子串在T中互不相交。
2)这些子串都是S1,S2,…,Sm中的某个串。
问最多能选出多少个子串。
思想:
使用KMP算法得到能在T中找到的子串的首尾索引
再对得到的集合序列排序,按尾部从小到大排,按贪心算法求解。
#include <bits/stdc++.h>using namespace std;const int N = 100003;int dp[N], nxt[N];vector<vector<int>> v;void Next(string t){int i=0, j=-1, n=t.length();nxt[0] = -1;while(i<n){if(j==-1 || t[i]==t[j])nxt[++i] = ++j;elsej = nxt[j];}}void KMP(string s, string t){Next(t);int n=s.length(), m=t.length(), i=0, j=0;while(i<n){if(j==-1 || s[i]==t[j]){i++;j++;}elsej = nxt[j];if(j==m){v.push_back({i - 1, i - m});j = nxt[j];}}}int main(){int n;cin>>n;string s, t[n];for(int i=0;i<n;i++)cin>>t[i];cin>>s;for(int i=0;i<n;i++)KMP(s, t[i]);sort(v.begin(),v.end());int l = v.size();int count = 0, start = 0;for(int i = 0; i < l; ++i){if(v[i][1] < start) continue;++count;start = v[i][0] + 1;}cout<<count;return 0;}
8. 二分查找
小Q的父母要出差N天,走之前给小Q留下了M块巧克力。小Q决定每天吃的巧克力数量不少于前一天吃的一半,但是他又不想在父母回来之前的某一天没有巧克力吃,请问他第一天最多能吃多少块巧克力
输入描述:
每个输入包含一个测试用例。
每个测试用例的第一行包含两个正整数,表示父母出差的天数N(N<=50000)和巧克力的数量M(N<=M<=100000)。
输出描述:
输出一个数表示小Q第一天最多能吃多少块巧克力。
示例1
输入
3 7
输出
4
#include<iostream>using namespace std;int n,m;//计算第一天吃s个巧克力一共需要多少个多少个巧克力int sum(int s){int sum=0;for(int i=0;i<n;i++){sum+=s;s=(s+1)>>1;//向上取整}return sum;}//二分查找int fun(){if(n==1) return m;int low=1;int high=m;//第一天的巧克力一定是大于等于1小于等于m的while(low<high){int mid=(low+high+1)>>1;//向上取整if(sum(mid)==m) return mid;//如果第一天吃mid个巧克力,刚刚好吃完所有巧克力,那么直接返回else if(sum(mid)<m){low = mid; //此边界条件我很迷糊}else{high=mid - 1;}}return high;}int main(){cin>>n>>m;int res=fun();cout<<res<<endl;return 0;}
9. 滑动窗口——爱生气的书店老板
题目:
今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。
在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。
请你返回这一天营业下来,最多有多少客户能够感到满意的数量。
示例:
输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3
输出:16
解释:
书店老板在最后 3 分钟保持冷静。
感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.
算法思想:
使用「秘密技巧」能得到的最终的顾客数 = 所有不生气时间内的顾客总数 + 在窗口 X 内使用「秘密技巧」挽留住的原本因为生气而被赶走顾客数。
因此,可以把题目分为以下两部分求解:
(1)所有不生气时间内的顾客总数:使用 ii 遍历[0, customers.length)[0,customers.length),累加grumpy[i] == 0grumpy[i]==0时的customers[i]customers[i]。
(2)在窗口 X 内因为生气而被赶走的顾客数:使用大小为 X 的滑动窗口,计算滑动窗口内的grumpy[i] == 1grumpy[i]==1时的customers[i]customers[i],得到在滑动窗口内老板生气时对应的顾客数。
class Solution:def maxSatisfied(self, customers: List[int], grumpy: List[int], X: int) -> int:N = len(customers)sum_ = 0# 所有不生气时间内的顾客总数for i in range(N):if grumpy[i] == 0:sum_ += customers[i]# 生气的 X 分钟内,会让多少顾客不满意curValue = 0# 先计算起始的 [0, X) 区间for i in range(X):if grumpy[i] == 1:curValue += customers[i]resValue = curValue# 然后利用滑动窗口,每次向右移动一步for i in range(X, N):# 如果新进入窗口的元素是生气的,累加不满意的顾客到滑动窗口中if grumpy[i] == 1:curValue += customers[i]# 如果离开窗口的元素是生气的,则从滑动窗口中减去该不满意的顾客数if grumpy[i - X] == 1:curValue -= customers[i - X]# 求所有窗口内不满意顾客的最大值resValue = max(resValue, curValue)# 最终结果是:不生气时的顾客总数 + 窗口X内挽留的因为生气被赶走的顾客数return sum_ + resValue
10. 买卖股票的最佳时机
一次买卖——一次遍历
题目:给定一个股票价格序列,从某一天买入,并从另一天卖出,返回最大利润。
法一:(维护当前卖出股票时,历史最低价的股票)
如果我是在历史最低点买的股票就好了!太好了,在题目中,我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。
那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice。
法二:(维护当前买入股票时,未来最高价的股票)倒序维护一个maxprice记录一个未来最高价格
法一:class Solution {public:int maxProfit(vector<int>& prices) {int inf = 1e9;int minprice = inf, maxprofit = 0;for (int price: prices) {maxprofit = max(maxprofit, price - minprice);minprice = min(price, minprice);}return maxprofit;}};法二:class Solution {public:int maxProfit(vector<int>& prices) {int n = prices.size();int maxstock=-1, maxprofit = 0;for (int i = n - 1; i >= 0;i--){maxstock = max(maxstock, prices[i]);if(maxstock > prices[i]){maxprofit = max(maxprofit, maxstock - prices[i]);}}return maxprofit;}};
多次买卖
题目:**可以多次买卖,买一个新股票时手里不能留有股票。求最大利润。
方法:**求所有升序和。
class Solution {public:int maxProfit(vector<int>& prices) {int n = prices.size();int sum = 0;for(int i=1; i<n;i++){if(prices[i] - prices[i-1] > 0){sum += prices[i] - prices[i-1];}}return sum;}};
