力扣Hot100-笔记

力扣热题100

2025年4月2日开始,每天刷几道。

两数之和

https://leetcode.cn/problems/two-sum/?envType=study-plan-v2&envId=top-100-liked

  • BF

    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
            // BF
            int n = nums.size();
            for (int i = 0; i < n - 1; i++) {
                for (int j = i + 1; j < n; j++) {
                    if (nums[i] + nums[j] == target)
                        return {i, j};
                }
            }
            return {-1, -1};
        }
    };
    
    • 时间复杂度:O(n^2)
    • 空间复杂度:O(1)
  • 哈希

    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
            // 哈希
            unordered_map<int,int> mp;
            for (int i = 0; i < nums.size(); i++) {
                int other = target - nums[i];
                if (mp.find(other) != mp.end()) {
                    return {mp[other], i};
                }
                mp[nums[i]] = i;
            }
            return {-1, -1};
        }
    };
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

49. 字母异位词分组

https://leetcode.cn/problems/group-anagrams/description/?envType=study-plan-v2&envId=top-100-liked

  • 排序+哈希(我的思路)

    • 建立一个哈希表,key为排序后的单词,value为一个存放字母异位词的vector
    • 从前往后遍历所有word,将其插入到哈希表中对应的vector中
    • 将结果从哈希表中提取出来,放入vector
    class Solution {
    public:
        vector<vector<string>> groupAnagrams(vector<string>& strs) {
            // 1. 如何判断两个单词是否为异位词? 排序后比较
            // 2. 如何将异位词放入同一个数组中? 哈希<排序后的单词, 字母异位词vector>
            unordered_map<string, vector<string>> mp;
            for (auto word : strs) {
                string copyWord = word;
                ranges::sort(copyWord);			// 排序
                mp[copyWord].push_back(word);	// 插入到对应字母异味词vector
            }
    
            vector<vector<string>> ans;
            ans.reserve(mp.size());				// 提前预留空间,避免反复扩容
            for (auto item : mp) {
                ans.push_back(item.second);
            }
            return ans;
        }
    };
    
    • 时间复杂度:O(n klogk),遍历排序每个单词,n为单词个数,k为单词长度
    • 空间复杂度:O(nk)
  • 计数法

    • 用一个由26个字符组成的字符串记录单词中出现的次数
    class Solution {
    public:
        vector<vector<string>> groupAnagrams(vector<string>& strs) {
            // 题目给出了str[i]仅为小写字母,即26个字符
            // 可以使用一个字符串存放单词中每个字母出现的次数
            // 1. 为什么要用字符串存放次数? 方便作为哈希表的key
            // 2. 为什么这个key可以代表字母异位词? 因为字母异位词中各字母出现的次数是一样的
            unordered_map<string, vector<string>> mp;
            for (auto word : strs) {
                string s(26, '0');
                for (auto ch : word) {
                    s[ch - 'a']++;
                }
                mp[s].emplace_back(word);
            }
    
            vector<vector<string>> ans;
            ans.reserve(mp.size());
            for (auto item : mp) {
                ans.push_back(item.second);
            }
            return ans;
        }
    };
    
    • 时间复杂度:O(nk)

    • 空间复杂度:O(nk)

2025年4月3日,今天写了5道,有些题之前做过,思路很清晰。

128. 最长连续序列

https://leetcode.cn/problems/longest-consecutive-sequence/description/?envType=study-plan-v2&envId=top-100-liked

  • 哈希表(unordered_set)

    • 题目要求时间复杂度为O(n),这意味着不能使用排序,因为排序至少要O(nlogn)的复杂度
    • 还要考虑数据重复的问题,不能让重复数据干扰判断
    class Solution {
    public:
        int longestConsecutive(vector<int>& nums) {
            // 1. 如何做到数据去重 + 快速查询? unordered_set
            // 2. 如何统计最长连续数的长度?	对于每个数x,从当前开始一直向后查看x+i是否存在
            // 3. 该算法仍然会导致O(n^2)的时间复杂度,如何解决? 
            // 		剪枝,避免重复判断。例如之前如果已经访问过x+1了,下次就不需要再查看。
            //		这可以通过判断x-1是否在unorded_set内来实现。
            unordered_set<int> st;
            int ans = 0;
            
            for (auto x : nums) {  // 入set去重+便于查询
                st.insert(x);
            }
    
            for (auto x : st) {
                if (st.count(x - 1) != 0)  	// 剪枝
                    continue;
    
                int seqCnt = 1;
                int cur = x;
                while (st.count(cur + 1) != 0) {  // 不断查看 x+i 是否存在
                    seqCnt++;
                    cur++;
                }
    
                if (seqCnt > ans) 
                    ans = seqCnt;
            }
    
            return ans;
        }
    };
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

283. 移动零

https://leetcode.cn/problems/move-zeroes/description/?envType=study-plan-v2&envId=top-100-liked

  • 双指针法

    • 两个指针slow和fast,fast用于寻找下一个不为0的元素,slow要么和fast指向相同的元素,要么指向0。
    • fast每遇到一个非0的元素,就将其与slow指向的元素交换,直至fast到到达末尾
    class Solution {
    public:
        void moveZeroes(vector<int>& nums) {
            int fast = 0;
            int slow = 0;
            while (fast < nums.size()) {
                if (nums[fast] != 0) {
                    swap(nums[fast], nums[slow++]);
                }
                fast++;
            }       
            return ;     
        }
    };
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

