力扣热题100——子串

和为K的子数组

在这里插入图片描述
我的暴力双指针解法超时,并且此方法仅适用于全非负数组!

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        int count = 0;  // 记录符合条件的子数组数量
        
        // 外层循环:固定子数组的起始位置l
        for (int l = 0; l < n; l++) {
            int current_sum = 0;  // 记录从l开始的子数组和
            
            // 内层循环:扩展子数组的结束位置r(从l到n-1)
            for (int r = l; r < n; r++) {
                current_sum += nums[r];  // 累加当前元素到子数组和
                
                // 若当前子数组和等于k,计数+1
                if (current_sum == k) {
                    count++;
                }
            }
        }
        
        return count;  // 返回总计数
    }
};

枚举:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int count = 0;
        for (int start = 0; start < nums.size(); ++start) {
            int sum = 0;
            for (int end = start; end >= 0; --end) {
                sum += nums[end];
                if (sum == k) {
                    count++;
                }
            }
        }
        return count;
    }
};

前缀和+哈希表的解法:

#include <vector>
#include <unordered_map>
using namespace std;

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        // 前缀和 -> 该前缀和出现的次数
        unordered_map<int, int> prefixSumCount;
        // 初始化:前缀和为 0 的情况至少出现 1 次(处理子数组从索引 0 开始的情况)
        prefixSumCount[0] = 1;
        
        // 当前累计的前缀和
        int currentSum = 0;
        // 记录和为 k 的子数组个数
        int result = 0;
        
        for (int num : nums) {
            // 累加当前数字,更新前缀和
            currentSum += num;
            
            // 核心逻辑:
            // 如果存在前缀和为 (currentSum - k),说明这两个前缀和之间的子数组和为 k
            // 例如:currentSum = 前缀和 A,若存在前缀和 B = A - k,则子数组 [B+1 ... A] 的和为 k
            if (prefixSumCount.find(currentSum - k) != prefixSumCount.end()) {
                result += prefixSumCount[currentSum - k];
            }
            
            // 更新当前前缀和的出现次数
            prefixSumCount[currentSum]++;
        }
        
        return result;
    }
};

滑动窗口最大值

在这里插入图片描述

#include <vector>   // 引入vector容器,用于存储结果
#include <deque>    // 引入deque双端队列,用于实现单调队列
using namespace std;

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;  // 存储每个滑动窗口的最大值
        deque<int> dq;    // 单调队列,存储元素的索引(核心数据结构)
                          // 队列特性:索引对应的nums元素值保持【单调递减】
        
        // 遍历数组中的每个元素(i是当前元素的索引)
        for (int i = 0; i < nums.size(); ++i) {
            
            // 步骤1:移除队列中【超出当前窗口范围】的元素
            // 当前窗口的有效索引范围是 [i - k + 1, i]
            // 如果队首元素的索引 <= i - k,说明它在窗口左侧外,需要删除
            if (!dq.empty() && dq.front() <= i - k) {
                dq.pop_front();  // 从队首删除
            }
            
            // 步骤2:维护队列的【单调递减特性】
            // 如果队尾元素对应的数值 <= 当前元素值,说明它不可能是未来窗口的最大值
            // 持续从队尾删除这些元素,直到队尾元素 > 当前元素或队列为空
            while (!dq.empty() && nums[dq.back()] <= nums[i]) {
                dq.pop_back();   // 从队尾删除
            }
            
            // 步骤3:将当前元素的索引加入队列
            // 此时队列仍保持单调递减,当前元素是队列中最小的(或唯一的)
            dq.push_back(i);
            
            // 步骤4:当窗口完全形成后(i >= k-1),记录当前窗口的最大值
            // 由于队列单调递减,队首元素就是当前窗口的最大值对应的索引
            if (i >= k - 1) {
                res.push_back(nums[dq.front()]);  // 加入结果
            }
        }
        
        return res;  // 返回所有窗口的最大值
    }
};

最小覆盖字串

滑动窗口加哈希表
在这里插入图片描述

#include <string>       // 引入string头文件,处理字符串
#include <unordered_map> // 引入哈希表头文件,用于统计字符出现次数
using namespace std;    // 使用std命名空间,简化代码书写

