1. 两数之和
暴力:{i,j}直接返回vector<int>
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0;i<nums.size();i++){
for(int j=i+1;j<nums.size();j++){
if(nums[i]+nums[j]==target){
//大括号里面直接写元素->vector
return {i,j};
}
}
}
return {};
}
};
哈希表:
map: 红黑树 具有自动排序的功能,是非严格的二叉搜索树。map内部的所有元素都是有序的,使用中序遍历可将键值按照从小到大遍历出来。插入的时间是O(logn),查询时间是O(logn)。可以支持所有类型的键值对
unordered_map: 哈希表(也叫散列表,通过关键码值映射到Hash表中一个位置来访问记录,查找的复杂度是O(1)。无序的(海量数据广泛应用)。插入的时间是O(logn),查询时间是O(1)。它的key只能是int、double等基本类型以及string,而不能是自己定义的结构体。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hashtable;
for (int i = 0; i < nums.size(); ++i) {
auto it = hashtable.find(target - nums[i]);
if (it != hashtable.end()) {
return {it->second, i};
}
hashtable[nums[i]] = i;
}
return {};
}
};
2. 字母异位词分组
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string,int> mp;
vector<vector<string>> res;
int num=-1;
for(auto i:strs){
string s=i;
sort(s.begin(),s.end());
if(mp.count(s)==0){
mp[s]=++num;
res.push_back({});
}
res[mp[s]].push_back(i);
}
return res;
}
};
官方:(还有一种是计每个字母出现的次数)
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> mp;
for (string& str: strs) {
string key = str;
sort(key.begin(), key.end());
mp[key].emplace_back(str);
}
vector<vector<string>> ans;
for (auto it = mp.begin(); it != mp.end(); ++it) {
ans.emplace_back(it->second);
}
return ans;
}
};
3. 最长连续序列
在未排序的数组中找到最长的连续序列。时间复杂度On,数字范围整个INT。
第一种:不断更新最长连续序列的左右两个端点 记录的最长连续序列值。
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_map<int,int> mp;
int mx=0;
for(auto i:nums){
//未更新过的,已经在的话再更新会翻倍!
if(mp.count(i)) continue;
//找出当前数字左右两端的最长连续序列的长度
int l=0;
if(mp.count(i-1)) l=mp[i-1];
int r=0;
if(mp.count(i+1)) r=mp[i+1];
//更新当前节点
mp[i]=l+r+1;
mx=max(mx,mp[i]);
//更新当前节点所在的连续序列的左右两个端点的最长序列值,一定是最大的,因为在原来的基础上把左或右还有当前节点加上了!
//只需要告知最左右两个就可以,因为之后出现的数一定是未出现过的,在这些区间外的点!!
mp[i-l]=mp[i];
mp[i+r]=mp[i];
}
return mx;
}
};
第二种:回溯,记录每个点能找到的最右端点。初始化为i+1。
class Solution {
public:
unordered_map<int,int> mp;
int find(int x){
if(!mp.count(x)) return x;
else return mp[x]=find(mp[x]);
}
int longestConsecutive(vector<int>& nums) {
for(auto i:nums) mp[i]=i+1;
int mx=0;
for(auto i:nums){
mx=max(mx,find(i)-i);
}
return mx;
}
};
第三种:集合 直接找
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> s;
for(auto i:nums){
s.insert(i);
}
int mx=0,now=0,cnt=0;
for(auto i:s){
if(!s.count(i-1)){
now=i;cnt=1;
while(s.count(now+1)){
now++;
cnt++;
}
}
mx=max(mx,cnt);
}
return mx;
}
};
4. 移动零
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int i=0,j=0,cnt=0;
for(i=0;i<nums.size();i++){
if(nums[i]!=0){
nums[j++]=nums[i];
}
else{
cnt++;
}
}
while(cnt--){
nums[j++]=0;
}
}
};
5. 盛最多水的容器
双指针:分别指向最左和最右,尽量保留 越靠近边上的 并且 高的
即无论我们怎么移动右指针,得到的容器的容量都小于移动前容器的容量。也就是说,这个左指针对应的数不会作为容器的边界了,那么我们就可以丢弃这个位置,将左指针向右移动一个位置,此时新的左指针于原先的右指针之间的左右位置,才可能会作为容器的边界。
class Solution {
public:
int maxArea(vector<int>& height) {
int i=0,j=height.size()-1,mx=0,now=0;
while(i<j){
now=(j-i)*min(height[i],height[j]);
mx=max(mx,now);
if(height[i]<height[j]){
i++;
}
else{
j--;
}
}
return mx;
}
};
6. 三数之和
如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足a+b+c=0。枚举一个元素 b′ 时,由于 b′>b,那么满足 a+b′+c′=0 的 c' 一定有 c′<c,即 c′在数组中一定出现在 c 的左侧。也就是说,我们可以从小到大枚举b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
int len=nums.size();
int k=len-1;
vector<vector<int>> res;
for(int i=0;i<len;i++){
if(i==0||nums[i-1]!=nums[i]){
k=len-1;
for(int j=i+1;j<len;j++){
if(j==i+1||nums[j-1]!=nums[j]){
while(k>j+1&&(nums[i]+nums[j]+nums[k])>0){
k--;
}
if(k>j&&(nums[i]+nums[j]+nums[k])==0){
res.push_back({nums[i],nums[j],nums[k]});
}
//cout<<nums[i]<<" "<<nums[j]<<" "<<nums[k]<<endl;
}
}
}
}
return res;
}
};
7. 接雨水
计算当前位置能接的雨水,左右两边最高的 较小值 就是能到达的最高。
class Solution {
public:
int trap(vector<int>& height) {
int len=height.size();
int left[200005],right[200005];
left[0]=height[0];
for(int i=1;i<height.size();i++){
left[i]=max(left[i-1],height[i]);
}
right[height.size()-1]=height[height.size()-1];
for(int j=height.size()-2;j>=0;j--){
right[j]=max(right[j+1],height[j]);
}
int sum=0;
for(int i=0;i<height.size();i++){
sum+=min(left[i],right[i])-height[i];
}
return sum;
}
};
其实用两个常量记录就可以,不需要开数组
8. 无重复字符的最长子串
基本的滑动窗口
class Solution {
public:
int lengthOfLongestSubstring(string s) {
map<char,int> mp;
int mx=0,i=0,j=0;
while(i<s.size()&&j<s.size()){
if(mp[s[j]]==0){
mp[s[j]]++;
mx=max(mx,j-i+1);
j++;
}
else{
mp[s[i]]--;
i++;
}
}
return mx;
}
};
9. 找到字符串中所有字母异位词
方法一:直接比较两个vector的大小(不推荐)
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
vector<int> mp(26);
vector<int> ms(26);
for(int i=0;i<p.size();i++){
mp[p[i]-'a']++;
}
for(int i=0;i<s.size();i++){
ms[s[i]-'a']++;
if(i>=p.size()){
ms[s[i-p.size()]-'a']--;
}
//cout<<ms['a']<<" "<<ms['b']<<" "<<ms['c']<<endl;
if(mp==ms) ans.push_back(i-p.size()+1);
}
return ans;
}
};
方法二:用一个map或vector数组记录字符串s和p的差值,diff记录不一样的个数。滑动窗口移动,记录的是 两段字符串中 所有字母的差值,如果是p中没有的字母,也一样会加上再减去,不影响结果的!(注意看-‘a’)
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
map<int,int> m;
for(int i=0;i<p.size();i++){
m[s[i]-'a']++;
m[p[i]-'a']--;
}
int diff=0;
for(int i=0;i<26;i++){
if(m[i]!=0) diff++;
}
vector<int> ans;
if(!diff) ans.push_back(0);
for(int i=p.size();i<s.size();i++){
if(m[s[i]-'a']==-1) diff--;
else if(m[s[i]-'a']==0) diff++;
m[s[i]-'a']++;
if(m[s[i-p.size()]-'a']==1) diff--;
else if(m[s[i-p.size()]-'a']==0) diff++;
m[s[i-p.size()]-'a']--;
if(diff==0) ans.push_back(i-p.size()+1);
}
return ans;
}
};
10. 和为 K 的子数组
子数组是连续的,子序列不一定是连续的。
计算和为k的子数组,连续段的和就要想要 前缀和,一次遍历边记录当前和,边计算有几个可以的。(经典题)
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int cnt=0,sum=0;
map<int,int> mp;//记录数组前缀和出现次数
mp[0]++;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
cnt+=mp[sum-k];
mp[sum]++;
}
return cnt;
}
};
11. 滑动窗口最大值
方法一:优先队列,定义pair,取值为第一个元素的最大值,判断位置弹出。
New: std::priority_queue<T,Container,Compare>::emplace 推入新元素到 priority_queue 。原位构造元素,即不进行移动或复制操作。以与提供给函数者准确相同的参数调用元素的构造函数。
等效地调用 c.emplace_back(std::forward<Args>(args)...); std::push_heap(c.begin(), c.end(), comp); 。
这里的话 q.emplace(nums[i],i);
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
priority_queue<pair<int,int>> q;
vector<int> ans;
for(int i=0;i<nums.size();i++){
while(!q.empty()&&q.top().second<=i-k) q.pop();
q.push(make_pair(nums[i],i));
if(i>=k-1) ans.push_back(q.top().first);
}
return ans;
}
};
方法二:维护一个单调队列,下标大小递增,值大小递减。因为如果现在滑动窗口内出现最大值,前面的最大值都可以忽略掉了,比它小的值有可能后面成为最大值。
(不用pair,只记录下标也可以的)
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<pair<int,int>> q;
vector<int> ans;
for(int i=0;i<nums.size();i++){
while(!q.empty()&&q.front().second<=i-k) q.pop_front();
while(!q.empty()&&q.back().first<=nums[i]) q.pop_back();
q.push_back(make_pair(nums[i],i));
if(i>=k-1) ans.push_back(q.front().first);
}
return ans;
}
};
方法三:类似稀疏表,分组(i从0--len-1 k个为一组)记录每个位置前后缀的最大值;
假如要求nums[i]到nums[i+k-1]的最大值,则可以取 i 到 i分组的末尾 的最大值(即i的后缀)和 i分组的下一组 到 i+k-1的最大值(即i+k-1的前缀),这两个数中的最大值即为答案,很巧妙的思想!!
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int len=nums.size();
int p[len+3],s[len+3];
for(int i=0;i<nums.size();i++){
if(i%k==0){
p[i]=nums[i];
}
else{
p[i]=max(p[i-1],nums[i]);
}
}
for(int i=nums.size()-1;i>=0;i--){
if((i+1)%k==0){
s[i]=nums[i];
}
else{
s[i]=max(s[i+1],nums[i]);
}
}
vector<int> ans;
for(int i=0;i<=nums.size()-k;i++){
ans.push_back(max(s[i],p[i+k-1]));
}
return ans;
}
};
12. 最小覆盖子串
找一个字符串s中包含字符串t的最小子串。很明显的滑动窗口,但是怎么计数和怎么滑动需要思考。计数
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char,int> mp;
int cnt=0,mx=0,l=0;
for(int i=0;i<t.size();i++) mp[t[i]]++,cnt++;
for(int i=0,j=0;j<s.size();j++){
if(mp[s[j]]>0) cnt--;
mp[s[j]]--;
if(cnt==0){
while(i<s.size()&&mp[s[i]]<0){
mp[s[i]]++;
i++;
}
if(mx==0||mx>j-i+1) mx=j-i+1,l=i;
mp[s[i]]++,i++,cnt++;
}
}
return s.substr(l,mx);
}
};