Leetcode热题100

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);
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值