重温hot100-day1

1. 1-两数之和

哈希做法,key是nums[i],value是i。遍历nums,找是否有target-nums[i],如果说明找到结果,直接打包返回就行。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> map;
        vector<int> result;
        for(int i=0; i<nums.size(); i++) //构建哈希表
            map.insert(make_pair(nums[i], i));
        for(int i=0; i<nums.size(); i++){
            auto it = map.find(target-nums[i]); //找另一个数
            if(it != map.end() && it->second != i){ //注意不能是自己
                result.emplace_back(i);
                result.emplace_back(it->second); //找到就打包返回
                break;
            }
        }
        return result;
    }
};

2. 49-字母异位词分组

哈希表,先求得每个string得hash,然后再构建一个<hash, vector>得map,这样就把相同hash(异位词)分组了

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> result;
        map<map<char, int>, vector<string>> res;
        vector<map<char, int>> temps;
        for(const auto& str:strs){ //求每个string得hash
            map<char, int> temp;
            for(const auto& s:str){
                if(temp.find(s) != temp.end()){
                    temp[s]++;
                }else{
                    temp.insert(make_pair(s,1));
                }
            }
            temps.emplace_back(temp);
        }
        for(int i=0; i<strs.size(); i++){ //找hash对应的vector
            if(res.find(temps[i]) != res.end()){ //如果有,追加string
                res[temps[i]].emplace_back(strs[i]);
            }else{ //如果没有,新建一个
                vector<string> vec;
                vec.emplace_back(strs[i]);
                res.insert(make_pair(temps[i], vec));
            }
        }
        for(const auto& re:res){ //res中就是已经分组好的
            result.emplace_back(re.second);
        }
        return result;
    }
};

3. 128-最长连续序列

这道题存在一个nums中找目标元素的技巧,利用map的find。其次就是,找连续,每个元素的+1在,那就while就行,需要剪枝,我们是从小开始判断的,例如,1、2、3.那么,从1判断到3.因此如果一个元素的-1在数组中,就说明他已经在前一轮判断中了,可以直接continue

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        int result_key = 0;
        map<int, int> temps;
        for(auto num:nums){  //以便快速查找,构建map
            if(temps.find(num) == temps.end()){
                temps.insert(make_pair(num, 1));
            }else{
                continue;
            }
        }
        for(auto temp:temps){
            int index = 0;
            if(temps.find(temp.first-1) != temps.end()) //说明已经在前一轮判断中了,连续,因此continue
                continue;
            while(temps.find(temp.first+index) != temps.end()){ //开始判断连续
                // res.emplace_back(nums[i]+index);
                index++;
            }
            result_key = result_key>index ?result_key:index;

        }
        return result_key;
    }
};

4. 283-移动零

双指针问题,快指针遍历,慢指针表示该存储的位置,快指针元素只要不是0,就赋值给慢指针。最后快指针结束,再把慢指针后面的位置补0就好。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int low = 0;
        int fast  = 0;
        while(fast<nums.size()){
            if(nums[fast] !=0){
                nums[low++] = nums[fast]; //移动到前面(low)
            }
            fast++;
        }
        while(low<nums.size()){ //最后的补0;
            nums[low++] = 0;
        }

    }
};

5. 11-盛最多水的容器

双指针问题,左右指针位置代表容器两边,往中间遍历,不断更新容积。首先对于容器高,肯定是两个指针中高度较小的是h。其次就是更新,优先动高度小的,因为要找容积大的

class Solution {
public:
    int maxArea(vector<int>& height) {
        int i = 0;
        int j = height.size()-1;
        int result = 0;
        while(i<=j){
            int h = min(height[i], height[j]);//小的是h
            int temp = (j-i)*h; //计算容积
            result = max(result, temp);
            if(height[i] > height[j]) //更新小的,为了找大的
                j--;
            else
                i++;
        }
        return result;
    }
};

补充1- 721账户合并

