From 70d203326f8678ae04ffd77905b16448c8b92c18 Mon Sep 17 00:00:00 2001 From: Shadow <15656193+shadowatomic@user.noreply.gitee.com> Date: Thu, 18 Sep 2025 19:26:08 +0800 Subject: [PATCH 1/4] =?UTF-8?q?20250918=E7=AC=AC=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._\351\203\221\346\235\203\345\263\260.cpp" | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 "topic01/submit/LC739_\351\203\221\346\235\203\345\263\260.cpp" diff --git "a/topic01/submit/LC739_\351\203\221\346\235\203\345\263\260.cpp" "b/topic01/submit/LC739_\351\203\221\346\235\203\345\263\260.cpp" new file mode 100644 index 0000000..61ed591 --- /dev/null +++ "b/topic01/submit/LC739_\351\203\221\346\235\203\345\263\260.cpp" @@ -0,0 +1,78 @@ +#include +using namespace std; + +// 【题目】力扣739. +// 【难度】中等 +// 【提交】2025.9.18 https://leetcode.cn/problems/daily-temperatures/submissions/663979629 +// 【标签】栈、数组、单调栈 +class Solution { +public: + vector dailyTemperatures(vector& temperatures) { + int n = temperatures.size(); + vector stack(n,0); + vector res(n,0); + for(int i = 0;i < n;i++){ + while(!stack.empty() && temperatures[stack.back()] < temperatures[i]){ + res[stack.back()] = i - stack.back(); + stack.pop_back(); + } + stack.push_back(i); + } + return res; + } +}; + +### 学习笔记:LeetCode 739. 每日温度 + +#### 题目理解 +给定每日温度的数组,要求返回每个位置“下一个更高温度出现的天数”,若后续无更高温度则为 0。本质是 Next Greater Element 问题(寻找每个元素之后第一个比它大的元素的位置差)。 + + +#### 题解思路:单调栈 +利用 单调栈维护一个单调递减的索引序列(栈中存储温度数组的索引,且索引对应的温度值递减)。遍历温度数组时: +- 若“当前温度 > 栈顶索引对应的温度”,说明找到了栈顶元素的“下一个更高温度”,此时计算天数差并更新结果,然后弹出栈顶; +- 重复上述过程,直到“栈为空”或“当前温度 ≤ 栈顶索引的温度”; +- 最后将当前索引入栈,维持栈的“单调递减”特性。 + +这种方法保证每个元素仅入栈、出栈各一次,时间复杂度为 O(n)。 + + +#### 代码注释与分析 +```cpp +class Solution { +public: + vector dailyTemperatures(vector& temperatures) { + int n = temperatures.size(); // 获取温度数组长度 + vector stack; // 单调栈(存储索引,原代码初始化为n个0,可优化为动态栈) + vector res(n, 0); // 结果数组,初始所有位置为0 + for(int i = 0; i < n; i++){ + // 当栈非空,且“当前温度 > 栈顶索引的温度”时,处理栈顶元素 + while(!stack.empty() && temperatures[stack.back()] < temperatures[i]){ + int topIdx = stack.back(); // 取出栈顶索引 + res[topIdx] = i - topIdx; // 计算“下一个更高温度”的天数差 + stack.pop_back(); // 弹出栈顶,继续检查新栈顶 + } + stack.push_back(i); // 当前索引入栈,维持“单调递减”栈 + } + return res; + } +}; +``` + + +#### 代码优缺点与改进建议 +##### 优点: +- 采用单调栈解决“Next Greater Element”问题,时间复杂度 O(n)(每个元素入栈、出栈各一次),空间复杂度 O(n),比“暴力双层循环(O(n^2))”更高效。 +- 逻辑清晰:利用栈的“单调性”快速定位“下一个更高温度”的位置,符合这类问题的经典解法思路。 + +##### 缺点: +- 原代码中栈初始化为 vector stack(n, 0),预先分配了 n 个元素的空间,但栈是动态入栈/出栈的,初始的 n 个 0 无实际意义,且占用不必要的内存。 + +##### 改进建议: +将栈的定义改为空 vector,即 vector stack;,让栈根据实际入栈元素动态调整大小,更节省内存且符合“栈的动态存储”特性。 + + +#### 总结 +单调栈是解决“Next Greater Element”类问题的核心工具,核心思想是通过维护栈的单调性,将双层循环的时间复杂度优化至线性。本题需注意: +- 栈存储的是“索引”而非“温度值”,便于计算“天数差”; +- 栈的单调性为“单调递减”,以快速找到“下一个更大元素”。 -- Gitee From 695bc9735879211fc192a4a2146aade2352ad99e Mon Sep 17 00:00:00 2001 From: Shadow <15656193+shadowatomic@user.noreply.gitee.com> Date: Thu, 18 Sep 2025 20:08:07 +0800 Subject: [PATCH 2/4] =?UTF-8?q?20250918=E7=AC=AC=E4=BA=8C=E6=AC=A1?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._\351\203\221\346\235\203\345\263\260.cpp" | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 "topic01/submit/LC1996_\351\203\221\346\235\203\345\263\260.cpp" diff --git "a/topic01/submit/LC1996_\351\203\221\346\235\203\345\263\260.cpp" "b/topic01/submit/LC1996_\351\203\221\346\235\203\345\263\260.cpp" new file mode 100644 index 0000000..e5d6dc7 --- /dev/null +++ "b/topic01/submit/LC1996_\351\203\221\346\235\203\345\263\260.cpp" @@ -0,0 +1,80 @@ +#include +using namespace std; + +// 【题目】力扣1996. +// 【难度】中等 +// 【提交】2025.9.18 https://leetcode.cn/problems/the-number-of-weak-characters-in-the-game/submissions/663993371 +// 【标签】栈、贪心、数组、排序、单调栈、第257场周赛 +class Solution { +public: + int numberOfWeakCharacters(vector>& properties) { + sort(properties.begin(),properties.end(),[](const vector& a, const vector& b){ + return a[0] == b[0] ? a[1] < b[1] : a[0] > b[0]; + }); + int ans = 0; + int max_defense = 0; + for(auto p : properties){ + if(p[1] < max_defense) ans++; + else max_defense = p[1]; + } + return ans; + } +}; + +### 学习笔记:LeetCode 1996. 游戏中弱角色的数量 + +#### 题目理解 +每个角色有“攻击”和“防御”两个属性。若存在另一个角色的攻击和防御都严格大于当前角色,则当前角色为“弱角色”。需统计弱角色的总数。 + + +#### 题解思路:排序 + 贪心遍历 +核心是通过排序简化“双重严格大于”的判断,步骤如下: +1. 排序规则设计: + - 攻击(attack)降序排列:保证遍历到当前角色时,之前的角色攻击 ≥ 当前角色。 + - 攻击相同时,防御(`defense`)升序排列:避免“攻击相同但防御更高”的角色被错误统计(攻击相同时,防御小的先处理,不干扰后续防御大的角色的判断)。 +2. 贪心遍历统计:遍历排序后的数组,维护“已遍历角色的最大防御值”max_defense。若当前角色的防御 < max_defense,说明存在“攻击≥当前(因攻击降序)且防御更高”的角色,当前角色是弱角色。 + + +#### 代码注释与分析 +```cpp +class Solution { +public: + int numberOfWeakCharacters(vector>& properties) { + // 自定义排序:攻击降序,攻击相同时防御升序 + sort(properties.begin(), properties.end(), [](const vector& a, const vector& b) { + return a[0] == b[0] ? a[1] < b[1] : a[0] > b[0]; + }); + int ans = 0; // 记录弱角色的数量 + int max_defense = 0; // 遍历过程中,已处理角色的最大防御值 + for (auto p : properties) { + // 若当前防御 < 历史最大防御 → 存在攻击≥当前且防御更高的角色,当前是弱角色 + if (p[1] < max_defense) { + ans++; + } else { + // 否则,更新最大防御为当前角色的防御(后续以此为判断基准) + max_defense = p[1]; + } + } + return ans; + } +}; +``` + + +#### 代码优缺点与改进建议 +##### 优点: +- 时间复杂度优秀:排序为 O(n\log n),遍历为 O(n),整体复杂度 O(n\log n),远优于暴力双重循环(O(n^2))。 +- 逻辑简洁高效:通过排序将“二维属性的双重严格大于”转化为“一维防御的大小比较”,贪心思路清晰。 +- 空间复杂度低:仅用常数额外空间(ans、max_defense),空间复杂度 O(1)(排序自身的 O(\log n) 为标准库开销)。 + +##### 缺点: +- 排序规则需深入理解:若不理解“攻击降序+防御升序”的设计目的,易对“攻击相同”的情况判断出错(如攻击相同时防御降序,会错误统计弱角色)。 +- 代码可读性依赖注释:自定义排序的lambda表达式若缺乏注释,读者难快速理解排序逻辑。 + +##### 改进建议: +- 强化排序注释:在lambda附近添加注释,说明“攻击降序保证先处理攻击强的;攻击相同时防御升序,避免攻击相同时错误统计”。 +- 优化变量名:将 max_defense 改为 maxDefenseSoFar 等更具描述性的名字,增强代码自解释性。 + + +#### 总结 +本题核心是通过排序转换问题场景,将“多属性约束的复杂判断”简化为“单属性的贪心比较”。排序规则的设计是关键:攻击降序保证“后续角色攻击不占优”,防御升序保证“攻击相同时不干扰防御的有效比较”。这种“排序+贪心”的思路在多属性约束的统计问题中非常实用。 -- Gitee From dc406c49b19bb8e1d933ab7297571f9a0b6663de Mon Sep 17 00:00:00 2001 From: Shadow <15656193+shadowatomic@user.noreply.gitee.com> Date: Thu, 18 Sep 2025 20:48:14 +0800 Subject: [PATCH 3/4] 20250918_LC84 --- ..._\351\203\221\346\235\203\345\263\260.cpp" | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 "topic01/submit/LC84_\351\203\221\346\235\203\345\263\260.cpp" diff --git "a/topic01/submit/LC84_\351\203\221\346\235\203\345\263\260.cpp" "b/topic01/submit/LC84_\351\203\221\346\235\203\345\263\260.cpp" new file mode 100644 index 0000000..609c00b --- /dev/null +++ "b/topic01/submit/LC84_\351\203\221\346\235\203\345\263\260.cpp" @@ -0,0 +1,120 @@ +#include +using namespace std; + +// 【题目】力扣84. +// 【难度】困难 +// 【提交】2025.9.18 https://leetcode.cn/problems/largest-rectangle-in-histogram/submissions/664008062 +// 【标签】栈、数组、单调栈 +class Solution { +public: + int largestRectangleArea(vector& heights) { + int n = heights.size(); + int ans = 0; + vector stack, left(n), right(n); + for(int i = 0;i < n;i++){ + while(!stack.empty() && heights[stack.back()] >= heights[i]){ + stack.pop_back(); + } + left[i] = stack.empty() ? -1 : stack.back(); + stack.push_back(i); + } + + stack = vector(); + for(int i = n-1;i >= 0;i--){ + while(!stack.empty() && heights[stack.back()] >= heights[i]){ + stack.pop_back(); + } + right[i] = stack.empty() ? n : stack.back(); + stack.push_back(i); + } + for(int i = 0;i < n;i++){ + ans = max(ans, (right[i] - left[i] - 1)* heights[i]); + } + return ans; + } +}; + +### 学习笔记:LeetCode 84. 柱状图中最大的矩形 + +#### 题目理解 +给定柱状图中各柱子的高度(每个柱子宽度为1且相邻),需找出能勾勒出的最大矩形的面积。 + + +#### 题解思路:单调栈 + 左右边界法 +核心思想是为每个柱子找到“左右两侧第一个高度更小的柱子”,从而确定以当前柱子高度为矩形高度时,宽度的最大范围。最终面积为「高度 × 宽度」,取所有情况的最大值。 + +实现上使用单调栈维护“递增的柱子索引”: +- 左边界:对于柱子 i,找到左边第一个高度严格小于它的柱子索引(若不存在则为 -1)。 +- 右边界:对于柱子 i,找到右边第一个高度严格小于它的柱子索引(若不存在则为 n,n 为柱子总数)。 + + +#### 代码注释 +```cpp +class Solution { +public: + int largestRectangleArea(vector& heights) { + int n = heights.size(); // 柱子的总数量 + int ans = 0; // 存储最终的最大矩形面积 + vector stack, left(n), right(n); // stack:单调栈;left/right:记录每个柱子的左右边界 + + // 第一步:计算每个柱子的「左边界」(左边第一个高度严格更小的柱子索引) + for (int i = 0; i < n; i++) { + // 栈非空,且栈顶柱子高度 ≥ 当前柱子高度 → 栈顶无法作为左边界,弹出 + while (!stack.empty() && heights[stack.back()] >= heights[i]) { + stack.pop_back(); + } + // 栈空 → 左边界为-1;否则左边界为栈顶索引 + left[i] = stack.empty() ? -1 : stack.back(); + stack.push_back(i); // 当前柱子索引入栈,维持「递增栈」特性 + } + + // 第二步:计算每个柱子的「右边界」(右边第一个高度严格更小的柱子索引) + stack = vector(); // 清空栈,复用空间(也可重新定义新栈) + for (int i = n - 1; i >= 0; i--) { + // 栈非空,且栈顶柱子高度 ≥ 当前柱子高度 → 栈顶无法作为右边界,弹出 + while (!stack.empty() && heights[stack.back()] >= heights[i]) { + stack.pop_back(); + } + // 栈空 → 右边界为n;否则右边界为栈顶索引 + right[i] = stack.empty() ? n : stack.back(); + stack.push_back(i); // 当前柱子索引入栈,维持「递增栈」特性 + } + + // 第三步:遍历每个柱子,计算以其为高度的矩形面积,更新最大面积 + for (int i = 0; i < n; i++) { + ans = max(ans, (right[i] - left[i] - 1) * heights[i]); + } + return ans; + } +}; +``` + + +#### 代码分析(优缺点与改进建议) +##### 优点: +1. 时间复杂度最优:每个柱子仅入栈、出栈各一次,整体时间复杂度为 O(n),远优于暴力双层循环(O(n^2))。 +2. 逻辑分层清晰:通过“计算左边界→计算右边界→遍历求最大面积”三步拆解问题,符合“分治+单调栈”的经典思路,易理解和维护。 +3. 空间利用高效:用 vector 模拟栈,且通过清空栈复用空间,空间复杂度为 O(n)(主要消耗在栈和左右边界数组上)。 + + +##### 缺点: +1. 栈的复用方式稍显冗余:第二次循环前通过 stack = vector() 清空栈,虽能工作,但可通过重新定义独立的栈让代码更简洁(可读性更好)。 +2. 变量命名可更具语义:stack 是通用名称,若命名为 monoStack(monotonic stack,单调栈),能更直观体现其“维护单调递增序列”的作用。 + + +##### 改进建议: +1. 优化变量命名:将 stack 改为 monoStack,明确“单调栈”的角色。 +2. 简化栈的复用逻辑:计算右边界时,直接定义新的 vector rightMonoStack;(与左边界的栈独立),代码更简洁: + ```cpp + // 计算左边界时 + vector leftMonoStack; + // 计算右边界时 + vector rightMonoStack; + ``` +3. 增强鲁棒性(可选):若输入数组为空(题目约束 1 <= heights.length,实际无需),可提前返回 0,但本题场景下非必需。 + + +#### 总结 +该代码采用“单调栈找左右边界”的方法,是解决“柱状图最大矩形”问题的经典高效解法。核心优势是通过单调栈将线性扫描的复杂度优化到 O(n),避免了暴力法的嵌套循环。 + +这种“用单调栈定位「左右第一个更优元素」”的思路,也可推广到其他类似问题(如「接雨水」「每日温度」等),是算法学习中的核心技巧之一。 -- Gitee From e74c7e08e7ded4fbe776b1375545898496bc3b23 Mon Sep 17 00:00:00 2001 From: Shadow <15656193+shadowatomic@user.noreply.gitee.com> Date: Thu, 18 Sep 2025 21:55:36 +0800 Subject: [PATCH 4/4] 20250918_LC85 --- ..._\351\203\221\346\235\203\345\263\260.cpp" | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 "topic01/submit/LC85_\351\203\221\346\235\203\345\263\260.cpp" diff --git "a/topic01/submit/LC85_\351\203\221\346\235\203\345\263\260.cpp" "b/topic01/submit/LC85_\351\203\221\346\235\203\345\263\260.cpp" new file mode 100644 index 0000000..1f30e6e --- /dev/null +++ "b/topic01/submit/LC85_\351\203\221\346\235\203\345\263\260.cpp" @@ -0,0 +1,180 @@ +#include +using namespace std; + +// 【题目】力扣85. +// 【难度】困难 +// 【提交】2025.9.18 https://leetcode.cn/problems/maximal-rectangle/submissions/664031627 +// 【标签】栈、数组、动态规划、矩阵、单调栈 +class Solution { +public: + int maximalRectangle(vector>& matrix) { + int m = matrix.size(); + if(m == 0) return 0; + int n = matrix[0].size(); + vector> left(m,vector(n,0)); + for(int i = 0;i < m;i++){ + for(int j = 0;j < n;j++){ + if(matrix[i][j] == '1'){ + if(j == 0) left[i][j] = 1; + else left[i][j] = left[i][j-1] + 1; + } + } + } + vector stack; + int ans = 0; + for(int j = 0;j < n;j++){ + vector up(m,0),down(m,0); + stack.clear(); + for(int i = 0;i < m;i++){ + while(!stack.empty() && left[stack.back()][j] >= left[i][j]){ + stack.pop_back(); + } + up[i] = stack.empty() ? -1 : stack.back(); + stack.push_back(i); + } + stack.clear(); + for(int i = m-1;i >= 0;i--){ + while(!stack.empty() && left[stack.back()][j] >= left[i][j]){ + stack.pop_back(); + } + down[i] = stack.empty() ? m : stack.back(); + stack.push_back(i); + } + for(int i = 0;i < m;i++){ + int height = down[i] - up[i] - 1; + ans = max(ans,height * left[i][j]); + } + } + return ans; + } +}; + +# 学习笔记:LeetCode 85. 最大矩形 + +## 一、题目解析 +给定仅包含 '0' 和 '1' 的二维矩阵,找出只包含 '1' 的最大矩形,返回其面积。 + + +## 二、题解思路 +将二维矩阵转化为多个一维“柱状图”问题(类似 LeetCode 84. 柱状图中最大矩形): +1. 预处理“高度数组”:对于每个位置 (i,j),计算以该位置为终点,向左连续 '1' 的个数(记为 left[i][j]),这相当于“柱状图”中第 i 行、第 j 列的“柱高”。 +2. 对每一列的“柱高”,用单调栈求最大矩形面积:对于每一列 j,其柱高数组为 left[0][j], left[1][j], ..., left[m-1][j]。利用单调栈找到每个柱高的左右边界(第一个比当前柱高小的位置),从而计算该柱高对应的最大矩形面积。 + + +## 三、代码注释(用户代码) +```cpp +class Solution { +public: + int maximalRectangle(vector>& matrix) { + int m = matrix.size(); + if(m == 0) return 0; // 空矩阵直接返回0 + int n = matrix[0].size(); + // left[i][j]:第i行第j列向左连续'1'的个数(柱高) + vector> left(m,vector(n,0)); + for(int i = 0;i < m;i++){ + for(int j = 0;j < n;j++){ + if(matrix[i][j] == '1'){ // 当前是'1',计算连续长度 + if(j == 0) left[i][j] = 1; // 第一列,连续长度为1 + else left[i][j] = left[i][j-1] + 1; // 否则是左边长度+1 + } + // 是'0'则left[i][j]保持0(初始化已处理) + } + } + vector stack; // 单调栈,用于找左右边界 + int ans = 0; + for(int j = 0;j < n;j++){ // 遍历每一列(处理每个“柱状图”) + vector up(m,0), down(m,0); // up[i]:i行柱高的“上边界”(上方第一个更小柱高的行索引) + // down[i]:i行柱高的“下边界”(下方第一个更小柱高的行索引) + stack.clear(); + for(int i = 0;i < m;i++){ // 从顶到底找“上边界” + // 保持栈单调递增:弹出所有≥当前柱高的元素 + while(!stack.empty() && left[stack.back()][j] >= left[i][j]){ + stack.pop_back(); + } + up[i] = stack.empty() ? -1 : stack.back(); // 栈空则上边界为-1(表示无更小元素) + stack.push_back(i); // 当前行索引入栈 + } + stack.clear(); + for(int i = m-1;i >= 0;i--){ // 从底到顶找“下边界” + while(!stack.empty() && left[stack.back()][j] >= left[i][j]){ + stack.pop_back(); + } + down[i] = stack.empty() ? m : stack.back(); // 栈空则下边界为m(表示无更小元素) + stack.push_back(i); + } + // 计算当前列每个柱高对应的矩形面积,更新最大值 + for(int i = 0;i < m;i++){ + int height = down[i] - up[i] - 1; // 有效高度的行数 + ans = max(ans, height * left[i][j]); // 面积=高度×宽度(left[i][j]是柱宽) + } + } + return ans; + } +}; +``` + + +## 四、代码分析(优缺点与改进建议) + +### 优点: +1. 思路正确:成功将二维矩阵转化为“柱状图”问题,利用单调栈求解,时间复杂度为 \( O(m \times n) \),在题目约束(\( m,n \leq 200 \))下可行。 +2. 结构清晰:分“预处理柱高”“单调栈找边界”“计算面积”三步,逻辑易理解。 + + +### 缺点: +1. 空间复杂度较高:使用二维数组 left 存储每一行的柱高,空间复杂度为 \( O(m \times n) \),可进一步优化。 +2. 单调栈遍历次数多:对每一列需进行两次单调栈遍历(正向找“上边界”、反向找“下边界”),增加了常数时间开销。 +3. 变量可读性待提升:m/n 可改为 rows/cols,stack 可命名为 monotonicStack 以更明确用途。 + + +### 改进建议: +1. 空间优化:用一维数组维护柱高。例如,每行更新时,若 matrix[i][j] == '1',则柱高 height[j] += 1;否则 `height[j] = 0`。这样可将空间复杂度从 \( O(m \times n) \) 降至 \( O(n) \)。 + +2. 单调栈优化:参考“柱状图最大矩形”的更优解法,一次单调栈遍历同时找到左右边界。栈中保存索引,弹出元素时,其“右边界”为当前元素,“左边界”为新栈顶元素,减少遍历次数。 + +3. 代码可读性:变量命名更清晰(如 rows/cols 替代 m/n,monotonicStack 替代 stack)。 + + +## 五、优化后代码示例 +```cpp +class Solution { +public: + int maximalRectangle(vector>& matrix) { + int rows = matrix.size(); + if (rows == 0) return 0; + int cols = matrix[0].size(); + vector height(cols, 0); // 一维柱高数组,逐行更新 + int maxArea = 0; + + for (int i = 0; i < rows; ++i) { + // 步骤1:更新当前行的柱高 + for (int j = 0; j < cols; ++j) { + height[j] = matrix[i][j] == '1' ? height[j] + 1 : 0; + } + // 步骤2:单调栈一次遍历找左右边界,计算最大面积 + stack monoStack; + monoStack.push(-1); // 哨兵,简化左边界判断 + for (int j = 0; j < cols; ++j) { + // 保持栈单调递增,弹出≥当前柱高的元素 + while (monoStack.top() != -1 && height[monoStack.top()] >= height[j]) { + int h = height[monoStack.top()]; + monoStack.pop(); + int width = j - monoStack.top() - 1; // 计算宽度 + maxArea = max(maxArea, h * width); + } + monoStack.push(j); // 当前索引入栈 + } + // 处理栈中剩余元素(右边界为cols) + while (monoStack.top() != -1) { + int h = height[monoStack.top()]; + monoStack.pop(); + int width = cols - monoStack.top() - 1; + maxArea = max(maxArea, h * width); + } + } + return maxArea; + } +}; +``` + +优化后代码更简洁,空间效率更高,且单调栈仅需一次遍历即可找到左右边界,时间常数项更小。 -- Gitee