11. 盛最多水的容器

  • 双指针法:

    • 首先让两个指针指向数组两端
    • 计算并存储当前容量。
    • 移动高度较小的那一端,继第二步的操作,直至两个指针相遇。
  • why this algo work?这才是这道题目的核心。

    • 为什么要移动高度较低的那一端?移动高的那端不行吗?

      因为高度较低的那端是水量的瓶颈,也就是说在容器宽度确定的情况下,容器高度由较小的一端决定,如果移动较高的一端,无论是增高、不变或降低都不能是水量增加。而移动较小的一端则有可能使得总水量增大。

    • 总结下来就是:移动高端肯定不会使水量增大,而移动低端有可能使水量增大,也有可能使水量减少,因此我们要通过移动低端来遍历所有可能性,而放弃移动高端则避免了无效的尝试

    class Solution {
    public:
        int maxArea(vector<int>& height) {
            int left = 0;
            int right = height.size() - 1;
            int ans = 0;
            while (left < right) {
                int capCur = min(height[left], height[right]) * (right - left);
                ans = max(ans, capCur);
                if (height[left] < height[right])
                    left++;
                else 
                    right--;
            }
            return ans;
        }
    };
    

15.三数之和

https://leetcode.cn/problems/3sum/description/?envType=study-plan-v2&envId=top-100-liked

  • 排序+双指针法

    • 首先必须进行排序,排序后方便进行去重,并且简化了查看方式,否则时间复杂度至少需要O(n^3),还不方便去重。

    • 第一重循环确定i,注意去重和剪枝。

    • 第二重循环使用双指针法查找使得和为0的j和k,注意去重。

    • 疑问:为什么只需要对i和j去重?因为当i和j不重复时,要想使得i, j, k所指元素之和为0,k也必不会重复。

    class Solution {
    public:
        vector<vector<int>> threeSum(vector<int>& nums) {
            sort(nums.begin(), nums.end());
            int n = nums.size();
            vector<vector<int>> ans;
            for (int i = 0; i < n - 2; i++) {
                if (nums[i] > 0) break;     // 剪枝
                if (i > 0 && nums[i] == nums[i-1]) continue; // 对i去重
                
                int j = i + 1;
                int k = n - 1;
                while (j < k) {
                    if (j > i + 1 && nums[j] == nums[j-1]) { // 对j去重
                        j++;
                        continue;
                    }
                    int sum = nums[j] + nums[k] + nums[i];
                    if (sum == 0) {
                        ans.push_back({nums[i], nums[j], nums[k]});
                        j++, k--;
                    } else if (sum < 0) {
                        j++;
                    } else {
                        k--;
                    }
                }
            }
            return ans;
        }
    };
    
    • 时间复杂度:O(n^2)
    • 空间复杂度:O(1)
  • 优化版本:

    class Solution {
    public:
        vector<vector<int>> threeSum(vector<int>& nums) {
            sort(nums.begin(), nums.end());
            int n = nums.size();
            vector<vector<int>> ans;
            for (int i = 0; i < n - 2; i++) {
                int x = nums[i];
                if (i > 0 && nums[i] == nums[i-1]) continue; // 对i去重
                if (x + nums[i+1] + nums[i+2] > 0) break;    // 剪枝
                if (x + nums[n-1] + nums[n-2] < 0) continue; // 剪枝
                
                int j = i + 1;
                int k = n - 1;
                while (j < k) {
                    int sum = nums[j] + nums[k] + nums[i];
                    if (sum < 0) {
                        j++;
                    } else if (sum > 0) {
                        k--;
                    } else {
                        ans.push_back({nums[i], nums[j], nums[k]});
                        j++, k--;
                        while (j < k && nums[j] == nums[j-1]) j++;	// 去重
                        while (j < k && nums[k] == nums[k+1]) k--;
                    }
                }
            }
            return ans;
        }
    };
    

42.接雨水

https://leetcode.cn/problems/trapping-rain-water/?envType=study-plan-v2&envId=top-100-liked

  • 记录左右最大高度(三次遍历)

    • 使用两个数组分别记录左侧最大高度和右侧最大高度(注意最左端和最右端的最大高度为自己),各一次遍历
    • 当前位置的水位由左右两侧最大高度的小者决定,并且还要减去当前位置本身的高度
    • 因此遍历一次,将每个位置的水量加到结果中
    class Solution {
    public:
        int trap(vector<int>& height) {
            vector<int> lmax(height.size(), 0);
            vector<int> rmax(height.size(), 0);
            int n = height.size();
            lmax[0] = height[0];
            rmax[n-1] = height.back();
            
            for (int i = 1; i < n; i++) {       // 记录左侧最大高度
                lmax[i] = max(lmax[i-1], height[i]);
            }
    
            for (int i = n - 2; i >= 0; i--) {  // 记录右侧最大高度
                rmax[i] = max(rmax[i+1], height[i]);
            }
    
            int ans = 0;
            for (int i = 0; i < n; i++) {       // 加上左右最大高度的低端高度-当前高度
                ans += min(lmax[i], rmax[i]) - height[i];
            }
    
            return ans;
        }
    };
    
    • 时间复杂度:O(n),三次遍历
    • 空间复杂度:O(n),两个长度为n的数组,分别保存左右侧的最大高度
  • 双指针法

    • 这里使用双指针避免开辟两个最大高度数组
    class Solution {
    public:
        int trap(vector<int>& height) {
            int left = 0, right = height.size() - 1;
            int lmax = height[left], rmax = height[right];
            int ans = 0;
            while (left < right) {
                lmax = max(lmax, height[left]);
                rmax = max(rmax, height[right]);
                if (height[left] < height[right]) {
                    ans += lmax - height[left];
                    left++;
                } else {
                    ans += rmax - height[right];
                    right--;
                }
            }
            return ans;
        }
    };
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(1)
  • 单调栈法(待学习)

接雨水没做出来,看了题解的思路,自己实现了一遍。

posted @ 2025-04-02 10:54  sys_malloc  阅读(112)  评论(0)    收藏  举报
总访问: counter for blog 次