这道题第一眼以为很简单,但按照暴力思路做,其中至少有4层for循环,夹杂各种if判断,妥妥ss代码,并且大概率会超时,因此果断放弃。
本题关键点在于,怎么判断两个邮箱是否相同,即一个邮箱是否在另一个用户邮箱出现过,出现过,就说明这两个邮箱组同属于一个人,就要合并,如果没有出现过,哪怕两个账户组名称一样,那也是两个邮箱组。
进一步剖析,有一堆邮箱,若干个邮箱是一个组,某个邮箱会在两个组内出现(这道题好处就是整体处理不用管名称,邮箱就是唯一决定因素,也就是说不存在相同邮箱不同名称的情况,因此只要有两个相同邮箱,就合并),此时需要将这两个邮箱组合并,嗯?这不就是并查集么

  • 首先,先做准备工作,对于每个邮箱先映射为int,整体操作都用int,相同邮箱就是一个int;再邮箱映射到账户名称,为了最后返回时找到邮箱组对应的名称(题目要求的格式,第一个是名称,后面是邮箱)
  • 第二步,就是重点,对于一个邮箱组,合并其中的所有邮箱,并查集的合并操作。如果有一个邮箱在两个邮箱组,那这两个邮箱组也会合并在一起。此时已经完成任务了,集合内的每个团就是一个要返回的邮箱组,剩下就是处理返回值就好
  • 第三步就是将同一个根节点的邮箱(同一个邮箱组)放在一起,在利用一个map<int, vector>,key就用该邮箱组的根节点就好。之前保存过邮箱到int的映射关系,这一步就是find每个int,都会返回根节点,然后把email插入到对应key(根节点int)的value中即可。这要就得到结果的每个邮箱组的vector了
  • 遍历第三步得到的map,每个value就是一个邮箱组,然后首部加入邮箱组对应的名称就好,随便用一个邮箱就行,第一步得到过邮箱->名称。就大功告成
class Solution {
public:
    class UnionFind {
    private:
        vector<int> p;
    public:
        UnionFind(int n) : p(n) {
            iota(p.begin(), p.end(), 0);
        }

        int find(int x) {
            return p[x] == x ? x : p[x] = find(p[x]);
        }

        void unite(int x, int y) {
            p[find(x)] = find(y);
        }
    };
    vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
        map<string, int> emailToIndex;
        unordered_map<string, string> emailToName;
        for (auto& account : accounts) {
            for (int i = 1; i < account.size(); ++i) {
                string& email = account[i];
                if (!emailToIndex.contains(email)) {
                    emailToIndex[email] = emailToIndex.size();  //邮箱->账户id的哈希
                    emailToName[email] = account[0];  //邮箱->账户的哈希
                }
            }
        }
        UnionFind uf(emailToIndex.size());
        for (auto& account : accounts) {
            for (int i = 2; i < account.size(); ++i) {
                //账户id放入集合:每个账户的邮箱都跟第一个邮箱合并
                uf.unite(emailToIndex[account[1]], emailToIndex[account[i]]);  
            }
        }
        unordered_map<int, vector<string>> indexToEmails;
        for (auto& [email, index] : emailToIndex) {
            indexToEmails[uf.find(index)].emplace_back(email); //将同属于一个集合的邮箱放在一起
        }
        vector<vector<string>> ans;
        for (auto& [_, emails] : indexToEmails) {
            vector<string> account;
            account.emplace_back(emailToName[emails[0]]); //先根据第一个邮箱把对应账户加进去
            for (auto& email : emails) { //再一次加入邮箱即可。
                account.emplace_back(email);
            }
            ans.emplace_back(account);
        }
        return ans;
    }
};

6 15-三数之和

典型双指针应用,第一个数就是一个for循环的i,然后双指针分别代表大于i的最小值和最大值。利用while改变两个指针的位置就行。重点在于有可能有重复的值,需要剪枝,第一个是i重复,需要continue。第二是两个指针++或者–值相同,那就利用while直到++或者–到不重复的值;代码很直观,记得还有个变种,三数之和最接近于target。一样的道理。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for(int i=0;i<nums.size();i++){
            if(nums[i] > 0)
                return result;
            if(i>0 && nums[i] == nums[i-1])
                continue;
            int left = i+1;
            int right = nums.size()-1;
            while(right>left){
                if(nums[i]+nums[left]+nums[right] > 0)
                    right--;
                else if(nums[i]+nums[left]+nums[right] < 0)
                    left++;
                else{
                    result.emplace_back(vector<int> {nums[i], nums[left], nums[right]});
                    while(right>left && nums[left] == nums[left+1])
                        left++;
                    while(right>left && nums[right] == nums[right-1])
                        right--;
                    right--;
                    left++;
                }
            }
        }
        return result;
    }
};

