diff --git "a/exercise/LC1944_\351\203\221\346\235\203\345\263\260 .cpp" "b/exercise/LC1944_\351\203\221\346\235\203\345\263\260 .cpp" new file mode 100644 index 0000000000000000000000000000000000000000..c505e386db379986991ea419a767293b2192e997 --- /dev/null +++ "b/exercise/LC1944_\351\203\221\346\235\203\345\263\260 .cpp" @@ -0,0 +1,90 @@ +#include +using namespace std; + +// 【题目】力扣1944. 队列中可以看到的人数 +// 【难度】困难 +// 【提交】2024.9.15 https://leetcode.cn/problems/number-of-visible-people-in-a-queue/description/ +// 【标签】栈、数组、单调栈、第57场双周赛 +class Solution { +public: + vector canSeePersonsCount(vector& heights) { + int n = heights.size(); + vector stack; + vector res(n, 0); + for(int i = n - 1; i >= 0; i--){ + int h = heights[i]; + while(!stack.empty() && stack.back() < h){ + stack.pop_back(); + res[i]++; + } + if(!stack.empty()){ + res[i]++; + } + stack.push_back(h); + } + return res; + } +}; + +### 学习笔记:队列中可以看到的人数(LeetCode 1944) + + +#### 一、题目理解 +有 n 个人排成队列,第 i 个人能看到右边第 j 个人的条件是:i < j,且 i 和 j 之间的所有人都比 i、j 矮(即 min(heights[i], heights[j]) > max(heights[i+1], ..., heights[j-1]))。需返回数组 answer,其中 answer[i] 是第 i 个人能看到的右边人数。 + + +#### 二、题解思路(单调栈) +要判断“中间所有人都更矮”,可转化为寻找右边“第一个比当前高的元素”,以及中间被当前元素“挡住”的元素。利用单调栈维护“从右到左的单调递减序列”,逆序遍历处理每个元素: +- 栈中存储的是当前元素右侧的高度,且栈底到栈顶单调递减(保证栈顶是右侧“最近的更高元素”)。 +- 对于当前元素 `h`,弹出栈中所有比 `h` 小的元素(这些元素会被 `h` 挡住,因此当前元素能“看到”它们,计数加1)。 +- 弹出后若栈仍非空,说明栈顶有一个比 `h` 大的元素(当前元素能“看到”这个元素,计数再加1)。 + + +#### 三、代码注释 +```cpp +class Solution { +public: + vector canSeePersonsCount(vector& heights) { + int n = heights.size(); + vector stack; // 单调栈:维护“从右到左的单调递减序列” + vector res(n, 0); // 结果数组,初始化为0 + for(int i = n - 1; i >= 0; i--){ // 从右往左遍历(逆序处理更易维护单调栈) + int h = heights[i]; + // 弹出栈中所有比h小的元素:这些元素会被h挡住,因此h能看到它们,计数+1 + while(!stack.empty() && stack.back() < h){ + stack.pop_back(); + res[i]++; + } + // 弹出后若栈非空,说明栈顶有一个比h大的元素,h能看到它,计数+1 + if(!stack.empty()){ + res[i]++; + } + // 将当前h压入栈,保持“单调递减”的性质 + stack.push_back(h); + } + return res; + } +}; +``` + + +#### 四、代码分析(优缺点与改进建议) +##### 优点: +1. 时间复杂度优秀:O(n)。每个元素最多入栈、出栈各一次,整体操作次数是线性的,避免了暴力法(O(n^2))的高复杂度。 +2. 空间复杂度合理:O(n)(栈的空间最坏为 n,如完全递减的序列)。 +3. 逻辑简洁高效:利用单调栈精准捕捉“可见性”的核心(高度的单调性与遮挡关系),逆序遍历的思路巧妙。 + + +##### 缺点: +1. 注释可读性可优化:现有注释偏基础,可更详细说明“单调栈的性质”和“每一步操作的意义”,降低新手理解成本。 +2. 直观性不足:仅看代码难直观理解“逆序+单调栈”如何对应题目中的“可见性”,需结合具体示例分步分析。 + + +##### 改进建议: +1. 增强注释细节:在代码中补充“单调栈维护递减序列”“逆序遍历是为了从右到左构建可见关系”等关键逻辑的注释。 +2. 结合示例分步解释:以示例1(heights = [10,6,8,5,11,9])为例,手动模拟每一步栈的变化和res[i]的计算过程,帮助理解。 +3. 调试辅助(可选):若用于学习,可在循环内打印stack和res[i]的值,直观观察栈的动态变化。 + + +#### 五、总结 +本题核心是利用单调栈处理“可见性”与“遮挡关系”,逆序遍历的思路让单调栈能高效维护右侧的高度序列。代码的时间复杂度为 O(n),是最优解法。理解这类“单调性 + 逆序遍历”的技巧,可解决更多类似的“序列可见性”“ Next Greater Element”问题。 diff --git "a/exercise/LC1_\351\203\221\346\235\203\345\263\260.cpp" "b/exercise/LC1_\351\203\221\346\235\203\345\263\260.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..7dadad664ac7aaffea4d5cc9e770b78783326b98 --- /dev/null +++ "b/exercise/LC1_\351\203\221\346\235\203\345\263\260.cpp" @@ -0,0 +1,105 @@ +#include +using namespace std; + +// 【题目】力扣1.两数之和 +// 【难度】简单 +// 【提交】2025.9.15 https://leetcode.cn/problems/two-sum/submissions/656691329 +// 【标签】数组、哈希表 +class Solution { +public: + vector twoSum(vector& nums, int target) { + int n = nums.size(); + vector res; + for(int i = 0;i < n-1;i++){ + for(int j = i+1;j < n;j++){ + if(nums[i]+nums[j] == target){ + res.push_back(i); + res.push_back(j); + goto flag; + } + } + } + flag: + return res; + } +}; + +### 学习笔记:两数之和问题题解与代码分析 + +#### 一、题目理解 +给定整数数组 nums 和目标值 target,找出数组中和为 `target` 的两个整数的下标,假设每种输入只有一个有效答案,且不能重复使用同一元素。 + + +#### 二、题解思路(暴力枚举法) +通过两层嵌套循环枚举所有可能的数对 (nums[i], nums[j])(其中 i < j),检查两数之和是否等于 target。若找到符合条件的数对,立即记录下标并终止循环。 + + +#### 三、代码注释 +```cpp +class Solution { +public: + vector twoSum(vector& nums, int target) { + int n = nums.size(); + vector res; // 存储结果下标 + for(int i = 0;i < n-1;i++){ // 第一层循环:枚举第一个数的下标i + for(int j = i+1;j < n;j++){ // 第二层循环:枚举第二个数的下标j(j > i,避免重复) + if(nums[i]+nums[j] == target){ // 检查两数之和是否等于target + res.push_back(i); // 记录第一个下标 + res.push_back(j); // 记录第二个下标 + goto flag; // 找到答案后,跳转到flag标签处(终止所有循环) + } + } + } + flag: // goto跳转的标签,用于直接终止嵌套循环 + return res; + } +}; +``` + + +#### 四、代码分析(优缺点) +##### 优点: +- 思路直观简单:暴力枚举所有数对,逻辑容易理解,适合算法入门学习。 +- 空间复杂度低:仅用常数额外空间(vector res 存储结果,不算额外复杂度),空间复杂度为 O(1)。 + + +##### 缺点: +- 时间复杂度高:两层嵌套循环,时间复杂度为 O(n2)。当数组规模较大(如 n = 10的4次方)时,10的4次方的运算会导致程序执行缓慢,无法通过更严格的时间限制。 +- 代码风格不优雅:使用 goto 语句跳出循环,虽然能快速终止,但破坏了代码的结构化逻辑,降低了可读性与可维护性(在工程代码中,goto 通常被视为“不良实践”)。 + + +#### 五、改进建议(哈希表优化法) +利用哈希表(unordered_map)将时间复杂度优化至 O(n),核心思路是: +- 遍历数组时,对于当前元素 nums[i],计算“需要的补数” complement = target - nums[i]。 +- 检查哈希表中是否存在 complement`: + - 若存在,直接返回当前下标 i 和 complement 对应的下标。 + - 若不存在,将当前元素 nums[i] 和其下标 i 存入哈希表。 + +优化后代码示例: +```cpp +class Solution { +public: + vector twoSum(vector& nums, int target) { + unordered_map hashMap; // 键:数组元素值,值:对应下标 + for (int i = 0; i < nums.size(); ++i) { + int complement = target - nums[i]; + // 检查补数是否在哈希表中 + if (hashMap.count(complement)) { + return {hashMap[complement], i}; // 存在则返回两个下标 + } + hashMap[nums[i]] = i; // 不存在则存入当前元素和下标 + } + return {}; // 题目保证有解,实际不会执行到这里 + } +}; +``` + + +#### 六、优化分析 +- 时间复杂度:仅需一次遍历(O(n)),哈希表的插入与查询操作平均时间复杂度为 O(1)。 +- 空间复杂度:最坏情况下存储 n 个元素,为 O(n),但时间效率的提升远大于空间的额外消耗。 +- 代码风格:结构化清晰,无 goto 等破坏结构的语句,可读性与可维护性更好。 + + +### 总结 +你的代码是两数之和的暴力枚举解法,思路直接但时间效率较低;通过哈希表优化,可将时间复杂度从 O(n2) 降至 O(n),同时提升代码的优雅性,更适合大规模数据场景。 diff --git "a/exercise/LC42_\351\203\221\346\235\203\345\263\260.cpp" "b/exercise/LC42_\351\203\221\346\235\203\345\263\260.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..5b662ec9d984a1a26a44fccb17f646b2a3ba408f --- /dev/null +++ "b/exercise/LC42_\351\203\221\346\235\203\345\263\260.cpp" @@ -0,0 +1,148 @@ +#include +using namespace std; + +// 【题目】力扣42.接雨水 +// 【难度】困难 +// 【提交】2025.9.15 https://leetcode.cn/problems/trapping-rain-water/description/ +// 【标签】栈、数组、双指针、动态规划、单调栈 +class Solution { +public: + int max(int x,int y){ + return x > y ? x : y; + } + int min(int x,int y){ + return x > y ? y : x; + } + int trap(vector& height) { + int n = height.size(); + if(n == 0){ + return 0; + } + vector leftMax(n); + leftMax[0] = height[0]; + for(int i = 1;i < n;i++){ + leftMax[i] = max(leftMax[i-1],height[i]); + } + vector rightMax(n); + rightMax[n-1] = height[n-1]; + for(int i = n-2;i >= 0;i--){ + rightMax[i] = max(rightMax[i+1],height[i]); + } + int ans = 0; + for(int i = 0;i < n;i++){ + ans += min(leftMax[i],rightMax[i]) - height[i]; + } + return ans; + } +}; + +### 学习笔记:接雨水问题题解与代码分析 + + +#### 一、题目理解 +给定每个宽度为1的柱子高度数组,计算下雨后能接住的雨水总量。核心逻辑是:**每个位置能接的雨水量 = 该位置左右两侧最高柱子的较小值 - 当前柱子高度(若结果为正)**。 + + +#### 二、题解思路(动态规划预处理) +要计算每个位置的接水量,需先知道每个位置**左侧的最大高度**(`leftMax`)和**右侧的最大高度**(`rightMax`): +1. 预处理`leftMax`数组:`leftMax[i]`表示位置`i`左侧(包括`i`)的最高柱子高度。从左到右遍历,`leftMax[0] = height[0]`,之后`leftMax[i] = max(leftMax[i-1], height[i])`。 +2. 预处理`rightMax`数组:`rightMax[i]`表示位置`i`右侧(包括`i`)的最高柱子高度。从右到左遍历,`rightMax[n-1] = height[n-1]`,之后`rightMax[i] = max(rightMax[i+1], height[i])`。 +3. 遍历每个位置`i`,累加每个位置的接水量:`min(leftMax[i], rightMax[i]) - height[i]`(因`leftMax`和`rightMax`包含自身,结果非负)。 + + +#### 三、代码注释 +```cpp +class Solution { +public: + // 自定义max函数,返回两数较大值 + int max(int x,int y){ + return x > y ? x : y; + } + // 自定义min函数,返回两数较小值 + int min(int x,int y){ + return x > y ? y : x; + } + int trap(vector& height) { + int n = height.size(); + // 处理特殊情况:无柱子时接水量为0 + if(n == 0){ + return 0; + } + // 左侧最大高度数组:leftMax[i]表示i左侧(含i)的最高高度 + vector leftMax(n); + leftMax[0] = height[0]; // 第一个位置的左侧最高为自身 + for(int i = 1;i < n;i++){ + leftMax[i] = max(leftMax[i-1], height[i]); + } + // 右侧最大高度数组:rightMax[i]表示i右侧(含i)的最高高度 + vector rightMax(n); + rightMax[n-1] = height[n-1]; // 最后一个位置的右侧最高为自身 + for(int i = n-2;i >= 0;i--){ + rightMax[i] = max(rightMax[i+1], height[i]); + } + int ans = 0; + // 遍历每个位置,计算并累加接水量 + for(int i = 0;i < n;i++){ + ans += min(leftMax[i], rightMax[i]) - height[i]; + } + return ans; + } +}; +``` + + +#### 四、代码分析(优缺点) +##### 优点: +1. 思路清晰,易理解:通过动态规划预处理左右最大高度数组,将“每个位置接水量”的计算转化为简单的数组操作,逻辑直观。 +2. 时间复杂度优秀:三次线性遍历(左最大、右最大、计算结果),整体时间复杂度为 O(n),能高效处理题目给定的 n ≤ 2 ×10的4次方的规模。 + + +##### 缺点: +1. 空间复杂度较高:需要额外维护两个长度为 n 的数组(leftMax 和 rightMax),空间复杂度为 O(n),在内存敏感场景下不够优化。 +2. 自定义函数冗余:C++ 标准库( 头文件)已提供 std::max 和 std::min 函数,自定义这两个函数属于“重复造轮子”,且可能与标准库函数产生冲突(若引入 )。 + + +#### 五、改进建议 +1. 空间优化:双指针法 + 用双指针代替预处理数组,将空间复杂度优化到 `O(1)`。核心思路:用 left 和 right 指针从两端向中间遍历,同时维护 leftMax 和 rightMax 两个变量(而非数组)。当 height[left] < height[right] 时,处理左指针位置的接水量;否则处理右指针位置,过程中动态更新 leftMax 和 rightMax。 + +2. 使用标准库函数 + 删除自定义的 max/min 函数,引入 头文件,直接使用 std::max 和 std::min,使代码更简洁、符合工程规范。 + + +#### 六、双指针优化版本示例 +```cpp +#include // 引入标准库的max和min + +class Solution { +public: + int trap(vector& height) { + int n = height.size(); + if (n == 0) return 0; + + int left = 0, right = n - 1; + int leftMax = height[left], rightMax = height[right]; + int ans = 0; + + while (left < right) { + if (height[left] < height[right]) { + // 左指针位置的接水量由leftMax决定 + ans += leftMax - height[left]; + left++; + leftMax = std::max(leftMax, height[left]); + } else { + // 右指针位置的接水量由rightMax决定 + ans += rightMax - height[right]; + right--; + rightMax = std::max(rightMax, height[right]); + } + } + return ans; + } +}; +``` +该版本时间复杂度仍为 O(n),但空间复杂度优化为 O(1),更节省内存。 + + +### 总结 +你的代码是接雨水问题的经典动态规划预处理解法,思路正确且时间效率优秀;但在空间利用和代码规范性上有优化空间,可通过双指针和标准库函数进一步改进。