class Solution {
public:
    // 函数定义:寻找s中包含t所有字符的最小子串
    string minWindow(string s, string t) {
        // 两个哈希表:
        // need:存储t中每个字符需要出现的次数
        // window:存储当前窗口中每个字符出现的次数
        unordered_map<char, int> need, window;
        
        // 初始化need:统计t中所有字符的需求数量
        for (char c : t) {
            need[c]++;  // 例如t="ABC",则need['A']=1, need['B']=1, need['C']=1
        }
        
        // 滑动窗口的左右指针(左闭右开区间 [left, right))
        int left = 0, right = 0;
        // valid:记录窗口中满足"需求数量"的字符种类数
        // 例如t需要A:1、B:1,窗口中A=1且B=1时,valid=2
        int valid = 0;
        // start:最小覆盖子串的起始索引
        // len:最小覆盖子串的长度(初始设为极大值)
        int start = 0, len = INT_MAX;
        
        // 右指针遍历s,扩张窗口
        while (right < s.size()) {
            // c:即将加入窗口的字符(右指针指向的字符)
            char c = s[right];
            // 右指针右移,扩大窗口范围
            right++;
            
            // 如果当前字符是t中需要的字符(存在于need中)
            if (need.count(c)) {
                // 将该字符加入window,计数+1
                window[c]++;
                // 若window中该字符的数量恰好等于need中的需求数量
                // 说明该字符的需求已满足,valid+1
                if (window[c] == need[c]) {
                    valid++;
                }
            }
            
            // 关键:当窗口中所有字符的需求都满足(valid等于need的大小)
            // 开始尝试收缩左边界,寻找更小的有效窗口
            while (valid == need.size()) {
                // 若当前窗口长度小于已记录的最小长度,更新结果
                if (right - left < len) {
                    start = left;  // 更新起始索引
                    len = right - left;  // 更新长度(右开区间,无需+1)
                }
                
                // d:即将移除窗口的字符(左指针指向的字符)
                char d = s[left];
                // 左指针右移,缩小窗口范围
                left++;
                
                // 如果移除的字符是t中需要的字符
                if (need.count(d)) {
                    // 若window中该字符的数量恰好等于need中的需求数量
                    // 移除后将不满足需求,valid-1
                    if (window[d] == need[d]) {
                        valid--;
                    }
                    // 将该字符从window中移除,计数-1
                    window[d]--;
                }
            }
        }
        
        // 若len仍为INT_MAX,说明没有找到有效子串,返回空
        // 否则返回从start开始,长度为len的子串
        return len == INT_MAX ? "" : s.substr(start, len);
    }
};
### 力扣100列表 力扣LeetCode)上的目通常是指那些被广泛讨论、高频面试或者具有较高难度的目。这些目涵盖了数据结构和算法的核心知识点,适合用来提升编程能力和解决实际问的能力。 以下是基于社区反馈整理的部分 **LeetCode Hot 100 Problems List**: #### 数组与字符串 1. Two Sum (两数之和)[^1] 2. Longest Substring Without Repeating Characters (无重复字符的最长子串)[^2] 3. Median of Two Sorted Arrays (两个有序数组的中位数)[^1] 4. Container With Most Water (盛最多水的容器)[^2] #### 链表 5. Reverse Linked List (反转链表) 6. Merge Two Sorted Lists (合并两个有序链表) 7. Remove Nth Node From End of List (删除倒数第N个节点) 8. Linked List Cycle II (环形链表II) #### 堆栈与队列 9. Valid Parentheses (有效的括号) 10. Min Stack (最小栈) 11. Sliding Window Maximum (滑动窗口最大值)[^2] #### 树与二叉树 12. Binary Tree Inorder Traversal (二叉树的中序遍历) 13. Validate Binary Search Tree (验证二叉搜索树) 14. Same Tree (相同的树) 15. Serialize and Deserialize Binary Tree (序列化与反序列化二叉树) #### 图论 16. Number of Islands (岛屿数量) 17. Course Schedule (课程表) 18. Clone Graph (克隆图) #### 排序与搜索 19. Find First and Last Position of Element in Sorted Array (在排序数组中查找元素的第一个和最后一个位置) 20. Search a 2D Matrix (二维矩阵搜索) 21. K Closest Points to Origin (最接近原点的K个点) #### 动态规划 22. Climbing Stairs (爬楼梯) 23. House Robber (打家劫舍)[^1] 24. Coin Change (零钱兑换) 25. Unique Paths (不同路径) #### 贪心算法 26. Jump Game (跳跃游戏)[^1] 27. Non-overlapping Intervals (无重叠区间) 28. Best Time to Buy and Sell Stock (买卖股票的最佳时机)[^1] #### 字符串匹配与处理 29. Implement strStr() (实现strStr()) 30. Longest Consecutive Sequence (最长连续序列) 31. Group Anagrams (分组异位词) --- ### 示例代码片段 以下是一个关于动态规划的经典例子——`Climbing Stairs` 的 Python 实现: ```python class Solution: def climbStairs(self, n: int) -> int: if n == 1 or n == 2: return n dp = [0] * (n + 1) dp[1], dp[2] = 1, 2 for i in range(3, n + 1): dp[i] = dp[i - 1] + dp[i - 2] return dp[n] ``` 上述代码通过动态规划的方式解决了 `Climbing Stairs` 问,时间复杂度为 \(O(n)\),空间复杂度同样为 \(O(n)\)[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值