7. 42 接雨水

这道题解法很多,首先单调栈解法:
能接雨水,就是有一个大小大的三元素。从左往右,栈元素递减(底部往上),这样每次遍历一个新元素,如果大于栈顶元素,那就满足大小大,就需要计算面积。如果小于,那就push。

class Solution {
public:
    int trap(vector<int>& height) {
        if(height.size() <= 2) return 0;
        stack<int> st;
        int result = 0;
        st.push(0);
        for(int i=1; i<height.size(); i++){
            if(height[i]<height[st.top()]) //单调递减
                st.push(i);
            else if (height[i] == height[st.top()]){ //值相同,忽略不计
                st.pop();
                st.push(i);
            }else{
                while(!st.empty() && height[i]>height[st.top()]){
                    int mid = st.top(); //这是底部
                    st.pop();
                    if(!st.empty()){
                        int h = min(height[i], height[st.top()]) - height[mid];
                        result += (i-st.top()-1)*h;
                    }
                    
                }
                st.push(i);
            }
        }
        return result;
    }
};

8. 3 无重复字符的最长字串

典型的滑动窗口题目,从左往右遍历,窗口右边不断更新(往右),直到遇到重复字符。那就下一趟for,窗口需要右移动(弹出左边元素)

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char> check; //记录char是否出现(无重复)
        int n = s.size();
        int result = 0, right=-1;
        for(int i=0; i<n; i++){
            if(i != 0){ 
                check.erase(s[i-1]); //滑动窗口,每次窗口往右,因此左边的需要erase
            }
            while(right+1 < n && !check.count(s[right+1])){ //逐步增加窗口右边边界,直到重复
                check.insert(s[right+1]);
                right++;
            }
            result = max(result, right-i+1);  //更新result
        }
        return result;
    }
};

9. 438 找到字符串中的所有字母异位词

滑动窗口记录两个map,一个是目标的map,另一个查询的滑动窗口map,从左往右更新,从0开始,如果相同,就说明找到了起始位置,push(i)即可。更新滑动窗口,将i弹出,把右边界i+p.size()加入。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int num_s=s.size(), num_p=p.size();
        vector<int> result;
        vector<int> sCount(26, 0);
        vector<int> pCount(26, 0);
        if(num_p > num_s)
            return result;
        for(int i=0;i<num_p;i++){ //记录长度为p的滑动窗口
            sCount[s[i]-'a']++;
            pCount[p[i]-'a']++;
        }
        // if(sCount == pCount)
        //     result.emplace_back(0);
        for(int i=0;i<=num_s-num_p;i++){
            if(sCount == pCount) //找到符合的窗口,加入i
                result.emplace_back(i);
            if(i==num_s-num_p){ //已经到最后一个位置了
                break;
            }
            --sCount[s[i]-'a']; //弹出左边
            ++sCount[s[i+num_p]-'a']; //加入右边
        }
        return result;
    }
};

10. 560 和为K的子数组

这道题是我感觉目前最难的理解的一个,暴力很简单,但数组过长应该会超时,因此需要遍历每个子数组的值。
因此这道题方法是利用前缀和。这样想,假如现在位置前缀和为b,之前某个位置前缀和为a,如果,b-a=target,是不是就说明,存在一个连续数组,就是这两个位置之间的元素之和为target,满足条件。等式变化一下,a=b-target。因此我们每次得到一个前缀和x,都在map中找找是不是有个x-target。有,就说明存在一个子数组。
反正第二次做,依旧想不来,研究题解半天才慢慢理解意思。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        mp[0] = 1; //不是很理解
        int count = 0, pre = 0;
        for (auto& x:nums) {
            pre += x;
            if (mp.find(pre - k) != mp.end()) { //找到目标
                count += mp[pre - k];
            }
            mp[pre]++;
        }
        return count;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值