[{"content":"1848. Minimum Distance to the Target Element Algorithm Steps Iterate nums and store the indices of all elements that satisfy nums[i] == target in pos Iterate pos, calculate the minimum abs(i - start) Complexity Analysis Time complexity: O(n)\nSpace complexity: O(n)\nC++ Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: int getMinDistance(vector\u0026lt;int\u0026gt;\u0026amp; nums, int target, int start) { std::vector\u0026lt;int\u0026gt; pos; // record all indices that satisfy nums[i] == target for (int i = 0; i \u0026lt; nums.size(); i ++ ) if (nums[i] == target) pos.push_back(i); int res = INT_MAX; // find minimum distance for (int x: pos) res = std::min(res, std::abs(x - start)); return res; } }; Python Code 1 2 3 4 5 6 7 8 9 10 11 12 class Solution: def getMinDistance(self, nums: List[int], target: int, start: int) -\u0026gt; int: pos = [] for i, x in enumerate(nums): if x == target: pos.append(i) res = float(\u0026#39;inf\u0026#39;) for x in pos: res = min(res, abs(x - start)) return res Go Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func getMinDistance(nums []int, target int, start int) int { pos := []int{} for i, x := range nums { if x == target { pos = append(pos, i) } } res := math.MaxInt for _, x := range pos { t := x - start if t \u0026lt; 0 { t = -t } res = min(res, t) } return res } JavaScript Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /** * @param {number[]} nums * @param {number} target * @param {number} start * @return {number} */ var getMinDistance = function (nums, target, start) { let pos = []; for (let i = 0; i \u0026lt; nums.length; i++) { if (nums[i] === target) { pos.push(i); } } let res = Infinity; for (let x of pos) { res = Math.min(res, Math.abs(x - start)); } return res; }; ","date":"2026-04-16T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-distance-to-the-target-element/","title":"Minimum Distance to the Target Element"},{"content":"43. Multiply Strings Algorithm Steps Iterate the array in reverse order\nConvert each character of num1 and num2 to integer, and store conversion results in integer array A and B Simulate multiplication\nWhen multiplying 2 numbers of length n and m, the result has at most n + m digits Multiply A and B bit by bit accumulation，and store result in array C C[i + j] = A[i] * B[j] Handle carries\nEach digit of C is treated as unit digit, and the carry-over is accumulated to the next digit Remove leading 0 and generate the result string\nSearch for the first non-zero bit in the C from higher bit to the lower bit Convert the result array to string Complexity Analysis Length of num1 and num2 is n and m Simulate multiplication O(n * m) Handel carries and generate result string O(n + m) Overall time complexity O(n * m)\nLength of Result array C is n + m，space complexity O(n + m)\nC++ Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public: string multiply(string num1, string num2) { // get the length of num1 and num2 int n = num1.size(), m = num2.size(); // iterate the string in reverse order and convert to integer array std::vector\u0026lt;int\u0026gt; A, B; for (int i = n - 1; i \u0026gt;= 0; --i) A.push_back(num1[i] - \u0026#39;0\u0026#39;); for (int i = m - 1; i \u0026gt;= 0; --i) B.push_back(num2[i] - \u0026#39;0\u0026#39;); // initialize result array C std::vector\u0026lt;int\u0026gt; C(n + m, 0); // simulate multiplication for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; m; ++j) C[i + j] += A[i] * B[j]; // handle carries for (int i = 0, t = 0; i \u0026lt; C.size(); ++i) { t += C[i]; C[i] = t % 10; // keep unit digit t /= 10; // process carry part } // remove leading zeros int k = C.size() - 1; while (k \u0026gt; 0 \u0026amp;\u0026amp; C[k] == 0) --k; // convert integer array to string std::string res; while (k \u0026gt;= 0) res += C[k--] + \u0026#39;0\u0026#39;; return res; } }; Python Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution: def multiply(self, num1: str, num2: str) -\u0026gt; str: n, m = len(num1), len(num2) A = [int(num1[i]) for i in range(n - 1, -1, -1)] B = [int(num2[i]) for i in range(m - 1, -1, -1)] C = [0] * (n + m) for i in range(n): for j in range(m): C[i + j] += A[i] * B[j] t = 0 for i in range(len(C)): t += C[i] C[i] = t % 10 t //= 10 k = len(C) - 1 while k \u0026gt; 0 and C[k] == 0: k -= 1 res = \u0026#34;\u0026#34; while k \u0026gt;= 0: res += str(C[k]) k -= 1 return res Go Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 func multiply(num1 string, num2 string) string { n, m := len(num1), len(num2) A := make([]int, n) B := make([]int, m) for i := 0; i \u0026lt; n; i ++ { A[i] = int(num1[n - 1 - i] - \u0026#39;0\u0026#39;) } for i := 0; i \u0026lt; m; i ++ { B[i] = int(num2[m - 1 - i] - \u0026#39;0\u0026#39;) } C := make([]int, n + m) for i := range A { for j := range B { C[i + j] += A[i] * B[j] } } t := 0 for i := 0; i \u0026lt; len(C); i ++ { t += C[i] C[i] = t % 10 t /= 10 } k := len(C) - 1 for k \u0026gt; 0 \u0026amp;\u0026amp; C[k] == 0 { k -= 1 } res := \u0026#34;\u0026#34; for k \u0026gt;= 0 { res += string(C[k] + \u0026#39;0\u0026#39;) k -= 1 } return res } JavaScript Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 /** * @param {string} num1 * @param {string} num2 * @return {string} */ var multiply = function (num1, num2) { let n = num1.length, m = num2.length; let A = [], B = []; for (let i = n - 1; i \u0026gt;= 0; i--) { A.push(Number(num1[i])); } for (let i = m - 1; i \u0026gt;= 0; i--) { B.push(Number(num2[i])); } let C = new Array(n + m).fill(0); for (let i = 0; i \u0026lt; n; i++) { for (let j = 0; j \u0026lt; m; j++) { C[i + j] += A[i] * B[j]; } } let t = 0; for (let i = 0; i \u0026lt; C.length; i++) { t += C[i]; C[i] = t % 10; t = Math.trunc(t / 10); } let k = C.length - 1; while (k \u0026gt; 0 \u0026amp;\u0026amp; C[k] === 0) { k--; } let res = \u0026#34;\u0026#34;; while (k \u0026gt;= 0) { res += String(C[k--]); } return res; }; ","date":"2026-04-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/multiply-strings/","title":"Multiply Strings"},{"content":"3740. Minimum Distance Between Three Equal Elements I Algorithm Steps Use map\u0026lt;int, vector\u0026lt;int\u0026gt;\u0026gt; to record all the indices of each element For each index array p Enumerate the length \u0026gt;= 3 Calculate (p[i] - p[i-2]) * 2 Take the minimum value Complexity Analysis Time complexity: O(n)\nSpace complexity: O(n)\nCpp Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public: int minimumDistance(vector\u0026lt;int\u0026gt;\u0026amp; nums) { std::unordered_map\u0026lt;int, std::vector\u0026lt;int\u0026gt;\u0026gt; f; for (int i = 0; i \u0026lt; nums.size(); ++ i) f[nums[i]].push_back(i); // record all the indices of each element int res = INT_MAX; for (auto [_, p] : f) // iterate each index array for (int i = 2; i \u0026lt; p.size(); ++ i) res = std::min(res, (p[i] - p[i - 2]) * 2); // calculate minimum distance = 2 * (k - i) return res == INT_MAX ? -1 : res; } }; Python Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution: def minimumDistance(self, nums: List[int]) -\u0026gt; int: f = {} for i, x in enumerate(nums): if x not in f: f[x] = [] f[x].append(i) res = float(\u0026#39;inf\u0026#39;) for p in f.values(): for i in range(2, len(p)): res = min(res, (p[i] - p[i - 2]) * 2) return -1 if res == float(\u0026#39;inf\u0026#39;) else res Go Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func minimumDistance(nums []int) int { f := make(map[int][]int) for i := 0; i \u0026lt; len(nums); i ++ { f[nums[i]] = append(f[nums[i]], i) } INF := 0x3f3f3f3f res := INF for _, p := range f { for i := 2; i \u0026lt; len(p); i ++ { if (p[i] - p[i - 2]) * 2 \u0026lt; res { res = (p[i] - p[i - 2]) * 2 } } } if res == INF { return -1 } return res } JavaScript Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * @param {number[]} nums * @return {number} */ var minimumDistance = function (nums) { let f = new Map(); for (let i = 0; i \u0026lt; nums.length; i++) { if (!f.has(nums[i])) f.set(nums[i], []); f.get(nums[i]).push(i); } let res = Infinity; for (let p of f.values()) { for (let i = 2; i \u0026lt; p.length; i++) { res = Math.min(res, (p[i] - p[i - 2]) * 2); } } return res == Infinity ? -1 : res; }; ","date":"2026-04-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-distance-between-three-equal-elements-i/","title":"Minimum Distance Between Three Equal Elements I"},{"content":"数的进制转换 编写一个程序，可以实现将一个数字由一个进制转换为另一个进制。\n这里有 62 个不同数位 {0−9,A−Z,a−z}。\n输入格式\n第一行输入一个整数，代表接下来的行数。\n接下来每一行都包含三个数字，首先是输入进制（十进制表示），然后是输出进制（十进制表示），最后是用输入进制表示的输入数字，数字之间用空格隔开。\n输入进制和输出进制都在 2 到 62 的范围之内。\n（在十进制下）A=10，B=11，…，Z=35，a=36，b=37，…，z=61 (0−9 仍然表示 0−9)。\n输出格式\n对于每一组进制转换，程序的输出都由三行构成。\n第一行包含两个数字，首先是输入进制（十进制表示），然后是用输入进制表示的输入数字。\n第二行包含两个数字，首先是输出进制（十进制表示），然后是用输出进制表示的输入数字。\n第三行为空白行。\n同一行内数字用空格隔开。\n输入样例：\n1 2 3 4 5 8 62 2 abcdefghiz 10 16 1234567890123456789012345678901234567890 16 35 3A0C92075C0DBF3B8ACBC5F96CE3F0AD2 35 23 333YMHOUE8JPLT7OX6K9FYCQ8A 输出样例：\n1 2 3 4 5 6 7 8 9 10 11 62 abcdefghiz 2 11011100000100010111110010010110011111001001100011010010001 10 1234567890123456789012345678901234567890 16 3A0C92075C0DBF3B8ACBC5F96CE3F0AD2 16 3A0C92075C0DBF3B8ACBC5F96CE3F0AD2 35 333YMHOUE8JPLT7OX6K9FYCQ8A 35 333YMHOUE8JPLT7OX6K9FYCQ8A 23 946B9AA02MI37E3D3MMJ4G7BL2F05 分析 使用字符串表示输入数字 将输入字符串转换为整型数组，每个元素表示一个数位，得到一个 a 进制的高精度数 遍历高精度数，每次对当前数位除以 b， 余数（当前位），商（继续参与下一轮） 收集余数，每次的余数加入结果（低位 → 高位） 反转结果数组，再映射成字符串 时空复杂度 每一轮需要遍历整个整型数组 O(n)，总轮数 k，时间复杂度为 O(n * k)\n整型数组占用 O(n)，结果数组占用 O(k)，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; std::string transform(int a, int b, std::string\u0026amp; a_line) { // 字符串 -\u0026gt; a 进制数字数组 std::vector\u0026lt;int\u0026gt; number; for (char c : a_line) { if (c \u0026gt;= \u0026#39;0\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;9\u0026#39;) number.push_back(c - \u0026#39;0\u0026#39;); else if (c \u0026gt;= \u0026#39;A\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;Z\u0026#39;) number.push_back(c - \u0026#39;A\u0026#39; + 10); else if (c \u0026gt;= \u0026#39;a\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;z\u0026#39;) number.push_back(c - \u0026#39;a\u0026#39; + 36); } std::reverse(number.begin(), number.end()); // 低位在前，便于做高精度除法 std::vector\u0026lt;int\u0026gt; res; // 循环：高精度数不断 ÷ b while (number.size()) { int t = 0; // 当前余数 for (int i = number.size() - 1; i \u0026gt;= 0; -- i) { number[i] = number[i] + t * a; // 当前位 + 上一位余数 t = number[i] % b; // 更新余数 number[i] /= b; // 更新商（保留用于下一轮） } res.push_back(t); // 收集余数（从低位到高位） // 去掉前导 0（避免无效计算） while (number.size() \u0026amp;\u0026amp; number.back() == 0) number.pop_back(); } std::reverse(res.begin(), res.end()); // 反转得到高位在前 // 转回字符 std::string b_line; for (int x : res) { if (x \u0026gt;= 0 \u0026amp;\u0026amp; x \u0026lt;= 9) b_line += char(x + \u0026#39;0\u0026#39;); else if (x \u0026gt;= 10 \u0026amp;\u0026amp; x \u0026lt;= 35) b_line += char(x - 10 + \u0026#39;A\u0026#39;); else if (x \u0026gt;= 36 \u0026amp;\u0026amp; x \u0026lt;= 61) b_line += char(x - 36 + \u0026#39;a\u0026#39;); } return b_line; } int main() { int n; std::cin \u0026gt;\u0026gt; n; while (n -- ) { int a, b; std::string a_line, b_line; std::cin \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b \u0026gt;\u0026gt; a_line; b_line = transform(a, b, a_line); std::cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; \u0026#39; \u0026#39; \u0026lt;\u0026lt; a_line \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; b \u0026lt;\u0026lt; \u0026#39; \u0026#39; \u0026lt;\u0026lt; b_line \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; std::endl; } return 0; } ","date":"2026-03-31T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/number-conversion/","title":"Number Conversion"},{"content":"3576. 数组元素相等转换 算法 每次操作可以翻转一对相邻元素的符号 为使所有元素相等，最终需要所有元素都是 1 或 -1 从左往右遍历数组，维护一个全局的乘积影响变量 mul（表示前面的操作对当前元素造成的正负反转） 遇到第 i 个元素与目标不符时（考虑乘积影响），对 i + 1 执行一次操作（乘以 -1） 统计所需的操作次数，若超过 k 或无法继续（要翻转的下一个元素不存在），则返回 false 复杂度分析 时间复杂度：O(n) 空间复杂度：O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public: // 尝试将所有元素变为 target（1 或 -1） bool checkTransform(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int k, int target) { int mul = 1; // 表示之前操作对当前位置的影响（1为不变，-1为取反） for (int i = 0; i \u0026lt; nums.size(); ++ i) { if (nums[i] * mul == target) { mul = 1; // 当前符合目标，不需要额外操作 continue; } if (k == 0 || i == nums.size() - 1) { return false; // 无法再操作，失败 } -- k; // 执行一次操作 mul = -1; // 接下来的元素将受到影响 } return true; } bool canMakeEqual(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { // 尝试变成全为 1 或全为 -1，两种都尝试 return checkTransform(nums, k, 1) || checkTransform(nums, k, -1); } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution: def checkTransform(self, nums, k, target): mul = 1 for i in range(len(nums)): if nums[i] * mul == target: mul = 1 continue if k == 0 or i == len(nums) - 1: return False k -= 1 mul = -1 return True def canMakeEqual(self, nums, k): return self.checkTransform(nums, k, 1) or self.checkTransform(nums, k, -1) Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func checkTransform(nums []int, k int, target int) bool { mul := 1 for i := 0; i \u0026lt; len(nums); i ++ { if nums[i] * mul == target { mul = 1 continue } if k == 0 || i == len(nums) - 1 { return false } k -- mul = -1 } return true } func canMakeEqual(nums []int, k int) bool { return checkTransform(nums, k, 1) || checkTransform(nums, k, -1) } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var canMakeEqual = function (nums, k) { function checkTransform(nums, k, target) { let mul = 1; for (let i = 0; i \u0026lt; nums.length; ++i) { if (nums[i] * mul == target) { mul = 1; continue; } if (k == 0 || i == nums.length - 1) { return false; } k--; mul = -1; } return true; } return checkTransform(nums, k, 1) || checkTransform(nums, k, -1); }; ","date":"2025-06-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/transform-array-to-all-equal-elements/","title":"Transform Array to All Equal Elements"},{"content":"3566. 等积子集的划分方案 算法 用两个变量 mul1 和 mul2 分别表示当前两个子集的乘积 从索引 0 开始枚举每个元素，将其放入 mul1 或 mul2 剪枝：如果任何一边的乘积已经超过 target，当前方案结束 若遍历到末尾，判断是否两个乘积都等于 target 复杂度分析 时间复杂度：O(2^n)，每个数都有两种放法，共 2^n 种可能 空间复杂度：O(n)，递归栈深度最多为 n C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public: // DFS 枚举当前第 i 个数放到哪个子集（乘积为 mul1 或 mul2） bool dfs(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int i, long long mul1, long long mul2, long long target) { if (mul1 \u0026gt; target || mul2 \u0026gt; target) { return false; // 剪枝：乘积已经超过目标 } if (i == nums.size()) { return mul1 == target \u0026amp;\u0026amp; mul2 == target; // 判断是否两个子集都达成目标乘积 } // 尝试将当前数 nums[i] 加入 mul1 或 mul2 子集中 return dfs(nums, i + 1, mul1 * nums[i], mul2, target) || dfs(nums, i + 1, mul1, mul2 * nums[i], target); } bool checkEqualPartitions(vector\u0026lt;int\u0026gt;\u0026amp; nums, long long target) { return dfs(nums, 0, 1, 1, target); // 初始两个子集乘积为 1 } }; Python 代码 1 2 3 4 5 6 7 8 9 10 class Solution: def dfs(self, nums: List[int], i: int, mul1: int, mul2: int, target: int) -\u0026gt; bool: if mul1 \u0026gt; target or mul2 \u0026gt; target: return False if i == len(nums): return mul1 == target and mul2 == target return self.dfs(nums, i + 1, mul1 * nums[i], mul2, target) or self.dfs(nums, i + 1, mul1, mul2 * nums[i], target) def checkEqualPartitions(self, nums: List[int], target: int) -\u0026gt; bool: return self.dfs(nums, 0, 1, 1, target) Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 func dfs(nums []int, i int, mul1 int, mul2 int, target int) bool { if mul1 \u0026gt; target || mul2 \u0026gt; target { return false } if i == len(nums) { return mul1 == target \u0026amp;\u0026amp; mul2 == target } return dfs(nums, i + 1, mul1 * nums[i], mul2, target) || dfs(nums, i + 1, mul1, mul2 * nums[i], target) } func checkEqualPartitions(nums []int, target int64) bool { return dfs(nums, 0, 1, 1, int(target)) } ","date":"2025-06-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/partition-array-into-two-equal-product-subsets/","title":"Partition Array Into Two Equal Product Subsets"},{"content":"80. 删除有序数组中的重复项II 分析 双指针：\n指针 i 表示当前处理后数组的有效长度 每次从前往后扫描数组元素 x，如果 x 与 nums[i - 1] 和 nums[i - 2] 不同时（即还没出现两次），就可以保留它 否则跳过该元素 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public: int removeDuplicates(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int i = 0; // 写指针，记录当前可以写入的位置 for (int x : nums) // 遍历每个元素 if (i \u0026lt; 2 || (nums[i - 1] != x || nums[i - 2] != x)) // 若前面不足两个元素，或当前元素不等于前两个元素 -\u0026gt; 可以保留 nums[i ++ ] = x; // 将当前元素写入位置 i，并自增 i return i; // 返回有效数组长度 } }; 时间复杂度 每个元素只遍历一次：O(n)\n空间复杂度 O(1)\n","date":"2025-05-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/remove-duplicates-from-sorted-array-ii/","title":"Remove Duplicates from Sorted Array II"},{"content":"92. 反转链表II 分析 创建一个虚拟头节点 dummy，它的 next 指向 head，简化边界情况处理（如 left = 1） 用指针 p 找到 left 节点的前一个节点 从 p-\u0026gt;next 开始反转区间 [left, right] 的节点，采用头插法实现 反转完成后，将前后链表重新接好 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: ListNode* reverseBetween(ListNode* head, int left, int right) { ListNode* dummy = new ListNode(-1); // 创建虚拟头节点，简化边界处理 dummy-\u0026gt;next = head; ListNode* p = dummy; // 将 p 移动到 left 前一个节点的位置 for (int i = 0; i \u0026lt; left - 1; ++ i) p = p-\u0026gt;next; // 初始化要反转的部分的起始节点 ListNode *prev = p-\u0026gt;next, *cur = prev-\u0026gt;next; // 头插法反转 [left, right] 区间 for (int i = 0; i \u0026lt; right - left; ++ i) { ListNode *next = cur-\u0026gt;next; // 暂存下一个节点 cur-\u0026gt;next = prev; // 当前节点指向前一个节点 prev = cur, cur = next; // 向前推进指针 } // 拼接反转后的子链表与剩余链表 ListNode *q = p-\u0026gt;next; // 反转前的 left 节点，反转后变成尾部 p-\u0026gt;next = prev; // p 指向反转后的头部 q-\u0026gt;next = cur; // 原 left 节点指向反转后剩余的部分 return dummy-\u0026gt;next; // 返回新链表头节点 } }; ","date":"2025-05-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reverse-linked-list-ii/","title":"Reverse Linked List II"},{"content":"112. 路径总和 分析 从根节点开始，每次将当前节点值 root-\u0026gt;val 从 targetSum 中减去 如果当前节点是叶子节点，判断此时 targetSum == root-\u0026gt;val 是否成立 否则递归判断左子树和右子树中是否有满足条件的路径 时间复杂度 时间复杂度 O(n)，最坏情况下每个节点都要访问一次\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: bool hasPathSum(TreeNode* root, int targetSum) { if (!root) return false; // 空节点，返回 false // 到达叶子节点，检查当前值是否等于剩余的目标和 if (!root-\u0026gt;left \u0026amp;\u0026amp; !root-\u0026gt;right) return targetSum == root-\u0026gt;val; // 递归左子树 if (root-\u0026gt;left \u0026amp;\u0026amp; hasPathSum(root-\u0026gt;left, targetSum - root-\u0026gt;val)) return true; // 递归右子树 if (root-\u0026gt;right \u0026amp;\u0026amp; hasPathSum(root-\u0026gt;right, targetSum - root-\u0026gt;val)) return true; // 左右子树都不满足条件 return false; } }; ","date":"2025-05-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/path-sum/","title":"Path Sum"},{"content":"113. 路径总和II 分析 当前节点 root-\u0026gt;val 加入 path 从 target 中减去当前节点值 如果当前节点是叶子节点，且 target == 0，说明当前路径满足条件，将 path 加入 res 否则递归左子树和右子树 最后进行回溯，将当前节点从 path 中弹出 时间复杂度 时间复杂度 O(n^2)，最坏情况下每个节点都可能被访问，且每条路径都可能需要复制一份保存到结果中\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; std::vector\u0026lt;int\u0026gt; path; void dfs(TreeNode* root, int target) { path.push_back(root-\u0026gt;val); // 添加当前节点到路径 target -= root-\u0026gt;val; // 更新剩余目标值 if (!root-\u0026gt;left \u0026amp;\u0026amp; !root-\u0026gt;right) // 到达叶子节点 { if (target == 0) res.push_back(path); // 满足条件，加入结果 } else { if (root-\u0026gt;left) dfs(root-\u0026gt;left, target); // 递归左子树 if (root-\u0026gt;right) dfs(root-\u0026gt;right, target); // 递归右子树 } path.pop_back(); // 回溯，移除当前节点 } vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; pathSum(TreeNode* root, int targetSum) { if (root) dfs(root, targetSum); // 从根节点开始DFS return res; } }; ","date":"2025-05-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/path-sum-ii/","title":"Path Sum II"},{"content":"796. 旋转字符串 分析 s + s 包含 s 所有可能的旋转结果，把 s + s 作为一个整体，在其中查找是否存在 goal 子串\n时间复杂度 时间复杂度 O(n)，find 操作在最坏情况下是 O(n^2)，但在实际实现中通常会做优化\n空间复杂度 空间复杂度 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 class Solution { public: bool rotateString(string s, string goal) { // 判断长度是否相等，并且 goal 是否是 s + s 的子串 return s.size() == goal.size() \u0026amp;\u0026amp; (s + s).find(goal) != std::string::npos; } }; ","date":"2025-05-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/rotate-string/","title":"Rotate String"},{"content":"1344. 时钟指针的夹角 分析 在一个圆形时钟上：\n分针每分钟转动 6°（360° ÷ 60） 时针每小时转动 30°（360° ÷ 12），并且每分钟还会向前转动 0.5°（30° ÷ 60min），即 minutes * 0.5° 。 因此可以计算：\n分针的角度为：a = minutes * 6 时针的角度为：b = hour * 30 + minutes * 0.5 两者的夹角是 |a - b|，但由于时钟是圆形，返回较小的那个角度 std::min(std::abs(a - b), 360 - std::abs(a - b))\n时间复杂度 时间复杂度 O(1)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public: double angleClock(int hour, int minutes) { // 将 hour 统一为 12 小时制 hour %= 12; // 分针的角度（每分钟 6 度） double a = minutes * 6; // 时针的角度（每小时 30 度 + 每分钟 0.5 度） double b = hour * 30 + minutes * 0.5; // 返回两个角度的较小值 return std::min(std::abs(a - b), 360 - std::abs(a - b)); } }; ","date":"2025-05-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/angle-between-hands-of-clock/","title":"Angle Between Hands of Clock"},{"content":"232. 栈实现队列 分析 使用两个栈：\nstk：用于存储新加入的元素（输入栈） cache：用于临时反转栈 stk 中的顺序，以模拟队列的出队（输出栈） 实现方式：\npush(x)：直接压入 stk pop() / peek()： 把 stk 中的所有元素依次压入 cache，此时顺序反转 cache.top() 就是队列头部元素 取出元素后，再把所有元素放回 stk，保持结构还原 empty()：只需判断 stk 是否为空 时间复杂度 push(): O(1) pop()/peek(): O(n) empty(): O(1) C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 class MyQueue { public: std::stack\u0026lt;int\u0026gt; stk, cache; // stk是输入栈，cache是输出栈 MyQueue() {} void push(int x) { stk.push(x); // 直接压入输入栈 } int pop() { // 将 stk 中所有元素转移到 cache 中 while (stk.size()) { cache.push(stk.top()); stk.pop(); } int x = cache.top(); // 弹出队头元素 cache.pop(); // 将剩余元素放回 stk while (cache.size()) { stk.push(cache.top()); cache.pop(); } return x; } int peek() { while (stk.size()) { cache.push(stk.top()); stk.pop(); } int x = cache.top(); // 查看队头元素但不删除 while (cache.size()) { stk.push(cache.top()); cache.pop(); } return x; } bool empty() { return stk.empty(); } }; // 优化，引入惰性转移（懒加载）策略，只在 cache 为空时才转移元素 class MyQueue { public: std::stack\u0026lt;int\u0026gt; stk, cache; MyQueue() {} void push(int x) { stk.push(x); } int pop() { if (cache.empty()) { while (!stk.empty()) { cache.push(stk.top()); stk.pop(); } } int x = cache.top(); cache.pop(); return x; } int peek() { if (cache.empty()) { while (!stk.empty()) { cache.push(stk.top()); stk.pop(); } } return cache.top(); } bool empty() { return stk.empty() \u0026amp;\u0026amp; cache.empty(); } }; ","date":"2025-05-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/implement-queue-using-stacks/","title":"Implement Queue Using Stacks"},{"content":"225. 用队列实现栈 分析 每次访问栈顶元素 top() 或弹出栈顶元素 pop() 时，先将队列中的前 size - 1 个元素重新入队，从而把「最后加入的元素」移动到队头，模拟栈顶\npush(): O(1) pop(): O(n) top(): O(n) empty(): O(1) 时间复杂度 排序复杂度：O(nlogn) 三重循环复杂度：外层循环 O(n)，内层双指针 O(n)，总复杂度为 O(n^2) 总时间复杂度 O(n^2)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class MyStack { public: std::queue\u0026lt;int\u0026gt; q; MyStack() {} // 压栈：直接将元素压入队列末尾 void push(int x) { q.push(x); } // 弹栈：将前 size - 1 个元素移至队尾，留下最后一个元素弹出 int pop() { int size = q.size(); while (size-- \u0026gt; 1) { q.push(q.front()); q.pop(); } int x = q.front(); q.pop(); return x; } // 查看栈顶：逻辑与 pop 类似，但最后将该元素重新压入队尾 int top() { int size = q.size(); while (size-- \u0026gt; 1) { q.push(q.front()); q.pop(); } int x = q.front(); q.pop(); q.push(x); return x; } // 判空：直接判断队列是否为空 bool empty() { return q.empty(); } }; ","date":"2025-05-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/implement-stack-using-queues/","title":"Implement Stack Using Queues"},{"content":"674. 最长连续递增序列 分析 初始化 res = 0 表示最长递增子序列长度 用变量 i 从头开始遍历数组，每次以 i 为起点 用 j 向右扩展，直到不满足递增条件 当前递增区间长度为 j - i，更新最大值 下次从 j 开始继续找下一段 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public: int findLengthOfLCIS(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = 0; for (int i = 0; i \u0026lt; nums.size(); ++ i) { int j = i + 1; // 找到从 i 开始最长的连续递增序列 while (j \u0026lt; nums.size() \u0026amp;\u0026amp; nums[j - 1] \u0026lt; nums[j]) ++ j; res = std::max(res, j - i); // 更新最大长度 i = j - 1; // 跳过已经检查过的部分 } return res; } }; ","date":"2025-04-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-continuous-increasing-subsequence/","title":"Longest Continuous Increasing Subsequence"},{"content":"1403. 非递增顺序的最小子序列 分析 元素大的更容易让子序列之和更快超过剩余元素之和，将数组降序排序 贪心策略：从大到小依次取元素，直到当前子序列的和严格超过剩余元素的和 由于优先取大的元素，自然能保证子序列长度最小、元素和最大，且结果是非递增排序的 时间复杂度 时间复杂度 O(nlogn)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: vector\u0026lt;int\u0026gt; minSubsequence(vector\u0026lt;int\u0026gt;\u0026amp; nums) { std::vector\u0026lt;int\u0026gt; res; std::sort(nums.begin(), nums.end(), std::greater()); // 1. 降序排序 int sum = 0; for (int x : nums) sum += x; // 2. 计算总和 int cur = 0; for (int x : nums) { cur += x; // 当前子序列的和 res.push_back(x); // 加入子序列 if (cur + cur \u0026gt; sum) // 当前子序列和 \u0026gt; 剩余元素和 break; } return res; } }; ","date":"2025-04-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-subsequence-in-non-increasing-order/","title":"Minimum Subsequence in Non Increasing Order"},{"content":"738. 单调递增的数字 分析 将 n 转换为字符串 s 找到 第一个下降的位置，即前一位大于后一位 为了让整体数值最大: 将下降点前一个位置的数减 1 注意：如果前面有连续相同的数字，需要往前找到第一个不同的数字再减 1 后面的所有位数填成 '9' 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: int monotoneIncreasingDigits(int n) { std::string s = std::to_string(n); int k = 0; // 先找到第一个下降的位置 while (k + 1 \u0026lt; s.size() \u0026amp;\u0026amp; s[k] \u0026lt;= s[k + 1]) ++ k; // 如果本身已经是单调递增，直接返回 n if (k == s.size() - 1) return n; // 否则，需要回退到第一个不同的位置 while (k \u0026amp;\u0026amp; s[k] == s[k - 1]) -- k; // 将第 k 位减 1 s[k] -= 1; // 将 k+1 及之后的位置全部置为 \u0026#39;9\u0026#39; for (int i = k + 1; i \u0026lt; s.size(); ++ i) s[i] = \u0026#39;9\u0026#39;; return std::stoi(s); } }; ","date":"2025-04-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/monotone-increasing-digits/","title":"Monotone Increasing Digits"},{"content":"491. 非递减子序列 分析 path 数组记录当前构建中的子序列 每当 path 长度大于等于 2，就加入答案集 每次从下标 u 开始向后枚举所有可能的数字： 若当前数字 nums[i] 可以接在 path 的末尾（即递增），就加入递归 用一个哈希集合 S 记录当前层用过的数字，避免同一层出现重复元素（防止结果中出现重复子序列） 时间复杂度 时间复杂度 O(2^n)\n空间复杂度 空间复杂度 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储所有结果 std::vector\u0026lt;int\u0026gt; path; // 当前构建中的子序列 void dfs(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int u) { // 如果子序列长度 \u0026gt;= 2，就加入结果集 if (path.size() \u0026gt;= 2) res.push_back(path); if (u == nums.size()) return; std::unordered_set\u0026lt;int\u0026gt; S; // 当前层去重 for (int i = u; i \u0026lt; nums.size(); ++ i) { // 满足递增条件且未在本层用过 if ((path.empty() || path.back() \u0026lt;= nums[i]) \u0026amp;\u0026amp; !S.count(nums[i])) { S.insert(nums[i]); // 标记本层已使用 path.push_back(nums[i]); dfs(nums, i + 1); // 递归下一个位置 path.pop_back(); // 回溯 } } } vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; findSubsequences(vector\u0026lt;int\u0026gt;\u0026amp; nums) { dfs(nums, 0); return res; } }; ","date":"2025-04-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/non-decreasing-subsequences/","title":"Non Decreasing Subsequences"},{"content":"915. 分割数组 分析 从右往左预处理出数组 r[i]，表示从位置 i 到末尾的最小值 遍历查找分界点： 从左往右遍历，用变量 l 记录当前 left 的最大值 每到一个位置，检查 l \u0026lt;= r[i + 1]，如果满足，说明当前划分是合法的，可以返回当前 left 的长度 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: int partitionDisjoint(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int n = nums.size(); std::vector\u0026lt;int\u0026gt; r(n); r[n - 1] = nums[n - 1]; // 1. 初始化最右边元素 for (int i = n - 2; i \u0026gt;= 0; --i) r[i] = std::min(nums[i], r[i + 1]); // 2. 预处理右边最小值数组 int l = INT_MIN; for (int i = 0; i + 1 \u0026lt; n; ++i) { l = std::max(l, nums[i]); // 3. 记录左边最大值 if (l \u0026lt;= r[i + 1]) // 4. 检查是否满足条件 return i + 1; } return 0; } }; ","date":"2025-04-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/partition-array-into-disjoint-intervals/","title":"Partition Array Into Disjoint Intervals"},{"content":"698. 划分为k个相等的子集 分析 目标和计算： 若 sum % k != 0，无法平均分配，直接返回 false 每个子集的目标和为 target = sum / k DFS 搜索状态： 从第 u 个元素开始，递归地尝试将当前元素加入某个子集 当前子集的和为 cur，如果正好等于 target，递归下一个子集k - 1 使用 st[i] 记录第 i 个元素是否已被使用 剪枝优化： 排序数组后，跳过相同的数字，避免重复搜索 当前 cur + nums[i] \u0026gt; target 时剪枝 时间复杂度 时间复杂度 O(k * 2^n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public: int target = 0; // 每个子集的目标和 std::vector\u0026lt;bool\u0026gt; st; // 标记数组：记录元素是否被使用 // 回溯搜索函数 bool dfs(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int u, int cur, int k) { if (k == 0) return true; // 成功划分成 k 个子集 if (cur == target) // 当前子集满了，开始下一个 return dfs(nums, 0, 0, k - 1); for (int i = u; i \u0026lt; nums.size(); ++ i) { if (!st[i]) { if (cur + nums[i] \u0026lt;= target) // 不超过目标和 { st[i] = true; if (dfs(nums, i + 1, cur + nums[i], k)) return true; st[i] = false; // 回溯 } // 跳过重复数字，避免冗余递归 while (i + 1 \u0026lt; nums.size() \u0026amp;\u0026amp; nums[i] == nums[i + 1]) ++ i; } } return false; } bool canPartitionKSubsets(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { int sum = 0; for (int x : nums) sum += x; if (sum % k) return false; // 不能整除，无法划分 target = sum / k; st = std::vector\u0026lt;bool\u0026gt;(nums.size()); sort(nums.begin(), nums.end()); // 优化搜索顺序（方便剪枝） return dfs(nums, 0, 0, k); } }; ","date":"2025-04-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/partition-to-k-equal-sum-subsets/","title":"Partition to K Equal Sum Subsets"},{"content":"468. 验证IP地址 分析 IPv4 的验证规则：\n总共有 4 段，以 . 分隔 每一段不能为空，也不能超过 4 位 每一段不能有前导零（如 01 是非法的） 每一段只能包含数字字符 每一段是 0 到 255 的十进制数字 IPv6 的验证规则：\n总共有 8 段，以 : 分隔 每一段不能为空，也不能超过 4 位 每一段是 1 ~ 4 个十六进制字符（0 - 9, a - f, A - F） 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 class Solution { public: // 字符串分割函数 std::vector\u0026lt;std::string\u0026gt; split(std::string ip, char sep) { std::vector\u0026lt;std::string\u0026gt; items; for (int i = 0; i \u0026lt; ip.size(); ++ i) { int j = i; std::string item; // 提取每段内容直到遇到分隔符 while (ip[j] != sep) item += ip[j ++ ]; items.push_back(item); i = j; } return items; } // IPv4 验证函数 std::string check_ipv4(std::string ip) { std::vector\u0026lt;std::string\u0026gt; items = split(ip + \u0026#39;.\u0026#39;, \u0026#39;.\u0026#39;); // 加一个终止符避免遗漏最后一段 if (items.size() != 4) return \u0026#34;Neither\u0026#34;; for (auto item : items) { if (item.empty() || item.size() \u0026gt; 3) return \u0026#34;Neither\u0026#34;; if (item.size() \u0026gt; 1 \u0026amp;\u0026amp; item[0] == \u0026#39;0\u0026#39;) return \u0026#34;Neither\u0026#34;; // 不允许前导0 for (auto c : item) if (c \u0026lt; \u0026#39;0\u0026#39; || c \u0026gt; \u0026#39;9\u0026#39;) return \u0026#34;Neither\u0026#34;; if (std::stoi(item) \u0026gt; 255) return \u0026#34;Neither\u0026#34;; } return \u0026#34;IPv4\u0026#34;; } // IPv6 合法字符判断 bool check_ch(char c) { if (c \u0026gt;= \u0026#39;0\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;9\u0026#39;) return true; if (c \u0026gt;= \u0026#39;a\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;f\u0026#39;) return true; if (c \u0026gt;= \u0026#39;A\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;F\u0026#39;) return true; return false; } // IPv6 验证函数 std::string check_ipv6(std::string ip) { std::vector\u0026lt;std::string\u0026gt; items = split(ip + \u0026#39;:\u0026#39;, \u0026#39;:\u0026#39;); if (items.size() != 8) return \u0026#34;Neither\u0026#34;; for (auto item : items) { if (item.empty() || item.size() \u0026gt; 4) return \u0026#34;Neither\u0026#34;; for (auto c : item) if (!check_ch(c)) return \u0026#34;Neither\u0026#34;; } return \u0026#34;IPv6\u0026#34;; } // 主函数，根据分隔符判断格式 string validIPAddress(string queryIP) { if (queryIP.find(\u0026#39;.\u0026#39;) != -1 \u0026amp;\u0026amp; queryIP.find(\u0026#39;:\u0026#39;) != -1) return \u0026#34;Neither\u0026#34;; // 同时包含 . 和 : if (queryIP.find(\u0026#39;.\u0026#39;) != -1) return check_ipv4(queryIP); if (queryIP.find(\u0026#39;:\u0026#39;) != -1) return check_ipv6(queryIP); return \u0026#34;Neither\u0026#34;; } }; ","date":"2025-04-23T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/validate-ip-address/","title":"Validate IP Address"},{"content":"670. 最大交换 分析 将数字转为字符串，方便逐位操作； 从左往右找到第一个不是降序的拐点，记为 i，即 s[i] \u0026lt; s[i + 1] 说明从这一位起，后面可能有更大的数字 从该拐点 i + 1 向右寻找最大的数字，且取最靠右的那个最大值（使前面替换的数字尽量大） 然后从左到右找到第一个比这个最大值小的数字，交换两者 由于只交换一次，直接返回交换后的值即可 如果整个数字是降序排列的，说明已经是最大值，直接返回原值 时间复杂度 时间复杂度 O(n^2)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: int maximumSwap(int num) { std::string s = std::to_string(num); // 转换为字符串方便操作 for (int i = 0; i + 1 \u0026lt; s.size(); ++ i) { if (s[i] \u0026lt; s[i + 1]) // 找到第一个非降序的位置 { int k = i + 1; // 找到从 i + 1 开始最大的数字，取最靠右的那个（保证交换尽可能早的位） for (int j = k + 1; j \u0026lt; s.size(); ++ j) if (s[k] \u0026lt;= s[j]) k = j; // 从左到右找第一个比 s[k] 小的数，交换 for (int j = 0; ; ++ j) if (s[j] \u0026lt; s[k]) { std::swap(s[j], s[k]); return std::stoi(s); // 返回交换后的整数 } } } return num; // 如果没有找到下降点，原数就是最大值 } }; ","date":"2025-04-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximum-swap/","title":"Maximum Swap"},{"content":"移除重复节点 分析 使用一个哈希集合 hash 来存储链表中已经出现的数值 用两个指针： pre：当前保留的最后一个节点； cur：正在检查的下一个节点； 如果 cur-\u0026gt;val 已经在 hash 中出现过，说明是重复节点： 将 pre-\u0026gt;next 指向 cur-\u0026gt;next，跳过当前节点 否则： 将该值加入 hash，并正常前进两个指针； 遍历完整个链表后返回 head 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: ListNode* removeDuplicateNodes(ListNode* head) { if (!head || !head-\u0026gt;next) return head; // 空链表或单节点链表直接返回 ListNode *pre = head, *cur = head-\u0026gt;next; std::unordered_set\u0026lt;int\u0026gt; hash; hash.insert(pre-\u0026gt;val); // 将头结点的值加入哈希集合 while (cur) { if (hash.count(cur-\u0026gt;val)) // 当前节点值已出现，跳过该节点 { pre-\u0026gt;next = cur-\u0026gt;next; cur = pre-\u0026gt;next; } else // 新值，保留该节点 { hash.insert(cur-\u0026gt;val); pre = cur; cur = cur-\u0026gt;next; } } return head; } }; ","date":"2025-04-18T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/remove-duplicate-node/","title":"Remove Duplicate Node"},{"content":"字符串压缩 分析 使用变量 cnt 记录当前字符的连续出现次数； 遍历字符串，统计连续相同字符的个数； 每当遇到下一个字符不同（或到字符串结尾）时，就把当前字符和它的个数拼接到结果字符串中； 最后判断压缩后的结果是否更短，是则返回压缩结果，否则返回原字符串 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public: string compressString(string s) { std::string res; // 存储压缩后的字符串 int cnt = 0; // 当前字符的计数 for (int i = 0; i \u0026lt; s.size(); ++ i) { ++cnt; // 如果当前字符是最后一个，或与下一个字符不同，就拼接 if (i + 1 == s.size() || s[i] != s[i + 1]) { res += s[i]; // 添加当前字符 res += std::to_string(cnt); // 添加出现次数 cnt = 0; // 重置计数器 } } // 如果压缩结果更短，返回压缩结果，否则返回原字符串 return res.size() \u0026lt; s.size() ? res : s; } }; ","date":"2025-04-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/compress-string/","title":"Compress String"},{"content":"判断字符是否唯一 分析 用一个整型变量 check 作为位掩码（bitmask），其每一位表示一个字符是否出现 遍历字符串中的每个字符： 如果该字符之前出现过（对应位为 1），说明不唯一，返回 false 否则将对应位设为 1 遍历完成仍未发现重复，返回 true 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: bool isUnique(string astr) { int check = 0; // 位掩码，初始为 0 for (char c : astr) { // 计算字符在字母表中的索引位置（0~25） int bit = c - \u0026#39;a\u0026#39;; // 如果对应位已为 1，说明重复 if (check \u0026amp; (1 \u0026lt;\u0026lt; bit)) return false; // 否则将该位置为 1 check |= (1 \u0026lt;\u0026lt; bit); } // 所有字符都不同 return true; } }; ","date":"2025-04-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/is-unique/","title":"Is Unique"},{"content":"URL化 分析 从末尾位置 k 开始往前填充字符 从实际字符串末尾 length - 1 开始向前遍历： 若遇到普通字符，则复制到末尾 若遇到空格 ' '，则插入 '%20' 最后返回处理后的子串 s.substr(k + 1) 即可 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: string replaceSpaces(string s, int length) { int k = s.size() - 1; // k 指向字符串的最末尾（即原始字符串+预留空间） // 从真实长度的尾部开始往前处理 for (int i = length - 1; i \u0026gt;= 0; --i) { if (s[i] == \u0026#39; \u0026#39;) // 空格替换为 \u0026#34;%20\u0026#34; { s[k--] = \u0026#39;0\u0026#39;; s[k--] = \u0026#39;2\u0026#39;; s[k--] = \u0026#39;%\u0026#39;; } else // 普通字符直接移动 { s[k--] = s[i]; } } // 截取有效部分返回 return s.substr(k + 1); } }; ","date":"2025-04-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/string-to-url/","title":"String to Url"},{"content":"最大连续子序列 给定 K 个整数的序列 {N0, N1, …, NK − 1}，其任意连续子序列可表示为 {Ni, Ni + 1, …, Nj}，其中 0 ≤ i ≤ j \u0026lt; K\n最大连续子序列是所有连续子序列中元素和最大的一个，例如给定序列 {−2, 11, −4, 13, −5, −2}，其最大连续子序列为 {11, −4, 13} ，最大和为 20\n编写程序得到其中最大子序列的和并输出该子序列的第一个和最后一个元素的下标\n输入格式\n输入包含多组测试数据\n每组数据占 2 行，第 1 行给出正整数 K\n第 2 行给出 K 个整数 N1, …, NK\n输出格式\n每组数据输出一行结果，包含最大子序列的和以及子序列的第一个下标 i 和最后一个元素的下标 j\n所有元素下标为 0 ∼ K − 1\n如果最大子序列不唯一，则选择 i 最小的那个子序列，如果仍不唯一，则选择 i 最小的子序列中 j 最小的那个子序列\n若所有 K 个元素都是负数，则定义其最大和为 0，输出 0 0 0\n数据范围\n1 ≤ K ≤ 105, −10000 ≤ Ni ≤ 10000, 输入最多包含 10 组数据\n输入样例：\n1 2 3 4 5 6 7 8 8 6 -2 11 -4 13 -5 -2 10 5 10 -10 10 -10 10 8 -1 -5 -2 3 -1 0 -2 0 4 -1 -2 -4 -3 输出样例：\n1 2 3 4 27 0 7 10 0 0 3 3 3 0 0 0 分析 last：当前以第 i 个元素结尾的子序列最大和 若 last \u0026lt; 0，则从当前元素重新开始，记录当前左端点 temp 如果当前 last 优于全局最大 res，则更新 res 和 左右端点 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include \u0026lt;iostream\u0026gt; #include \u0026lt;climits\u0026gt; const int N = 100010; int nums[N]; int main() { int n; while (std::cin \u0026gt;\u0026gt; n) { for (int i = 0; i \u0026lt; n; ++i) std::cin \u0026gt;\u0026gt; nums[i]; int last = 0; // 当前子段和 int res = INT_MIN; // 最大子段和 int l = 0, r = 0; // 结果子序列的起止下标 int temp = 0; // 记录当前子段的起点 for (int i = 0; i \u0026lt; n; ++i) { // 如果前缀和为负，丢弃，重开新子段，并记录左端点 if (last \u0026lt; 0) temp = i; last = std::max(nums[i], last + nums[i]); if (res \u0026lt; last) { res = last; l = temp; // 更新起点为当前有效起点 r = i; // 终点为当前下标 } } if (res \u0026lt; 0) std::cout \u0026lt;\u0026lt; \u0026#34;0 0 0\u0026#34; \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // 所有数都为负数 else std::cout \u0026lt;\u0026lt; res \u0026lt;\u0026lt; \u0026#39; \u0026#39; \u0026lt;\u0026lt; l \u0026lt;\u0026lt; \u0026#39; \u0026#39; \u0026lt;\u0026lt; r \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } return 0; } ","date":"2025-04-10T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/maximum-contiguous-subsequence/","title":"Maximum Contiguous Subsequence"},{"content":"Dijkstra 求最短路II 给定一个 n 个点 m 条边的有向图，图中可能存在重边和自环，所有边权均为正值\n请你求出 1 号点到 n 号点的最短距离，如果无法从 1 号点走到 n 号点，则输出 −1\n输入格式\n第一行包含整数 n 和 m 接下来 m 行每行包含三个整数 x, y, z，表示存在一条从点 x 到点 y 的有向边，边长为 z 输出格式\n输出一个整数，表示 1 号点到 n 号点的最短距离 如果路径不存在，则输出 −1 数据范围\n1 ≤ n, m ≤ 150000 图中涉及边长均不超过 10000 输入样例\n1 2 3 4 3 3 1 2 2 2 3 1 1 3 4 输出样例\n1 3 分析 n的范围变为 1 ~ 150000，属于稀疏图（点多边少），采用 邻接表 + 堆优化\n图初始化 使用邻接表建图，每次调用 add(a, b, c) 记录一条边 Dijkstra 主体逻辑 起点 1 入堆，距离设为 0 每次从堆中取出距离最小的点 ver，如果该点已确定最短路则跳过 遍历 ver 的所有出边 ver → j： 若 dist[j] \u0026gt; dist[ver] + w，说明找到了更短路径，更新并入堆 输出答案 若 dist[n] == INF，说明无法到达 n，输出 -1 否则输出 dist[n] 时间复杂度 时间复杂度 O(mlogn)\n空间复杂度 空间复杂度为 O(n + m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include \u0026lt;cstring\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;queue\u0026gt; #include \u0026lt;utility\u0026gt; typedef std::pair\u0026lt;int, int\u0026gt; PII; // \u0026lt;最短距离, 点编号\u0026gt; const int N = 1000010; const int INF = 0x3f3f3f3f; int h[N], e[N], w[N], ne[N], idx; // 邻接表 int dist[N]; // 存储最短距离 bool st[N]; // 标记是否已经确定最短距离 int n, m; // 添加一条边 a -\u0026gt; b，权值为 c void add(int a, int b, int c) { e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx++; } // 堆优化 Dijkstra 主函数 int dijkstra() { std::memset(dist, INF, sizeof(dist)); dist[1] = 0; std::priority_queue\u0026lt;PII, std::vector\u0026lt;PII\u0026gt;, std::greater\u0026lt;PII\u0026gt;\u0026gt; heap; heap.push({0, 1}); // 起点入堆 while (heap.size()) { PII t = heap.top(); heap.pop(); int ver = t.second; if (st[ver]) continue; // 如果已经处理过，跳过 st[ver] = true; // 遍历 ver 的所有出边 for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] \u0026gt; dist[ver] + w[i]) { dist[j] = dist[ver] + w[i]; heap.push({dist[j], j}); } } } return dist[n] == INF ? -1 : dist[n]; } int main() { std::memset(h, -1, sizeof(h)); // 初始化邻接表 std::cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; for (int i = 0; i \u0026lt; m; ++ i) { int x, y, z; std::cin \u0026gt;\u0026gt; x \u0026gt;\u0026gt; y \u0026gt;\u0026gt; z; add(x, y, z); // 构建图 } std::cout \u0026lt;\u0026lt; dijkstra() \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; return 0; } ","date":"2025-04-05T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/dijkstra-finding-the-shortest-path-ii/","title":"Dijkstra Finding the Shortest Path II"},{"content":"Dijkstra 求最短路I 给定一个 n 个点 m 条边的有向图，图中可能存在重边和自环，所有边权均为正值\n请你求出 1 号点到 n 号点的最短距离，如果无法从 1 号点走到 n 号点，则输出 −1\n输入格式\n第一行包含整数 n 和 m 接下来 m 行每行包含三个整数 x, y, z，表示存在一条从点 x 到点 y 的有向边，边长为 z 输出格式\n输出一个整数，表示 1 号点到 n 号点的最短距离 如果路径不存在，则输出 −1 数据范围\n1 ≤ n ≤ 500 1 ≤ m ≤ 105 图中涉及边长均不超过 10000 输入样例\n1 2 3 4 3 3 1 2 2 2 3 1 1 3 4 输出样例\n1 3 分析 初始化 dist[i] 记录从起点 1 到 i 的最短路径，初始化为 INF（无穷大） st[i] 记录该点是否已确定最短路径，初始化为 false g[i][j] 记录 i 到 j 的边权，初始化为 INF Dijkstra 算法执行 选取当前未确定最短路径且距离最近的点 t 将 t 标记为已确定 (st[t] = true) 遍历 t 的所有邻接点 j，如果 dist[t] + g[t][j] \u0026lt; dist[j]，更新 dist[j] 重复 n 轮，直到所有点处理完 返回结果 如果 dist[n] == INF，说明无法到达 n，返回 -1 否则返回 dist[n]，即 1 号点到 n 号点的最短路径长度 时间复杂度 寻找最小 dist[t] 的点：O(n) 遍历所有边更新 dist[j]：O(n^2) 总时间复杂度 O(n^2)\n空间复杂度 空间复杂度为 O(n^2)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include \u0026lt;cstring\u0026gt; #include \u0026lt;iostream\u0026gt; const int N = 510; const int INF = 0x3f3f3f3f; int g[N][N]; // 邻接矩阵存储图 int dist[N]; // 记录最短路径距离 bool st[N]; // 记录该点是否已确定最短路径 int n, m; // n个点，m条边 int dijkstra() { std::memset(dist, INF, sizeof(dist)); dist[1] = 0; // 起点1号点，最短路径初始化为0 for (int i = 0; i \u0026lt; n; ++i) { // 选取当前未确定的点中，dist最小的点 t int t = -1; for (int j = 1; j \u0026lt;= n; ++j) if (!st[j] \u0026amp;\u0026amp; (t == -1 || dist[j] \u0026lt; dist[t])) t = j; st[t] = true; // 标记 t 号点已经确定最短路径 // 用 t 号点更新其他点的最短距离 for (int j = 1; j \u0026lt;= n; ++j) dist[j] = std::min(dist[j], dist[t] + g[t][j]); } return dist[n] == INF ? -1 : dist[n]; // 如果 n 号点仍是 INF，说明无法到达 } int main() { std::memset(g, INF, sizeof(g)); // 初始化邻接矩阵，所有边初始设为无穷大 std::cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; while (m--) { int x, y, z; std::cin \u0026gt;\u0026gt; x \u0026gt;\u0026gt; y \u0026gt;\u0026gt; z; g[x][y] = std::min(g[x][y], z); // 处理重边，取最小值 } std::cout \u0026lt;\u0026lt; dijkstra() \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; return 0; } ","date":"2025-04-04T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/dijkstra-finding-the-shortest-path-i/","title":"Dijkstra Finding the Shortest Path I"},{"content":"143. 重排链表 分析 第一步：找到链表的中点 遍历链表长度，再通过 (len + 1) / 2 找到中间节点位置 第二步：反转链表后半段 从中点开始，原地反转链表后半段 第三步：合并两段链表 将前半段和后半段反转后的链表进行交叉合并 注意尾指针的处理，避免无限循环 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public: void reorderList(ListNode* head) { if (!head) return; // 1. 统计链表长度 int len = 0; for (ListNode* p = head; p; p = p-\u0026gt;next) ++len; // 2. 找到链表中点 ListNode* mid = head; for (int i = 0; i \u0026lt; (len + 1) / 2 - 1; ++i) mid = mid-\u0026gt;next; // 3. 反转链表后半部分 ListNode *a = mid, *b = mid-\u0026gt;next; for (int i = 0; i \u0026lt; len / 2; ++i) { ListNode* c = b-\u0026gt;next; b-\u0026gt;next = a; a = b; b = c; } // 4. 合并两段链表 ListNode *p = head, *q = a; for (int i = 0; i \u0026lt; len / 2; ++i) { ListNode* o = q-\u0026gt;next; q-\u0026gt;next = p-\u0026gt;next; p-\u0026gt;next = q; if (len % 2 == 0 \u0026amp;\u0026amp; i == len / 2 - 1) q-\u0026gt;next = nullptr; q = o; p = p-\u0026gt;next-\u0026gt;next; } if (len % 2 == 1) p-\u0026gt;next = nullptr; } }; ","date":"2025-04-04T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reorder-list/","title":"Reorder List"},{"content":"1108. IP地址无效化 分析 遍历字符串，逐个检查字符 如果是 '.' ，就将其替换为 \u0026quot;[.]\u0026quot; 否则，直接添加到新字符串中 时间复杂度 时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public: string defangIPaddr(string address) { std::string res; // 存储转换后的字符串 for (char c : address) // 遍历输入字符串 if (c == \u0026#39;.\u0026#39;) res += \u0026#34;[.]\u0026#34;; // 遇到 \u0026#39;.\u0026#39; 时替换为 \u0026#34;[.]\u0026#34; else res += c; // 其他字符直接加入结果字符串 return res; } }; ","date":"2025-04-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/defanging-an-ip-address/","title":"Defanging an IP Address"},{"content":"93. 复原IP地址 分析 有效 IP 地址的要求\n由 4 个整数组成（范围 0 ~ 255） 整数之间用 . 分隔 不能有前导 0（如 \u0026quot;01\u0026quot; 是非法的） 所有字符必须被使用 dfs 递归逻辑\nu：当前扫描到的字符索引 k：当前已解析出的 IP 段数 path：当前已拼接的 IP 地址（尚未去除最后一个 .） 递归终止条件： u == s.size()：如果字符串已经完全遍历： 如果 k == 4，说明成功找到一个 IP 地址，去掉最后一个 .，加入结果集 res 否则，返回（分段不足 4 段） 如果 k == 4 且 s 还未遍历完，返回 遍历 1 ~ 3 位\n循环遍历 i = u 到 i \u0026lt; s.size()，每次尝试截取 1 ~ 3 位： 若存在前导 0，停止搜索 转换成整数 t：t = t * 10 + s[i] - '0' 如果 t \u0026gt; 255，停止搜索（数字超出 IP 地址范围） 递归调用 dfs，继续尝试下一段 时间复杂度 最多有 4 段，每段最多 3 种选择，时间复杂度 O(3^4)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { public: std::vector\u0026lt;std::string\u0026gt; res; // 存放所有合法 IP 地址 void dfs(std::string\u0026amp; s, int u, int k, std::string path) { if (u == s.size()) // 如果字符串已遍历完 { if (k == 4) // 且正好分成了 4 段 { path.pop_back(); // 去掉最后一个 \u0026#39;.\u0026#39; res.push_back(path); // 加入结果集 } return; } if (k == 4) // 超过 4 段，直接返回 return; for (int i = u, t = 0; i \u0026lt; s.size(); ++i) { if (i \u0026gt; u \u0026amp;\u0026amp; s[u] == \u0026#39;0\u0026#39;) // 不能有前导 0 break; t = t * 10 + s[i] - \u0026#39;0\u0026#39;; // 将字符转换成整数 if (t \u0026gt; 255) // 超出 255，直接停止 break; dfs(s, i + 1, k + 1, path + std::to_string(t) + \u0026#39;.\u0026#39;); // 递归 } } vector\u0026lt;string\u0026gt; restoreIpAddresses(string s) { dfs(s, 0, 0, \u0026#34;\u0026#34;); return res; } }; ","date":"2025-04-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/restore-ip-address/","title":"Restore IP Address"},{"content":"图中点的层次 给定一个 n 个点 m 条边的有向图，图中可能存在重边和自环\n所有边的长度都是 1，点的编号为 1 ∼ n\n请你求出 1 号点到 n 号点的最短距离，如果从 1 号点无法走到 n 号点，输出 −1\n输入格式\n第一行包含两个整数 n 和 m 接下来 m 行，每行包含两个整数 a 和 b，表示存在一条从 a 走到 b 的长度为 1 的边 输出格式\n输出一个整数，表示 1 号点到 n 号点的最短距离 数据范围\n1 1 ≤ n, m ≤ 105 输入样例\n1 2 3 4 5 6 4 5 1 2 2 3 3 4 1 3 1 4 输出样例\n1 1 分析 邻接表存储： h[N]：存储每个节点的邻接链表的头节点（初始化为 -1） e[N]：存储邻接表中的边的目标节点 ne[N]：存储邻接表中链表的下一个节点的索引 idx：记录当前边的编号 add(a, b)：将一条从节点 a 到节点 b 的边加入邻接表中 bfs()： 初始化 d 数组为 -1，表示所有节点初始时不可达 从节点 1 开始进行 BFS，记录每个节点的最短路径 如果节点 n 被访问过，则返回 d[n]；否则返回 -1 时间复杂度 BFS 只会遍历每个节点和每条边一次，其中 n 是节点数，m 是边数\n时间复杂度为 O(n + m)\n空间复杂度 存储图的邻接表需要 O(n + m) 的空间 存储队列和距离数组需要 O(n) 的空间 空间复杂度为 O(n + m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include \u0026lt;cstring\u0026gt; #include \u0026lt;iostream\u0026gt; const int N = 100010; // 节点和边的最大数量 int h[N], e[N], ne[N], idx; // 邻接表存储 int q[N]; // 队列 int d[N]; // 存储最短路径 int n, m; // 节点数和边数 // 添加一条边到邻接表 void add(int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } // BFS 求最短路径 int bfs() { int hh = 0, tt = 0; q[0] = 1; // 起始节点 1 std::memset(d, -1, sizeof(d)); // 初始化距离数组为 -1 d[1] = 0; // 起点到起点的距离为 0 while (hh \u0026lt;= tt) // 队列不为空 { int t = q[hh++]; // 出队一个节点 for (int i = h[t]; i != -1; i = ne[i]) // 遍历该节点的所有邻接节点 { int j = e[i]; // 邻接节点 j if (d[j] == -1) // 如果节点 j 尚未访问 { d[j] = d[t] + 1; // 更新节点 j 的距离 q[++tt] = j; // 将节点 j 加入队列 } } } return d[n]; // 返回从 1 到 n 的最短路径长度 } int main() { std::cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; // 输入节点数和边数 std::memset(h, -1, sizeof(h)); // 初始化邻接表 for (int i = 0; i \u0026lt; m; ++i) { int a, b; std::cin \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b; // 输入每条边 add(a, b); // 将边加入邻接表 } std::cout \u0026lt;\u0026lt; bfs() \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // 输出从 1 到 n 的最短距离 return 0; } ","date":"2025-04-02T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/level-of-point-in-graph/","title":"Level of Point in Graph"},{"content":"有向图的拓扑排序 给定一个 n 个点 m 条边的有向图，点的编号是 1 到 n，图中可能存在重边和自环\n请输出任意一个该有向图的拓扑序列，如果拓扑序列不存在，则输出 −1\n若一个由图中所有点构成的序列 A 满足：对于图中的每条边 (x, y)，x 在 A 中都出现在 y 之前，则称 A 是该图的一个拓扑序列\n输入格式\n第一行包含两个整数 n 和 m 接下来 m 行，每行包含两个整数 x 和 y，表示存在一条从点 x 到点 y 的有向边 (x, y) 输出格式\n共一行，如果存在拓扑序列，则输出任意一个合法的拓扑序列即可 否则输出 −1 数据范围\n1 1 ≤ n, m ≤ 105 输入样例\n1 2 3 4 3 3 1 2 2 3 1 3 输出样例\n1 1 2 3 分析 邻接表的构建： h[N]：存储每个节点的邻接表头 e[N]：存储邻接节点的目标节点 ne[N]：存储邻接表中链表的下一个节点 idx：记录当前边的编号 add(a, b)：将一条从节点 a 到节点 b 的边加入邻接表 topsort()： 使用队列 q 来存储入度为零的节点，初始化时将所有入度为零的节点加入队列 在 BFS 遍历过程中，若某个节点的入度减为零，则将其加入队列 最终，如果排序成功且队列中处理的节点数为 n，则说明拓扑排序存在，否则返回 false 时间复杂度 队列中的每个节点会被入队和出队一次，最多访问 n 个节点和 m 条边\n时间复杂度 O(n + m)\n空间复杂度 存储邻接表需要 O(n + m) 的空间 存储入度数组和队列需要 O(n) 的空间 空间复杂度为 O(n + m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include \u0026lt;cstring\u0026gt; #include \u0026lt;iostream\u0026gt; const int N = 100010; int h[N], e[N], ne[N], idx; // 邻接表 int q[N], d[N]; // 队列和入度数组 int n, m; // 节点数和边数 // 添加一条边到邻接表 void add(int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } // 拓扑排序算法 bool topsort() { int hh = 0, tt = -1; // 将所有入度为 0 的节点加入队列 for (int i = 1; i \u0026lt;= n; ++i) if (d[i] == 0) q[++tt] = i; // 进行拓扑排序 while (hh \u0026lt;= tt) { int t = q[hh++]; // 出队一个节点 for (int i = h[t]; i != -1; i = ne[i]) // 遍历该节点的所有邻接节点 { int j = e[i]; // 邻接节点 j if (--d[j] == 0) // 如果该节点的入度减为 0，则加入队列 q[++tt] = j; } } // 如果队列中的节点数等于总节点数，说明拓扑排序成功 return tt == n - 1; } int main() { std::memset(h, -1, sizeof(h)); // 初始化邻接表 std::cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; // 输入节点数和边数 for (int i = 0; i \u0026lt; m; ++i) { int x, y; std::cin \u0026gt;\u0026gt; x \u0026gt;\u0026gt; y; // 输入每条边 add(x, y); // 将边加入邻接表 ++d[y]; // 更新节点 y 的入度 } if (topsort()) // 如果拓扑排序成功 { for (int i = 0; i \u0026lt; n; ++i) // 输出拓扑排序 std::cout \u0026lt;\u0026lt; q[i] \u0026lt;\u0026lt; \u0026#39; \u0026#39;; std::cout \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } else std::cout \u0026lt;\u0026lt; -1 \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // 如果无法进行拓扑排序，输出 -1 return 0; } ","date":"2025-04-02T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/top-sort/","title":"Top Sort"},{"content":"八数码 在一个 3 × 3 的网格中，1 ∼ 8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3 × 3 的网格中\n例如：\n1 2 3 1 2 3 x 4 6 7 5 8 在游戏过程中，可以把 x 与其上、下、左、右四个方向之一的数字交换（如果存在）\n我们的目的是通过交换，使得网格变为如下排列（称为正确排列）：\n1 2 3 1 2 3 4 5 6 7 8 x 例如，示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列\n交换过程如下：\n1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 x 4 6 4 x 6 4 5 6 4 5 6 7 5 8 7 5 8 7 x 8 7 8 x 现在，给你一个初始网格，请你求出得到正确排列至少需要进行多少次交换\n输入格式\n输入占一行，将 3 × 3 的初始网格描绘出来 例如，如果初始网格如下所示： 1 2 3 1 2 3 x 4 6 7 5 8 则输入为：1 2 3 x 4 6 7 5 8\n输出格式\n输出占一行，包含一个整数，表示最少交换次数 如果不存在解决方案，则输出 −1 输入样例\n1 2 3 4 1 5 x 7 6 8 输出样例\n1 19 分析 抽象为 无权图的最短路径搜索，状态间转换是 交换 x 的位置\n状态表示 使用字符串表示网格状态，目标状态：\u0026ldquo;12345678x\u0026rdquo; 状态转移 x 可与 上下左右 相邻数交换 BFS 求最短步数 队列 q 记录待搜索的状态 哈希表 d 记录已访问状态，防止重复搜索 BFS 逐层扩展 取出队首状态 tmp 遍历 x 的 4 个移动方向： 生成新状态 next_state，如果未访问： 记录步数 d[next_state] = d[tmp] + 1 加入队列 q.push(next_state) 若 tmp == end_state，返回步数 BFS 终止 如果队列为空，仍未找到解，则返回 -1（无解） 时间复杂度 时间复杂度 O(9!)\n空间复杂度 空间复杂度为 O(9!)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include \u0026lt;iostream\u0026gt; #include \u0026lt;queue\u0026gt; #include \u0026lt;unordered_map\u0026gt; int bfs(std::string\u0026amp; start_state) { std::string end_state = \u0026#34;12345678x\u0026#34;; // 目标状态 std::queue\u0026lt;std::string\u0026gt; q; // BFS 队列 std::unordered_map\u0026lt;std::string, int\u0026gt; d; // 记录步数 q.push(start_state); d[start_state] = 0; // 方向数组：右、下、左、上 int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; while (!q.empty()) { std::string tmp = q.front(); q.pop(); int distance = d[tmp]; // 取当前状态的步数 if (tmp == end_state) return distance; // 目标状态，返回最小步数 int k = tmp.find(\u0026#39;x\u0026#39;); // 找到 \u0026#39;x\u0026#39; 的索引 int x = k / 3, y = k % 3; // 转换为二维坐标 for (int i = 0; i \u0026lt; 4; ++ i) // 遍历 4 个方向 { int a = x + dx[i], b = y + dy[i]; if (a \u0026gt;= 0 \u0026amp;\u0026amp; a \u0026lt; 3 \u0026amp;\u0026amp; b \u0026gt;= 0 \u0026amp;\u0026amp; b \u0026lt; 3) // 确保新位置合法 { std::swap(tmp[k], tmp[a * 3 + b]); // 交换 \u0026#39;x\u0026#39; 与相邻数字 if (!d.count(tmp)) // 该状态未访问 { q.push(tmp); // 加入队列 d[tmp] = distance + 1; // 记录步数 } std::swap(tmp[a * 3 + b], tmp[k]); // 交换回来，恢复原状态 } } } return -1; // 无法到达目标状态 } int main() { std::string start_state; for (int i = 0; i \u0026lt; 9; ++ i) { char c; std::cin \u0026gt;\u0026gt; c; start_state += c; } std::cout \u0026lt;\u0026lt; bfs(start_state) \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; return 0; } ","date":"2025-04-01T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/digit-8/","title":"Digit 8"},{"content":"走迷宫 给定一个 n × m 的二维整数数组，用来表示一个迷宫，数组中只包含 0 或 1，其中 0 表示可以走的路，1 表示不可通过的墙壁\n最初，有一个人位于左上角 (1, 1) 处，已知该人每次可以向上、下、左、右任意一个方向移动一个位置\n请问，该人从左上角移动至右下角 (n, m) 处，至少需要移动多少次\n数据保证 (1, 1) 处和 (n, m) 处的数字为 0，且一定至少存在一条通路\n输入格式\n第一行包含两个整数 n 和 m 接下来 n 行，每行包含 m 个整数（0 或 1），表示完整的二维数组迷宫 输出格式\n输出一个整数，表示从左上角移动至右下角的最少移动次数 数据范围\n1 1 ≤ n, m ≤ 100 输入样例\n1 2 3 4 5 6 5 5 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0 输出样例\n1 8 分析 每次在 上下左右 四个方向上移动 1 步，抽象为无权图的最短路径问题\nBFS（广度优先搜索） 可以保证 最先到达终点的一定是最短路径\n变量定义 g[N][N] 记录迷宫（0 可通行，1 代表墙） d[N][N] 记录 从起点 (0, 0) 到当前点 (i, j) 需要走的步数，初始值设为 -1 代表未访问 q[N*N] 是 BFS 队列（数组模拟队列），用于存储待访问的坐标点 BFS 过程 (0, 0) 开始，将 d[0][0] 设为 0 并入队 依次处理队列中的每个点 (x, y)： 遍历四个方向 dx[i], dy[i]，尝试移动到 (a, b) = (x + dx[i], y + dy[i]) 如果 (a, b) 没超出边界、未访问、且 g[a][b] == 0（可通行），则： 记录 d[a][b] = d[x][y] + 1 将 (a, b) 入队，继续搜索 终点 (n - 1, m - 1) 的 d 值即为最短路径 时间复杂度 每个格子最多入队一次，BFS 遍历所有点的时间复杂度为 O(nm)\n空间复杂度 主要由 d 数组和队列 q 组成，空间复杂度为 O(nm)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include \u0026lt;cstring\u0026gt; #include \u0026lt;iostream\u0026gt; const int N = 110; int g[N][N]; // 迷宫 int d[N][N]; // 记录最短路径步数 int main() { int n, m; std::cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; // 读取迷宫 for (int i = 0; i \u0026lt; n; ++ i) for (int j = 0; j \u0026lt; m; ++ j) std::cin \u0026gt;\u0026gt; g[i][j]; std::memset(d, -1, sizeof d); // -1 表示未访问 d[0][0] = 0; // 起点步数设为 0 using PII = std::pair\u0026lt;int, int\u0026gt;; PII q[N * N]; // BFS 队列 int hh = 0, tt = 0; q[0] = {0, 0}; // 起点入队 // 方向数组：右、下、左、上 int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; // BFS 遍历 while (hh \u0026lt;= tt) { auto [x, y] = q[hh ++ ]; // 取队头元素 for (int i = 0; i \u0026lt; 4; ++ i) { int a = x + dx[i], b = y + dy[i]; if (a \u0026gt;= 0 \u0026amp;\u0026amp; a \u0026lt; n \u0026amp;\u0026amp; b \u0026gt;= 0 \u0026amp;\u0026amp; b \u0026lt; m \u0026amp;\u0026amp; g[a][b] == 0 \u0026amp;\u0026amp; d[a][b] == -1) { q[ ++ tt] = {a, b}; // 加入队列 d[a][b] = d[x][y] + 1; // 记录步数 } } } // 输出终点的最短步数 std::cout \u0026lt;\u0026lt; d[n - 1][m - 1] \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; return 0; } ","date":"2025-04-01T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/walking-through-the-maze/","title":"Walking Through the Maze"},{"content":"排列数字 给定一个整数 n，将数字 1 ∼ n 排成一排，将会有很多种排列方法\n现在，请你按照字典序将所有的排列方法输出\n输入格式 共一行，包含一个整数 n\n输出格式 按字典序输出所有排列方案，每个方案占一行\n数据范围\n1 1 ≤ n ≤ 7 输入样例：\n1 3 输出样例：\n1 2 3 4 5 6 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 分析 数组 path[N] 存储当前排列的路径 布尔数组 st[N] 记录数字是否已经被使用，避免重复选择 递归 dfs(n, u)，表示当前正在构造第 u 个位置的数字： 如果 u == n，说明已经构造完成，将 path 输出 否则，遍历 1 ~ n，依次尝试将每个未使用的数字填入 path[u]： 标记该数字为已使用 (st[num] = true) 递归进入下一层 dfs(n, u + 1) 回溯，撤销选择 st[num] = false 以尝试其他方案 时间复杂度 全排列的数量为 n!，每个排列需要 O(n) 的时间输出，总体时间复杂度 O(n * n!)\n空间复杂度 递归深度最多为 O(n)（递归栈） path 和 st 数组均为 O(n) 总体空间复杂度 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include \u0026lt;iostream\u0026gt; const int N = 10; int path[N]; // 存储当前排列 bool st[N]; // 记录数字是否已使用 void dfs(int n, int u) { if (u == n) // 递归终点：填满 path 数组 { for (int i = 0; i \u0026lt; n; ++ i) std::cout \u0026lt;\u0026lt; path[i] \u0026lt;\u0026lt; \u0026#39; \u0026#39;; std::cout \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; return; } for (int num = 1; num \u0026lt;= n; ++ num) // 依次尝试 1~n if (!st[num]) // 确保当前数字未使用 { st[num] = true; // 标记为已使用 path[u] = num; // 记录当前选择 dfs(n, u + 1); // 递归进入下一层 st[num] = false; // 回溯，撤销选择 } } int main() { int n; std::cin \u0026gt;\u0026gt; n; dfs(n, 0); // 从第 0 位置开始搜索 return 0; } ","date":"2025-03-31T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/arrange-numbers/","title":"Arrange Numbers"},{"content":"N 皇后问题 n−皇后问题是指将 n 个皇后放在 n × n 的国际象棋棋盘上，使得皇后不能相互攻击到，即任意两个皇后都不能处于同一行、同一列或同一斜线上\n现在给定整数 n，请你输出所有的满足条件的棋子摆法\n分析 N 皇后问题要求在 N × N 的棋盘上放置 N 个皇后，使得：\n同一行 不能有多个皇后 同一列 不能有多个皇后 同一对角线（主对角线、反对角线） 不能有多个皇后 数组\ncol[i] 记录第 i 列是否有皇后 dg[u - i + n] 记录主对角线 (row - col 相同) 是否有皇后 udg[u + i] 记录副对角线 (row + col 相同) 是否有皇后 f[N][N] 存储棋盘布局，初始时全部填充 '.'，放置皇后时填 'Q' 深度优先搜索（DFS）+ 回溯\nu == n：说明 n 行都已放置皇后，找到一个合法方案，输出棋盘 否则，尝试在当前行 u 的每一列 i 放置皇后： 若 (i, u) 不冲突，则放置 'Q'，更新 col、dg、udg，递归 dfs(n, u + 1) 回溯（撤销选择），恢复 '.'，取消 col、dg、udg 标记 时间复杂度 最坏情况下，回溯需要遍历所有排列，时间复杂度为 O(N!)\n空间复杂度 O(N²) 存储棋盘 f 递归深度最多 O(N)，因此递归栈空间复杂度为 O(N) C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include \u0026lt;iostream\u0026gt; const int N = 20; bool col[N], dg[N], udg[N]; // 标记列、主对角线、副对角线 char f[N][N]; // 存储棋盘 void dfs(int n, int u) { if (u == n) // 终止条件：成功放置 N 个皇后 { for (int i = 0; i \u0026lt; n; i ++ ) puts(f[i]); // 输出棋盘 puts(\u0026#34;\u0026#34;); // 额外空行 return; } for (int i = 0; i \u0026lt; n; ++ i) // 尝试在当前行 u 的每一列 i 放置皇后 if (!col[i] \u0026amp;\u0026amp; !dg[u - i + n] \u0026amp;\u0026amp; !udg[u + i]) // 剪枝，检查列 \u0026amp; 对角线是否可用 { col[i] = dg[u - i + n] = udg[u + i] = true; // 标记皇后占用位置 f[u][i] = \u0026#39;Q\u0026#39;; // 放置皇后 dfs(n, u + 1); // 递归放置下一行 f[u][i] = \u0026#39;.\u0026#39;; // 回溯：恢复空棋盘 col[i] = dg[u - i + n] = udg[u + i] = false; // 取消标记 } } int main() { int n; std::cin \u0026gt;\u0026gt; n; // 读取 N // 初始化棋盘 for (int i = 0; i \u0026lt; n; ++ i) for (int j = 0; j \u0026lt; n; ++ j) f[i][j] = \u0026#39;.\u0026#39;; dfs(n, 0); // 递归搜索 return 0; } ","date":"2025-03-31T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/n-queens/","title":"N-Queens"},{"content":"912. 排序数组 分析 选取基准：选择当前区间的第一个元素 nums[l] 作为基准 x 双指针划分： 使用 i 从左向右找大于等于 x 的元素 使用 j 从右向左找小于等于 x 的元素 交换 nums[i] 和 nums[j]，直到 i \u0026gt;= j，此时 j 是分界点 递归排序： 递归地对 [l, j] 和 [j + 1, r] 两部分继续快速排序，直到 l \u0026gt;= r 时间复杂度 时间复杂度 O(nlogn)\n空间复杂度 空间复杂度为 O(logn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /*** quick sort ***/ class Solution { public: // 快速排序函数 void quick_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int l, int r) { if (l \u0026gt;= r) // 递归终止条件 return; int x = nums[l], i = l - 1, j = r + 1; // 双指针划分 while (i \u0026lt; j) { do ++i; while (nums[i] \u0026lt; x); // 找到大于等于 x 的元素 do --j; while (nums[j] \u0026gt; x); // 找到小于等于 x 的元素 if (i \u0026lt; j) std::swap(nums[i], nums[j]); // 交换不符合位置的元素 } quick_sort(nums, l, j); // 递归处理左半部分 quick_sort(nums, j + 1, r); // 递归处理右半部分 } vector\u0026lt;int\u0026gt; sortArray(vector\u0026lt;int\u0026gt;\u0026amp; nums) { quick_sort(nums, 0, nums.size() - 1); return nums; } }; ","date":"2025-03-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/sort-an-array/","title":"Sort An Array"},{"content":"不同路径数 给定一个 n × m 的二维矩阵，其中的每个元素都是一个 [1,9] 之间的正整数\n从矩阵中的任意位置出发，每次可以沿上下左右四个方向前进一步，走过的位置可以重复走\n走了 k 次后，经过的元素会构成一个 k + 1 位数。\n请求出一共可以走出多少个不同的 k + 1 位数。\n输入格式\n第一行包含三个整数 n, m, k。\n接下来 n 行，每行包含 m 个空格隔开的整数，表示给定矩阵\n输出格式\n输出一个整数，表示可以走出的不同 k+1 位数的个数\n数据范围\n对于 30% 的数据, 1 ≤ n, m ≤ 2, 0 ≤ k ≤ 2 对于 100% 的数据，1 ≤ n, m ≤ 5, 0 ≤ k ≤ 5, m × n \u0026gt; 1\n输入样例：\n1 2 3 4 3 3 2 1 1 1 1 1 1 2 1 1 输出样例：\n1 5 分析 遍历起点：矩阵中每个 (i, j) 坐标都可以作为起点，进行搜索 深度优先搜索进行数的构造： 递归地沿上下左右 4 个方向移动。 走 k 步后，将生成的 k + 1 位数存入 unordered_set，去重后计算数量 递归参数包括当前位置 (x, y)、已走步数 u、当前构造的数字 cur 输出不同数的总个数 时间复杂度 每个格子最多 4^k 次递归调用，整体复杂度 O(nm * 4^k)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include \u0026lt;iostream\u0026gt; #include \u0026lt;unordered_set\u0026gt; const int N = 10; int g[N][N]; // 存储输入矩阵 std::unordered_set\u0026lt;int\u0026gt; s; // 记录不同的 k + 1 位数 int n, m, k; int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; // 方向数组，表示四个方向的移动 // 深度优先搜索 (DFS) void dfs(int x, int y, int u, int cur) { if (u == k) // 走 k 步后，存入 set s.insert(cur); else { for (int i = 0; i \u0026lt; 4; ++i) // 遍历四个方向 { int a = x + dx[i], b = y + dy[i]; if (a \u0026gt;= 0 \u0026amp;\u0026amp; a \u0026lt; n \u0026amp;\u0026amp; b \u0026gt;= 0 \u0026amp;\u0026amp; b \u0026lt; m) // 确保不越界 dfs(a, b, u + 1, cur * 10 + g[a][b]); // 递归搜索 } } } int main() { scanf(\u0026#34;%d%d%d\u0026#34;, \u0026amp;n, \u0026amp;m, \u0026amp;k); // 读取 n, m, k for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; m; ++j) scanf(\u0026#34;%d\u0026#34;, \u0026amp;g[i][j]); // 读取矩阵数据 for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; m; ++j) dfs(i, j, 0, g[i][j]); // 每个点作为起点进行 DFS printf(\u0026#34;%d\\n\u0026#34;, s.size()); // 输出不同的 k + 1 位数个数 return 0; } ","date":"2025-03-13T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/number-of-unique-paths/","title":"Number of Unique Paths"},{"content":"1588. 所有奇数长度子数组的和 分析 枚举起点 i 和终点 j，累积和\n双重循环遍历 arr，枚举所有可能的子数组起点 i 和终点 j 维护 sum 变量，计算 arr[i] ~ arr[j] 的子数组和： 每次 j 递增，就加上 arr[j] 如果 j - i + 1 是奇数，说明当前 sum 是一个奇数长度子数组和，就将当前 sum 累加到 res 里 时间复杂度 时间复杂度 O(n^2)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: int sumOddLengthSubarrays(vector\u0026lt;int\u0026gt;\u0026amp; arr) { int res = 0; for (int i = 0; i \u0026lt; arr.size(); ++i) // 遍历子数组的起点 i { int sum = 0; for (int j = i; j \u0026lt; arr.size(); ++j) // 遍历子数组的终点 j { sum += arr[j]; // 计算子数组和 if ((j - i + 1) % 2 == 1) // 仅当长度为奇数时才累加 res += sum; } } return res; } }; ","date":"2025-03-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/sum-of-all-odd-length-subarrays/","title":"Sum of All Odd Length Subarrays"},{"content":"350. 两个数组的交集II 分析 由于交集的每个元素的出现次数需要和两个数组中的最小出现次数一致，因此需要一个数据结构来存储 nums1 中每个元素的个数\n由于 std::unordered_multiset 允许存储重复元素，所以可以用它来存储 nums1 的所有元素\n遍历 nums2，检查当前元素是否在 unordered_multiset 中，如果存在，则加入结果数组，并从 unordered_multiset 中删除该元素，以确保每个元素只被匹配一次\n时间复杂度 时间复杂度 O(n + m)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: vector\u0026lt;int\u0026gt; intersect(vector\u0026lt;int\u0026gt;\u0026amp; nums1, vector\u0026lt;int\u0026gt;\u0026amp; nums2) { std::vector\u0026lt;int\u0026gt; res; std::unordered_multiset\u0026lt;int\u0026gt; hash; // 1. 统计 nums1 的元素频次 for (int x : nums1) hash.insert(x); // 2. 遍历 nums2，检查是否在 hash 中 for (int x : nums2) if (hash.count(x)) // 如果 x 在 hash 中 { res.push_back(x); // 加入结果数组 hash.erase(hash.find(x)); // 删除 hash 中的一个 x（防止重复计数） } return res; } }; ","date":"2025-03-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/intersection-of-two-arrays-ii/","title":"Intersection of Two Arrays II"},{"content":"349. 两个数组的交集 分析 将 nums1 中的元素插入到哈希集合中 遍历 nums2，对于每一个元素，如果该元素在哈希集合中存在，就表示它是 nums1 和 nums2 的交集中的元素 每次发现交集元素时，将其添加到结果数组 res 中，并从哈希集合中删除该元素，以保证结果中不包含重复元素 时间复杂度 时间复杂度 O(n + m)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: vector\u0026lt;int\u0026gt; intersection(vector\u0026lt;int\u0026gt;\u0026amp; nums1, vector\u0026lt;int\u0026gt;\u0026amp; nums2) { std::unordered_set\u0026lt;int\u0026gt; hash; // 用于存储 nums1 中的元素 std::vector\u0026lt;int\u0026gt; res; // 用于存储交集的结果 // 将 nums1 中的所有元素插入到哈希集合中 for (int x : nums1) hash.insert(x); // 遍历 nums2，如果元素存在于哈希集合中，则表示是交集的一部分 for (int x : nums2) if (hash.count(x)) // 查找 x 是否在哈希集合中 { res.push_back(x); // 将交集元素添加到结果中 hash.erase(x); // 删除该元素，避免重复添加 } return res; // 返回交集结果 } }; ","date":"2025-03-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/intersection-of-two-arrays/","title":"Intersection of Two Arrays"},{"content":"502.IPO 分析 项目排序\n将每个项目的启动资本和利润配对，并按照启动资本排序 动态选择\n将当前资本 w 可以启动的所有项目的利润放入最大堆 从堆中选择一个利润最大的项目并完成，更新当前资本 退出条件\n如果没有可以选择的项目（即堆为空），或者已经完成了 k 个项目，就可以退出 时间复杂度 排序项目数组：O(nlogn) 每次操作通过堆插入或弹出元素，最多需要 O(logn) 时间，因此 k 次操作的复杂度是 O(klogn) 总时间复杂度 O(nlogn)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public: int findMaximizedCapital(int k, int w, vector\u0026lt;int\u0026gt;\u0026amp; profits, vector\u0026lt;int\u0026gt;\u0026amp; capital) { int n = profits.size(); std::vector\u0026lt;std::pair\u0026lt;int, int\u0026gt;\u0026gt; projects; // 将项目的启动资本和利润配对，并按启动资本排序 for (int i = 0; i \u0026lt; n; ++i) projects.push_back({capital[i], profits[i]}); std::sort(projects.begin(), projects.end()); // 最大堆，用来存放前资本 w 可以启动的所有项目的利润 std::priority_queue\u0026lt;int\u0026gt; heap; int i = 0; // 完成最多 k 个项目 while (k--) { // 将所有可以启动的项目的利润加入堆 while (i \u0026lt; n \u0026amp;\u0026amp; projects[i].first \u0026lt;= w) heap.push(projects[i++].second); // 如果堆为空，表示没有可用的项目，退出 if (heap.empty()) break; // 从堆中选择利润最大的项目并完成 w += heap.top(); heap.pop(); } return w; } }; ","date":"2025-02-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/ipo/","title":"IPO"},{"content":"221. 最大正方形 分析 初始化：\nn 和 m 分别为矩阵的行数和列数。 状态表示：f[i][j] 表示以 matrix[i-1][j-1] 为右下角的最大正方形的边长 状态转移：\n对于每一个 matrix[i - 1][j - 1] == '1'，我们通过状态转移方程更新 f[i][j] 的值 f[i][j] 的值是通过左边、上边和左上角的最小值加 1 来计算的，表示我们可以扩展一个更大的正方形：f[i][j] = min(f[i-1][j], f[i][j-1], f[i-1][j-1]) + 1 更新最大边长：\n每次更新 f[i][j] 时，比较当前 f[i][j] 的值是否大于当前最大边长 res，如果大于，则更新 res 返回结果：\n最终返回的结果是 res * res，即最大正方形的面积 时间复杂度 时间复杂度 O(n * m)\n空间复杂度 空间复杂度为 O(n * m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: int maximalSquare(vector\u0026lt;vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; matrix) { // 判断输入矩阵是否为空 if (matrix.empty() || matrix[0].empty()) return 0; int n = matrix.size(), m = matrix[0].size(); // 初始化状态表示 f，大小为 (n+1) * (m+1) std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f(n + 1, std::vector\u0026lt;int\u0026gt;(m + 1)); int res = 0; // 存储最大正方形的边长 // 遍历矩阵每个元素 for (int i = 1; i \u0026lt;= n; ++i) for (int j = 1; j \u0026lt;= m; ++j) if (matrix[i - 1][j - 1] == \u0026#39;1\u0026#39;) // 如果当前位置是 \u0026#39;1\u0026#39; { // 更新 f[i][j] 为左边、上边和左上角最小值加 1 f[i][j] = std::min(f[i - 1][j], std::min(f[i][j - 1], f[i - 1][j - 1])) + 1; // 更新最大正方形边长 res = std::max(res, f[i][j]); } // 返回最大正方形的面积 return res * res; } }; ","date":"2025-02-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximal-square/","title":"Maximal Square"},{"content":"188. 买卖股票的最佳时机IV 分析 无交易限制的情况：\n当 k \u0026gt;= n / 2（即可以无限次交易的情况），那么我们可以通过每次只要当前价格比前一天高就进行交易，获取利润 这种情况下，我们只需要遍历数组，当 prices[i] \u0026lt; prices[i + 1] 时，进行买卖，利润就会累加 有交易次数限制的情况：\n当 k \u0026lt; n / 2 时，我们需要考虑最多进行 k 次交易的情况。为此，我们需要使用动态规划来计算不同交易次数的最大利润 使用动态规划状态 f[i][j] 表示前 i 天内进行 j 次交易的最大利润。我们还可以使用辅助数组 g[i][j] 来记录前 i 天内进行 j-1 次交易，买入股票时的状态（即股票买入价格的最优状态） 每次更新 f[i][j] 和 g[i][j] 时，考虑的是： 卖出：通过 g[i-1][j] + prices[i-1] 来记录前 i-1 天完成 j 次交易的最大利润加上当前价格（即卖出） 买入：通过 f[i-1][j-1] - prices[i-1] 来记录前 i-1 天完成 j-1 次交易的最大利润减去当前价格（即买入） 状态转移方程\nf[i][j] = max(f[i-1][j], g[i-1][j] + prices[i-1])：卖出时，选择前 i-1 天进行 j 次交易的最大利润，再加上今天卖出的利润 g[i][j] = max(g[i-1][j], f[i-1][j-1] - prices[i-1])：买入时，选择前 i-1 天进行 j-1 次交易的最大利润，再减去今天买入的价格 时间复杂度 时间复杂度 O(k * n)\n空间复杂度 空间复杂度为 O(k * n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public: int maxProfit(int k, vector\u0026lt;int\u0026gt;\u0026amp; prices) { int res = 0; int n = prices.size(); // 交易次数 k 大于等于 n / 2，则可以进行无限次交易 if (k \u0026gt;= n / 2) { for (int i = 0; i \u0026lt; n - 1; ++i) if (prices[i] \u0026lt; prices[i + 1]) res += prices[i + 1] - prices[i]; // 贪心：只要后一天价格高于前一天就交易 return res; } int INF = 0x3f3f3f3f; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; f(n + 1, vector\u0026lt;int\u0026gt;(k + 1, -INF)); // 动态规划数组，初始化为一个大负值 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; g = f; // 辅助数组，用于存储买入操作时的状态 f[0][0] = 0; // 在第 0 天，不进行任何交易，利润为 0 // 遍历每一天 for (int i = 1; i \u0026lt;= n; ++i) for (int j = 0; j \u0026lt;= k; ++j) { // 若不做交易，利润保持不变。若交易，进行卖出操作 f[i][j] = max(f[i - 1][j], g[i - 1][j] + prices[i - 1]); g[i][j] = g[i - 1][j]; // 不进行买入操作 if (j) // 如果可以进行交易 g[i][j] = max(g[i][j], f[i - 1][j - 1] - prices[i - 1]); // 进行买入操作 res = max(res, f[i][j]); // 更新最大利润 } return res; // 返回最终的最大利润 } }; ","date":"2025-02-23T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/best-time-to-buy-and-sell-stock-iv/","title":"Best Time to Buy and Sell Stock IV"},{"content":"123. 买卖股票的最佳时机III 分析 为了计算出最大利润，可以将问题拆解成两个阶段：\n第一笔交易：计算在前几天内，买入并卖出获得的最大利润 第二笔交易：计算在后几天内，买入并卖出获得的最大利润，并与第一笔交易的最大利润结合 第一次交易：\n初始化一个数组 f，用来记录前 i 天结束时的最大利润 变量 minp 用来记录前 i 天中的最低股价。在每一天，计算当前股价和 minp 的差值，更新 f[i] 为当前的最大利润 第二次交易：\n在完成第一次交易后，我们从后往前遍历 prices 数组，计算从第 i 天开始的最大卖出利润 maxp - prices[i-1]，然后将其与 f[i-1]（表示前 i-1 天的最大利润）合并得到总利润 最终结果：\nres 记录的是两次交易合并的最大利润 时间复杂度 时间复杂度 O(n)，遍历 prices 数组两次\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: int maxProfit(vector\u0026lt;int\u0026gt;\u0026amp; prices) { int n = prices.size(); // f[i] 表示前 i 天结束时的最大利润 std::vector\u0026lt;int\u0026gt; f(n + 1); // 计算第一次交易的最大利润 for (int i = 1, minp = INT_MAX; i \u0026lt;= n; ++i) { f[i] = std::max(f[i - 1], prices[i - 1] - minp); // max(不交易, 卖出利润) minp = std::min(minp, prices[i - 1]); // 更新最小买入价格 } int res = 0; // 计算第二次交易的最大利润 for (int i = n, maxp = 0; i \u0026gt; 0; --i) { // 计算第二次交易和第一次交易合并的最大利润 res = std::max(res, maxp - prices[i - 1] + f[i - 1]); maxp = std::max(maxp, prices[i - 1]); // 更新最大卖出价格 } return res; } }; ","date":"2025-02-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/best-time-to-buy-and-sell-stock-iii/","title":"Best Time to Buy and Sell Stock III"},{"content":"97. 交错字符串 分析 初始化：\n先计算 s1 和 s2 的长度 n 和 m，并检查 s3 的长度是否为 n + m。如果不是，直接返回 false 为了方便处理边界情况，我们给 s1、s2 和 s3 的开头加上一个空格 ' '，这样可以简化状态转移的边界处理 状态表示 f：\nf[i][j] 表示由 s1 的前 i 个字符和 s2 的前 j 个字符是否能交错组成 s3 的前 i + j 个字符。 初始化 f[0][0] = true，表示空的 s1 和空的 s2 能交错组成空的 s3 动态规划转移：\n对于每一个 i 和 j，检查当前字符是否匹配： 如果 s1[i] 和 s3[i + j] 相等，则可以从 f[i-1][j] 更新到 f[i][j] 如果 s2[j] 和 s3[i + j] 相等，则可以从 f[i][j-1] 更新到 f[i][j] 结果返回：\n最终返回 f[n][m]，即判断 s1 和 s2 是否能够交错组成 s3 时间复杂度 时间复杂度 O(n * m)，其中 n 是 s1 的长度，m 是 s2 的长度\n空间复杂度 空间复杂度为 O(n * m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution { public: bool isInterleave(string s1, string s2, string s3) { int n = s1.size(), m = s2.size(); // 如果 s3 的长度不等于 s1 和 s2 的长度之和，直接返回 false if (s3.size() != n + m) return false; // 加上空字符方便处理边界情况 s1 = \u0026#39; \u0026#39; + s1; s2 = \u0026#39; \u0026#39; + s2; s3 = \u0026#39; \u0026#39; + s3; // 初始化状态表示f std::vector\u0026lt;std::vector\u0026lt;bool\u0026gt;\u0026gt; f(n + 1, std::vector\u0026lt;bool\u0026gt;(m + 1)); // 状态转移 for (int i = 0; i \u0026lt;= n; ++i) { for (int j = 0; j \u0026lt;= m; ++j) { if (i == 0 \u0026amp;\u0026amp; j == 0) // 起始点，空的 s1 和 s2 可以交错组成空的 s3 f[i][j] = true; else { // 如果 s1[i] == s3[i + j]，则可以从 f[i-1][j] 更新到 f[i][j] if (i \u0026amp;\u0026amp; s1[i] == s3[i + j]) f[i][j] = f[i - 1][j]; // 如果 s2[j] == s3[i + j]，则可以从 f[i][j-1] 更新到 f[i][j] if (j \u0026amp;\u0026amp; s2[j] == s3[i + j]) f[i][j] = f[i][j] || f[i][j - 1]; } } } // 返回结果 return f[n][m]; } }; ","date":"2025-02-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/interleaving-string/","title":"Interleaving String"},{"content":"63. 不同路径II 分析 状态定义：f[i][j] 代表从起点 (0, 0) 到达 (i, j) 的路径数\n状态转移：\n如果 obstacleGrid[i][j] == 0，表示当前位置可以走（没有障碍物），我们根据递推公式更新路径数： 如果当前位置是起点 (0, 0)，则 f[0][0] = 1 否则，当前位置的路径数由上方和左方的路径数之和 f[i][j] = f[i-1][j] + f[i][j-1] 得出 如果 obstacleGrid[i][j] == 1，说明当前位置有障碍物，不能走到这里，无需处理 f[i][j]，因为初始的 f[i][j] 值为 0 返回最终结果：循环结束后，f[n-1][m-1] 就是从起点到终点的不同路径数\n时间复杂度 时间复杂度 O(n * m)\n空间复杂度 空间复杂度为 O(n * m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: int uniquePathsWithObstacles(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; obstacleGrid) { int n = obstacleGrid.size(), m = obstacleGrid[0].size(); // 初始化 DP 数组 std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f(n, std::vector\u0026lt;int\u0026gt;(m)); // 遍历每个格子 for (int i = 0; i \u0026lt; n; ++ i) for (int j = 0; j \u0026lt; m; ++ j) if (obstacleGrid[i][j] == 0) // 如果当前格子没有障碍物 { if (i == 0 \u0026amp;\u0026amp; j == 0) // 起始位置 f[i][j] = 1; // 从起点出发，路径数为 1 else { if (i) // 如果不在第一行 f[i][j] += f[i - 1][j]; // 从上方格子来的路径数 if (j) // 如果不在第一列 f[i][j] += f[i][j - 1]; // 从左方格子来的路径数 } } return f[n - 1][m - 1]; // 返回右下角的路径数 } }; ","date":"2025-02-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/unique-paths-ii/","title":"Unique Paths II"},{"content":"120. 三角形最小路径和 分析 状态定义：triangle[i][j] 表示从底层到第i行j列节点的最小路径和\n状态转移：triangle[i][j] 的值等于下一层相邻节点中的较小值 + 当前节点值: triangle[i][j] += std::min(triangle[i + 1][j], triangle[i + 1][j + 1])\n最终，triangle[0][0] 中存储的就是从底端到顶端的最小路径和\n时间复杂度 时间复杂度 O(n^2)，其中 n 是三角形的行数。每一行最多有 n 个元素，处理每个元素时需要计算两个相邻节点的最小值\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public: int minimumTotal(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; triangle) { // 从倒数第二行开始，向上更新最小路径和 for (int i = triangle.size() - 2; i \u0026gt;= 0; --i) for (int j = 0; j \u0026lt; triangle[i].size(); ++j) // 遍历每一行的元素 triangle[i][j] += std::min(triangle[i + 1][j], triangle[i + 1][j + 1]); // 更新当前点的最小路径和 return triangle[0][0]; // 返回到达顶点的最小路径和 } }; ","date":"2025-02-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/triangle/","title":"Triangle"},{"content":"149. 直线上最多的点数 分析 哈希表：通过哈希表来记录每个斜率出现的频次。对于每个点，遍历其他点，计算斜率，并更新哈希表。如果多个点具有相同的斜率，则它们在同一条直线上 特殊情况：需要考虑以下几种情况： 重复点：如果两个点重合，它们的斜率是相同的 竖直线：当 dx = 0 时，说明是竖直线，斜率可以用一个特殊值（如无穷大）表示 最终结果：对于每个点，计算该点与其他点的最大共线点数，并维护一个最大值 时间复杂度 时间复杂度 O(n^2)，其中 n 是点的数量。对于每个点 p，我们遍历所有其他点 q，计算它们的斜率\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public: int maxPoints(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; points) { int res = 0; for (std::vector\u0026lt;int\u0026gt;\u0026amp; p : points) { int same = 0, vertical = 0; std::unordered_map\u0026lt;long double, int\u0026gt; count; for (std::vector\u0026lt;int\u0026gt;\u0026amp; q : points) { if (p == q) // 统计重叠点的个数 ++same; else if (p[0] == q[0]) // x坐标相同，统计竖直线上点的个数 ++vertical; else { long double slope = (long double)(q[1] - p[1]) / (q[0] - p[0]); // 计算斜率 ++count[slope]; // 记录相同斜率的点数 } } // 找到最大斜率的点数（加上重叠点数） for (auto [slope, cnt] : count) res = std::max(res, same + cnt); // 比较竖直线的点数（加上重叠点数）和最大斜率的点数 res = std::max(res, same + vertical); } return res; } }; ","date":"2025-02-18T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/max-points-on-a-line/","title":"Max Points On A Line"},{"content":"50. Pow(x, n) 分析 长整型 LL 定义：long long 类型用于处理 n = INT_MIN 的情况，因为 abs(INT_MIN) 会溢出 int 类型 判断 n 是否为负数：如果 n 是负数，最后需要返回 1 / res 快速幂的实现：通过右移 k（即 k \u0026gt;\u0026gt;= 1）将 n 每次除以 2，并根据 k 是否为奇数来决定是否将当前的 x 乘到结果中 计算 x^n： 如果 k 的最低位是 1（即 k \u0026amp; 1），则当前的 x 需要乘到结果中 每次将 x 平方，即 x = x * x，这相当于将指数减少了一半 负指数情况：如果 n 是负数，返回 1 / res，否则返回计算结果 res 时间复杂度 时间复杂度 O(logn)，每次将 n 减半，通过二进制方式快速计算\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: double myPow(double x, int n) { using LL = long long; // 使用 long long 类型来处理 n = INT_MIN 的特殊情况 bool is_minus = (n \u0026lt; 0); // 判断 n 是否为负数 double res = 1; // 使用绝对值的 n 进行计算，避免负数的影响 for (LL k = std::abs(LL(n)); k; k \u0026gt;\u0026gt;= 1) // 快速幂算法：k 是二进制表示 { if (k \u0026amp; 1) // 如果 k 的最低位是 1 res *= x; // 将当前的 x 乘到结果上 x *= x; // x 自身平方 } if (is_minus) // 如果 n 是负数，返回 1 / res return 1 / res; return res; } }; ","date":"2025-02-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/pow-x-n/","title":"Pow X N"},{"content":"排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include \u0026lt;iostream\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;vector\u0026gt; void quick_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int l, int r); void merge_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int l, int r); void select_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums); void insert_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums); void binary_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums); void bubble_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums); int main() { int n; scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); std::vector\u0026lt;int\u0026gt; nums(n); for (int i = 0; i \u0026lt; n; ++ i) scanf(\u0026#34;%d\u0026#34;, \u0026amp;nums[i]); quick_sort(nums, 0, n - 1); merge_sort(nums, 0, n - 1); select_sort(nums); insert_sort(nums); binary_sort(nums); bubble_sort(nums); for (int i = 0; i \u0026lt; n; ++ i) printf(\u0026#34;%d \u0026#34;, nums[i]); return 0; } 快速排序 选取基准值（pivot）：x = nums[(l + r) \u0026gt;\u0026gt; 1] 双指针划分数组： i 从左往右，找到第一个 ≥ pivot 的数 j 从右往左，找到第一个 ≤ pivot 的数 交换 nums[i] 和 nums[j]，直到 i \u0026gt;= j 递归处理左右子数组： 递归排序 [l, j]（左半部分） 递归排序 [j+1, r]（右半部分） 复杂度 时间复杂度 最佳情况（每次都均匀划分）：O(nlogn) 平均情况（随机数据）：O(nlogn) 最坏情况（已排序数组，划分极端不均匀）：O(n²) 空间复杂度 最坏情况（单侧划分，递归深度 O(n))）：O(n) 平均情况（均匀划分，递归深度 O(logn))）：O(logn) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void quick_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int l, int r) { if (l \u0026gt;= r) return; // 递归终止条件 int i = l - 1, j = r + 1, x = nums[(l + r) \u0026gt;\u0026gt; 1]; // 取中间值作为基准 while (i \u0026lt; j) { do ++i; while (nums[i] \u0026lt; x); // 找到左侧大于等于 pivot 的元素 do --j; while (nums[j] \u0026gt; x); // 找到右侧小于等于 pivot 的元素 if (i \u0026lt; j) std::swap(nums[i], nums[j]); // 交换不符合条件的元素 } quick_sort(nums, l, j); // 递归排序左半部分 quick_sort(nums, j + 1, r); // 递归排序右半部分 } 归并排序 递归地将数组划分为左右两部分： 直到子数组长度为 1，即 l \u0026gt;= r，终止递归 分别对左右两部分进行排序： 递归调用 merge_sort(nums, l, mid) 递归调用 merge_sort(nums, mid + 1, r) 合并两个有序数组： 使用双指针方法合并 nums[l ~ mid] 和 nums[mid+1 ~ r] 额外使用一个 tmp 数组存储排序后的结果，再复制回 nums 复杂度 时间复杂度 每次划分数组需要 O(logn) 次递归 每次合并两个有序数组需要 O(n) 时间 总时间复杂度：O(nlogn) 空间复杂度 需要额外 O(n) 的辅助数组 tmp 存储合并结果 总空间复杂度：O(n) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void merge_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int l, int r) { if (l \u0026gt;= r) return; // 递归终止条件：子数组长度为1 int mid = (l + r) \u0026gt;\u0026gt; 1; // 取中间点 merge_sort(nums, l, mid); // 递归排序左半部分 merge_sort(nums, mid + 1, r); // 递归排序右半部分 std::vector\u0026lt;int\u0026gt; tmp(nums.size()); // 临时数组用于合并 int i = l, j = mid + 1, k = 0; // i 遍历左半部分，j 遍历右半部分 // 归并两个有序子数组 while (i \u0026lt;= mid \u0026amp;\u0026amp; j \u0026lt;= r) { if (nums[i] \u0026lt;= nums[j]) tmp[k++] = nums[i++]; // 选较小的数加入临时数组 else tmp[k++] = nums[j++]; } // 处理剩余元素 while (i \u0026lt;= mid) tmp[k++] = nums[i++]; while (j \u0026lt;= r) tmp[k++] = nums[j++]; // 复制回原数组 for (int i = l, k = 0; i \u0026lt;= r; ++i, ++k) nums[i] = tmp[k]; } 堆排序 建堆：从最后一个非叶子节点 n/2 开始自上而下堆化 down() 建立大根堆： 满足大根堆的性质：父节点大于等于子节点 建立小根堆： 满足小根堆的性质：父节点小于等于子节点 排序： 交换堆顶元素（最大值/最小值）和堆尾元素，然后调整剩余元素继续保持大根堆/小根堆 每次取出堆顶元素，最终形成升序/降序排列 复杂度 时间复杂度 建堆：O(n) 排序调整堆：每次 down() 需要 O(log n)，执行 n 次，总计 O(nlogn) 总时间复杂度：O(nlogn) 空间复杂度 堆排序在原数组上进行排序，没有额外的数组存储，空间复杂度 O(1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include \u0026lt;iostream\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;vector\u0026gt; // 维护小根堆性质 (较大值向下调整) void big_down(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int u, int sz) { int t = u; if (u * 2 \u0026lt;= sz \u0026amp;\u0026amp; nums[t] \u0026gt; nums[u * 2]) // 左子节点较小 t = u * 2; if (u * 2 + 1 \u0026lt;= sz \u0026amp;\u0026amp; nums[t] \u0026gt; nums[u * 2 + 1]) // 右子节点较小 t = u * 2 + 1; if (t != u) // 需要交换 { std::swap(nums[t], nums[u]); big_down(nums, t, sz); // 递归调整 } } // 维护大根堆性质 (较小值向下调整) void small_down(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int u, int sz) { int t = u; if (u * 2 \u0026lt;= sz \u0026amp;\u0026amp; nums[t] \u0026lt; nums[u * 2]) // 左子节点较大 t = u * 2; if (u * 2 + 1 \u0026lt;= sz \u0026amp;\u0026amp; nums[t] \u0026lt; nums[u * 2 + 1]) // 右子节点较大 t = u * 2 + 1; if (t != u) // 需要交换 { std::swap(nums[t], nums[u]); small_down(nums, t, sz); // 递归调整 } } int main() { int n; scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); // 下标从1开始存储堆，避免叶子结点边界处理 std::vector\u0026lt;int\u0026gt; nums(n + 1); for (int i = 1; i \u0026lt;= n; ++i) { scanf(\u0026#34;%d\u0026#34;, \u0026amp;nums[i]); } // 建堆 (从最后一个非叶子节点开始) for (int i = n / 2; i \u0026gt;= 1; --i) { big_down(nums, i, n); } int sz = n; // 堆排序 for (int i = 0; i \u0026lt; n; ++i) { printf(\u0026#34;%d \u0026#34;, nums[1]); // 输出堆顶元素 std::swap(nums[1], nums[sz]); // 交换堆顶和堆尾元素 -- sz; // 缩小堆的范围 big_down(nums, 1, sz); // 重新调整堆 } printf(\u0026#34;\\n\u0026#34;); return 0; } 选择排序 在第 i 轮中，从 i 到 n - 1 中选择最小值，并将其与 i 位置交换 进行 n - 1 轮排序后，数组有序 复杂度 时间复杂度 O(n^2) 空间复杂度 O(1) 1 2 3 4 5 6 7 8 9 10 11 12 void select_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums) { for (int i = 0; i \u0026lt; nums.size() - 1; ++i) { int min_idx = i; // 记录当前最小值的索引 for (int j = i + 1; j \u0026lt; nums.size(); ++j) if (nums[min_idx] \u0026gt; nums[j]) // 找到更小的元素 min_idx = j; std::swap(nums[i], nums[min_idx]); // 交换当前元素与最小值 } } 插入排序 从第二个元素开始，假设第一个元素已经是有序的 比较当前元素与已排序部分的元素，找到合适的插入位置 将当前元素插入到合适的位置，并调整数组中元素的位置 复杂度 时间复杂度 最佳情况（数组已经有序）：O(n) 只需进行一次比较即可，每个元素都只需插入一次 最坏情况（数组逆序）：O(n^2) 每次插入时需要将前面的所有元素都移动一遍 平均时间复杂度：O(n^2) 空间复杂度 空间复杂度：O(1)，插入排序是原地排序，无需额外空间 1 2 3 4 5 6 7 8 9 10 11 12 13 void insert_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums) { for (int i = 1; i \u0026lt; nums.size(); ++ i) // 从第二个元素开始 { int x = nums[i], j = i; // 保存当前元素并从当前位置开始向前检查 while (j \u0026gt;= 1 \u0026amp;\u0026amp; x \u0026lt; nums[j - 1]) // 查找插入位置 { nums[j] = nums[j - 1]; // 向右移动元素 -- j; // 移动索引 } nums[j] = x; // 插入当前元素 } } 折半插入排序 从第二个元素开始，假设第一个元素已经是有序的 使用二分查找在已排序部分找到插入的位置 将当前位置之后的元素向后移动一位，并将当前元素插入到找到的位置 复杂度 时间复杂度 最佳情况（数组已经有序）：O(n) 每次只需进行一次比较即可，每个元素都只需插入一次 最坏情况（数组逆序）：O(n^2) 二分查找将时间复杂度降低为 O(logn)，但每次插入仍然需要将元素移动，整体仍为 O(n^2) 平均时间复杂度：O(n^2) 空间复杂度 空间复杂度：O(1)，原地排序，不需要额外的空间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void binary_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums) { for (int i = 1; i \u0026lt; nums.size(); ++ i) // 从第二个元素开始 { if (nums[i - 1] \u0026lt; nums[i]) // 如果当前元素比前一个元素大，则跳过 continue; int x = nums[i]; // 保存当前元素 int l = 0, r = i - 1; // 设置二分查找的左右边界 while (l \u0026lt; r) // 二分查找插入位置 { int mid = (l + r) \u0026gt;\u0026gt; 1; if (x \u0026lt; nums[mid]) r = mid; // 当前元素小于mid，右边界左移 else l = mid + 1; // 当前元素大于mid，左边界右移 } for (int j = i - 1; j \u0026gt;= r; -- j) // 移动元素 nums[j + 1] = nums[j]; nums[r] = x; // 插入当前元素到正确的位置 } } 冒泡排序 冒泡排序主要通过重复交换相邻的元素来将较小的元素冒泡到数组的前端，较大的元素则被“推”到数组的末端\n比较相邻元素：从数组的第一个元素开始，逐一比较相邻的两个元素。如果它们的顺序不正确（即前一个元素大于后一个元素），则交换它们的位置 冒泡操作：经过一轮遍历后，最小的元素被冒泡到数组的前端 重复遍历：继续对剩余部分进行相同的操作，直到整个数组排序完成 提前终止：如果某一轮遍历中没有发生交换，说明数组已经是有序的，可以提前终止排序 复杂度 时间复杂度分析 最坏时间复杂度：O(n^2)，即当数组完全逆序时，每一轮都需要交换，每一对相邻元素都会进行比较和交换 最佳时间复杂度：O(n)，即当数组已经是有序时，只需进行一次遍历，不需要交换 平均时间复杂度：O(n^2)，适用于随机排列的数组 空间复杂度 空间复杂度：O(1)，原地排序，不需要额外的存储空间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void bubble_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums) { for (int i = 0; i \u0026lt; nums.size() - 1; ++ i) // 外层循环控制排序的轮数 { bool is_swap = false; // 用于检查是否发生交换 for (int j = nums.size() - 1; j \u0026gt; i; -- j) // 内层循环进行元素的比较与交换 { if (nums[j] \u0026lt; nums[j - 1]) // 如果前一个元素大于后一个元素 { std::swap(nums[j], nums[j - 1]); // 交换元素 is_swap = true; // 标记发生了交换 } } if (!is_swap) // 如果没有发生交换，说明数组已经有序，可以提前结束 break; } } ","date":"2025-02-17T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/sort/","title":"Sort"},{"content":"69. x的平方根 分析 初始化：设定左右边界 l = 0 和 r = x，表示搜索区间 二分查找： 通过 (l + 1ll + r) \u0026gt;\u0026gt; 1 计算中间值 mid。这里使用了 1ll 来确保计算过程中不会发生整数溢出 判断 mid * mid \u0026lt;= x，如果成立，说明平方根的整数部分不大于 mid，我们将搜索区间的左边界 l 移动到 mid 如果 mid * mid \u0026gt; x，说明平方根在 mid 左边，更新右边界 r 为 mid - 1 终止条件：当 l == r 时，搜索结束，r 即为 x 的整数平方根 时间复杂度 总时间复杂度 O(logx)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: int mySqrt(int x) { int l = 0, r = x; // 设置搜索区间 [0, x] while (l \u0026lt; r) { int mid = (l + 1ll + r) \u0026gt;\u0026gt; 1; // 计算中间值 mid，避免整数溢出 if (mid \u0026lt;= x / mid) // 如果 mid 的平方小于等于 x，则 mid 可能是答案 l = mid; // 如果 mid 满足条件，调整左边界为 mid else r = mid - 1; // 如果 mid 不满足条件，调整右边界为 mid-1 } return r; // 返回最终的结果，r 为整数平方根 } }; ","date":"2025-02-16T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/sqrt-x/","title":"Sqrt X"},{"content":"172. 阶乘后的零 分析 尾随零的产生是因为 10 = 2 * 5，每次有一个因数 2 和 5 的乘积，都会形成一个 10，从而在阶乘结果的末尾增加一个零\n由于阶乘的数值中，因数 2 的出现频率远高于因数 5，所以我们只需要考虑 5 出现的次数，计算 n! 中 5 的因数的个数，即可得到尾随零的个数\n计算 n! 中因数 5 的个数 对于 n 中所有能被 5 整除的数，每个数都至少包含一个 5，例如：5, 10, 15, 20... 如果一个数能被 25 整除，则它会包含额外的一个因数 5，比如 25 会有两个因数 5，50 也会有两个因数 5，依此类推 因此，n! 中尾随零的数量就是 n / 5 + n / 25 + n / 125 + ...，直到 5^k \u0026gt; n 时间复杂度 时间复杂度：O(log_5 n) ，因为每次将 n 除以 5，直到 n 小于 5 为止\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public: int trailingZeroes(int n) { int res = 0; while (n) { res += n / 5; // 计算能被 5 整除的数 n /= 5; // 除以 5，继续计算更高次方的 5 } return res; } }; ","date":"2025-02-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/factorial-trail-zeroes/","title":"Factorial Trail Zeroes"},{"content":"9. 回文数 分析 负数一定不是回文数： 例如 -121 变成 121-，不对称，因此直接返回 false 将数字翻转后与原数比较： 反转 x 的每一位数字，存入 b，然后判断 b == x 是否成立 时间复杂度 时间复杂度与 x 的位数成正比，最多 10 次（int 最大 32 位）\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public: bool isPalindrome(int x) { if (x \u0026lt; 0) // 负数一定不是回文数 return false; long a = x, b = 0; // a 复制 x，b 存储翻转后的数 while (a) { b = b * 10 + a % 10; // 取出 a 的最后一位，加到 b 的末尾 a /= 10; // 去掉 a 的最后一位 } return b == x; // 判断反转后的 b 是否等于原数 x } }; ","date":"2025-02-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/palindrome-number/","title":"Palindrome Number"},{"content":"66. 加一 分析 从低位到高位进行加法，考虑进位 如果所有位数都发生进位（如 999 -\u0026gt; 1000），需要在最高位补 1 时间复杂度 时间复杂度 O(n)，遍历数组两次（反转 + 加法）\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: vector\u0026lt;int\u0026gt; plusOne(vector\u0026lt;int\u0026gt;\u0026amp; digits) { std::reverse(digits.begin(), digits.end()); // 反转数组，使低位在前 int t = 1; // 初始进位值为 1，即加 1 for (int\u0026amp; x: digits) { t += x; // 当前位加上进位 x = t % 10; // 更新当前位 t /= 10; // 计算新的进位 } if (t) // 最高位还有进位 digits.push_back(t); std::reverse(digits.begin(), digits.end()); // 还原数组顺序 return digits; } }; ","date":"2025-02-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/plus-one/","title":"Plus One"},{"content":"201. 数字范围按位与 分析 按位与的性质 任何数字与 0 进行按位与，结果都是 0，即 x \u0026amp; 0 = 0 相邻数字的按位与会减少 1 位的 1，例如： 1 2 3 4 7 = 0111 6 = 0110 5 = 0101 4 = 0100 计算 5 \u0026amp; 6 \u0026amp; 7 = 4，可以看到最右侧的 1 被清除了\n观察区间变化 若 left 和 right 的二进制前缀相同，则它们的按位与结果保留这个前缀，后面的位全部补 0 若 left 和 right 的某一位出现了不同（即 0-\u0026gt;1 或 1-\u0026gt;0），则从该位及其更低位的按位与结果必然为 0 时间复杂度 由于整数的二进制表示最多 32 位，我们最多遍历 32 次，因此时间复杂度为 O(1)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: int rangeBitwiseAnd(int left, int right) { int res = 0; // 存储最终结果 for (int i = 30; i \u0026gt;= 0; -- i) // 从高位到低位遍历每一位 { if ((left \u0026gt;\u0026gt; i \u0026amp; 1) != (right \u0026gt;\u0026gt; i \u0026amp; 1)) // 如果某一位的 left 和 right 不同，终止循环 break; if (left \u0026gt;\u0026gt; i \u0026amp; 1) // 如果 left 在该位是 1，则保留该位 res += (1 \u0026lt;\u0026lt; i); } return res; } }; ","date":"2025-02-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/bitwise-and-of-numbers-range/","title":"Bitwise and of Numbers Range"},{"content":"373. 查找和最小的K对数字 分析 初始化最小堆：\n初始时，最小堆可以由 nums1[0] 和 nums2[i]（i 从 0 到 m-1）组成，因为 nums1 和 nums2 都是有序的，最小的数对总是以 nums1[0] 开头与 nums2 的元素组合 每个堆元素包含一个三元组 [sum, i, j]，其中 sum 是当前数对的和，i 是 nums1 的索引，j 是 nums2 的索引 使用堆来不断提取和最小的数对：\n每次从堆中取出和最小的数对，并将其加入结果集。 如果 nums1[i+1] 和 nums2[j] 存在，推入堆中新的数对 nums1[i+1] 和 nums2[j] 的组合 堆的操作：\n每次都取出和最小的数对进行处理，直到得到 k 个数对 时间复杂度 初始化堆：将 nums2 中的每个元素与 nums1[0] 配对，复杂度为 O(mlogm)，其中 m 是 nums2 的大小 每次弹出和插入操作的时间复杂度为 O(logk) 总体时间复杂度为 O(klogk)，其中 k 是要求的数对个数\n空间复杂度 空间复杂度为 O(k)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; kSmallestPairs(vector\u0026lt;int\u0026gt;\u0026amp; nums1, vector\u0026lt;int\u0026gt;\u0026amp; nums2, int k) { std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储结果 int n = nums1.size(), m = nums2.size(); if (n == 0 || m == 0) // 如果数组为空，直接返回空结果 return res; // 使用优先队列（最小堆）存储 (sum, i, j) std::priority_queue\u0026lt;std::vector\u0026lt;int\u0026gt;, std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt;, std::greater\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt;\u0026gt; heap; // 将 nums1[0] 与 nums2 中的每个元素配对，推入堆中 for (int i = 0; i \u0026lt; m; ++i) heap.push({nums1[0] + nums2[i], 0, i}); // 从堆中取出和最小的 k 对 while (k-- \u0026amp;\u0026amp; heap.size()) { std::vector\u0026lt;int\u0026gt; t = heap.top(); heap.pop(); res.push_back({nums1[t[1]], nums2[t[2]]}); // 将结果加入 res // 如果 t[1]+1 \u0026lt; n，继续将 nums1[t[1]+1] 与 nums2[t[2]] 配对推入堆中 if (t[1] + 1 \u0026lt; n) heap.push({nums1[t[1] + 1] + nums2[t[2]], t[1] + 1, t[2]}); } return res; } }; ","date":"2025-02-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-k-pairs-with-smallest-sums/","title":"Find K Pairs with Smallest Sums"},{"content":"137. 只出现一次的数字II 分析 考虑整数在计算机中以 32 位二进制形式表示，可以逐位统计数组中每个位上的 1 的出现次数\n逐位遍历\n对于每一位 i（从 0 到 31 位），统计所有数字中第 i 位上的 1 的总数 c 模 3 运算\n若某个位上的 1 的出现次数不是 3 的倍数，则说明这个位在目标数中为 1 将该位加入结果 res 结果构建\n将每一位结果通过位运算 res |= (c % 3) \u0026lt;\u0026lt; i 累加到最终结果 res 中 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public: int singleNumber(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = 0; for (int i = 0; i \u0026lt; 32; ++i) // 遍历每一个二进制位 { int c = 0; // 统计当前二进制位上 1 的数量 for (int x : nums) c += (x \u0026gt;\u0026gt; i) \u0026amp; 1; // 若该二进制位的 1 数量不是 3 的倍数，将该位加入结果 res |= (c % 3) \u0026lt;\u0026lt; i; } return res; } }; ","date":"2025-02-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/single-number-ii/","title":"Single Number II"},{"content":"67. 二进制求和 分析 字符串逆序遍历 为了方便从最低位到最高位逐位相加，首先将 a 和 b 字符串进行反转 逐位相加 维护进位变量 t，依次对 a 和 b 的每一位进行相加： 若当前索引在 a 范围内，则加上 a[i] - '0' 若当前索引在 b 范围内，则加上 b[i] - '0' 将当前位结果 t % 2 转为字符加入结果字符串 c 更新进位 t /= 2 处理多余进位 当遍历完两个字符串后，若仍有进位，则继续加入结果字符串 结果反转 将结果字符串 c 反转回正序，即为最终答案 时间复杂度 时间复杂度 O(max(m, n))，其中 m 和 n 分别为字符串 a 和 b 的长度\n空间复杂度 空间复杂度为 O(max(m, n))\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: string addBinary(string a, string b) { // 反转字符串以方便从低位开始计算 std::reverse(a.begin(), a.end()); std::reverse(b.begin(), b.end()); std::string c; for (int i = 0, t = 0; i \u0026lt; a.size() || i \u0026lt; b.size() || t; ++i) { // 加上 a 和 b 当前位的数值 if (i \u0026lt; a.size()) t += a[i] - \u0026#39;0\u0026#39;; if (i \u0026lt; b.size()) t += b[i] - \u0026#39;0\u0026#39;; // 当前位结果加入结果字符串 c += t % 2 + \u0026#39;0\u0026#39;; // 更新进位 t /= 2; } // 反转回正序 std::reverse(c.begin(), c.end()); return c; } }; ","date":"2025-02-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/add-binary/","title":"Add Binary"},{"content":"918. 环形子数组的最大和 分析 双倍扩展数组\n为了处理环形数组，将 nums 扩展为两倍长度的数组 s（模拟拼接后的连续数组），便于通过滑动窗口寻找环形最大子数组 前缀和数组\ns[i] 表示从 nums[0] 到 nums[i % n] 的前缀和，通过前缀和计算任意区间的子数组和 滑动窗口 + 单调队列\n维护一个单调递增的双端队列 q，存储前缀和的索引： 队首：保证窗口长度不超过 n，且队首元素是当前窗口内的最小前缀和 队尾：维护递增性质 动态更新结果\n在每一步中计算 s[i] - s[q.front()]，更新最大子数组和 时间复杂度 总时间复杂度 O(n)，每个元素最多进出队列一次，遍历一遍即可得到结果\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public: int maxSubarraySumCircular(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int n = nums.size(); std::vector\u0026lt;int\u0026gt; s(n * 2); // 扩展两倍长度的前缀和数组 s[0] = nums[0]; // 构建前缀和数组 for (int i = 1; i \u0026lt; n * 2; ++i) s[i] = s[i - 1] + nums[i % n]; std::deque\u0026lt;int\u0026gt; q; // 单调队列存储前缀和的索引 q.push_front(0); int res = INT_MIN; for (int i = 1; i \u0026lt; n * 2; ++i) { // 窗口长度大于 n 时弹出队首 while (q.size() \u0026amp;\u0026amp; i - q.front() \u0026gt; n) q.pop_front(); // 更新最大子数组和 res = std::max(res, s[i] - s[q.front()]); // 维护单调递增的队列 while (q.size() \u0026amp;\u0026amp; s[i] \u0026lt;= s[q.back()]) q.pop_back(); q.push_back(i); } return res; } }; ","date":"2025-02-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximum-sum-circular-subarray/","title":"Maximum Sum Circular Subarray"},{"content":"77. 组合 分析 初始化与递归入口 定义 path 存储当前选择的组合，res 存储所有合法结果 调用 dfs(n, k, 1) 开始递归。 递归终止条件 若选满 k 个数（即 k == 0），将当前路径加入结果 res，并返回 递归过程 遍历从 start 到 n 的所有数： 将当前数加入 path 递归调用 dfs(n, k - 1, i + 1) 进入下一层搜索 回溯时弹出当前数字，恢复状态 时间复杂度 总时间复杂度 O(C_n^k)，组合数量为 C_n^k = n! / k!(n-k)!，每个组合需要遍历一次\n空间复杂度 空间复杂度为 O(k)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储结果 std::vector\u0026lt;int\u0026gt; path; // 当前路径 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; combine(int n, int k) { dfs(n, k, 1); // 从数字 1 开始递归 return res; } void dfs(int n, int k, int start) { // 递归终止条件：已选择 k 个数 if (!k) { res.push_back(path); return; } for (int i = start; i \u0026lt;= n; ++i) { path.push_back(i); // 选择当前数 dfs(n, k - 1, i + 1); // 递归搜索下一层 path.pop_back(); // 回溯，撤销选择 } } }; ","date":"2025-02-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/combinations/","title":"Combinations"},{"content":"211. 添加与搜索单词 分析 构建 Trie 树\nNode 节点中包含： 一个 son[26] 数组，用于存储 26 个字母子节点指针 一个 ended 布尔变量，标记当前节点是否为某单词的结尾 addWord() 操作： 从根节点出发，逐字符插入 Trie 中，不存在的节点创建新的子节点 插入完成后将末尾节点标记为单词结束 实现查找逻辑\nsearch() 方法调用 dfs() 深搜判断是否能匹配给定单词 DFS 搜索策略\n遇到普通字符：检查对应子节点是否存在，递归查找下一字符 遇到 .：遍历所有子节点，若任一分支匹配成功即可返回 true 若遍历结束时抵达有效单词结尾，则返回 true 时间复杂度 addWord(word)：O(L)，其中 L 是单词长度 search(word)： 普通查找：O(L) 含 . 的查找：最坏情况 O(26^L) 空间复杂度 O(N \\times L)，N 为单词数量，L 为单词平均长度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class WordDictionary { public: struct Node { Node() { ended = false; for (int i = 0; i \u0026lt; 26; ++i) son[i] = nullptr; } Node* son[26]; // 子节点数组 bool ended; // 标记单词结束 }; Node* root; WordDictionary() { root = new Node(); } // 插入单词 void addWord(string word) { Node* p = root; for (char c : word) { int u = c - \u0026#39;a\u0026#39;; if (!p-\u0026gt;son[u]) p-\u0026gt;son[u] = new Node(); p = p-\u0026gt;son[u]; } p-\u0026gt;ended = true; } // 查找单词 bool search(string word) { return dfs(root, word, 0); } // 深度优先搜索实现查找逻辑 bool dfs(Node* p, std::string word, int i) { if (i == word.size()) return p-\u0026gt;ended; // 抵达单词末尾，判断是否有效 if (word[i] != \u0026#39;.\u0026#39;) { int u = word[i] - \u0026#39;a\u0026#39;; if (p-\u0026gt;son[u]) return dfs(p-\u0026gt;son[u], word, i + 1); } else { for (int j = 0; j \u0026lt; 26; ++j) { if (p-\u0026gt;son[j] \u0026amp;\u0026amp; dfs(p-\u0026gt;son[j], word, i + 1)) return true; } } return false; } }; ","date":"2025-01-31T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/design-add-and-search-words-data-structure/","title":"Design Add and Search Words Data Structure"},{"content":"127. 单词接龙 分析 构建字典集合 将 wordList 存入哈希集合 S 中，方便快速判断单词是否存在 BFS 初始化 用 queue 队列维护当前单词，初始化队列并记录 beginWord 的距离为 0 用哈希表 dist 记录每个单词到 beginWord 的最短路径长度 BFS 遍历 每次从队列中取出当前单词 cur，尝试修改它的每一位字符 对每个字符位置，从 ‘a’ 到 ‘z’ 枚举替换，形成新单词 temp 如果 temp 在字典中且未被访问过： 更新 dist[temp] = dist[cur] + 1，并将其加入队列 如果 temp 等于 endWord，返回转换次数 dist[temp] + 1 终止条件 如果 BFS 遍历结束仍未找到 endWord，返回 0 时间复杂度 总时间复杂度 O(L * 26 * N)，L 是单词长度，26 是字母表大小，N 是字典中单词数量\n空间复杂度 空间复杂度为 O(N)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public: int ladderLength(string beginWord, string endWord, vector\u0026lt;string\u0026gt;\u0026amp; wordList) { // 存储字典单词集合 std::unordered_set\u0026lt;std::string\u0026gt; S; for (std::string word : wordList) S.insert(word); // 记录单词到起点的最短路径长度 std::unordered_map\u0026lt;std::string, int\u0026gt; dist; dist[beginWord] = 0; std::queue\u0026lt;std::string\u0026gt; q; q.push(beginWord); while (!q.empty()) { std::string cur = q.front(); q.pop(); // 枚举每个字符位置进行替换 for (int i = 0; i \u0026lt; cur.size(); ++i) { std::string temp = cur; for (char j = \u0026#39;a\u0026#39;; j \u0026lt;= \u0026#39;z\u0026#39;; ++j) { temp[i] = j; // 修改第 i 位字符 if (S.count(temp) \u0026amp;\u0026amp; !dist.count(temp)) { dist[temp] = dist[cur] + 1; if (temp == endWord) return dist[temp] + 1; // 返回结果 q.push(temp); } } } } return 0; // 无法转换 } }; ","date":"2025-01-31T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/word-ladder/","title":"Word Ladder"},{"content":"210. 课程表II 分析 建图与统计入度 使用邻接表表示课程之间的依赖关系 使用一个数组记录每门课程的入度（依赖它的课程数） 使用队列进行拓扑排序 将入度为 0 的课程加入队列，表示这些课程没有依赖，可以直接学习 依次从队列中取出课程，将其加入结果序列，并减少依赖它的课程的入度。如果某课程的入度变为 0，则将其加入队列 判断是否可行 如果完成拓扑排序后，结果序列的长度等于课程总数，说明可以完成所有课程；否则说明存在循环依赖，无法完成课程学习 时间复杂度 建图和统计入度需要 O(E)，其中 E 为依赖关系的数量 拓扑排序遍历每个节点及其边，总时间复杂度为 O(V + E)，其中 V 为课程数 空间复杂度 邻接表和入度数组的存储需要 O(V + E) 队列和结果数组的空间为 O(V) 总体复杂度为 O(V + E)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public: vector\u0026lt;int\u0026gt; findOrder(int numCourses, vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; prerequisites) { // 建图和入度数组 std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; g(numCourses); std::vector\u0026lt;int\u0026gt; d(numCourses); // 构建图并计算入度 for (std::vector\u0026lt;int\u0026gt;\u0026amp; pre : prerequisites) { g[pre[1]].push_back(pre[0]); ++d[pre[0]]; } // 将入度为 0 的课程加入队列 std::queue\u0026lt;int\u0026gt; q; for (int i = 0; i \u0026lt; numCourses; ++i) if (d[i] == 0) q.push(i); // 拓扑排序 std::vector\u0026lt;int\u0026gt; res; while (!q.empty()) { int i = q.front(); q.pop(); res.push_back(i); // 将当前课程加入结果序列 // 遍历当前课程的后继节点 for (int j : g[i]) { if (--d[j] == 0) // 如果入度减为 0，则加入队列 q.push(j); } } // 如果结果序列的长度等于课程数，则返回结果；否则返回空数组 return res.size() == numCourses ? res : std::vector\u0026lt;int\u0026gt;(); } }; ","date":"2025-01-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/course-schedule-ii/","title":"Course Schedule II"},{"content":"433. 最小基因变化 分析 数据准备 将基因库 bank 存入一个哈希集合 gene 中，方便快速判断某个基因序列是否有效 用哈希表 hash 记录每个基因序列的访问状态及其变化次数 初始化 BFS 将起始基因序列 startGene 入队，设初始变化次数为 0 BFS 遍历 每次从队列中取出当前基因序列 s，对其进行逐位变换。 枚举 8 个字符中的每一位，尝试将其替换为 ‘A’、‘C’、‘G’ 或 ‘T’ 对于每个生成的新基因序列 t，若它在基因库中且未被访问过 将其加入队列，并记录变化次数 hash[t] = hash[s] + 1 若 t == endGene，则直接返回变化次数 结束条件： 如果 BFS 遍历完队列仍未找到 endGene，则返回 -1 时间复杂度 每个基因序列有 8 个字符，每个字符有 4 种变换，最多产生 O(8 * 4 * n) 次尝试，其中 n 是基因库的大小 BFS 遍历每个基因序列一次，复杂度为 O(n) 总时间复杂度为 O(32n) ，即 O(n)\n空间复杂度 哈希集合 gene 和 hash 占用 O(n) 空间 队列最多存储 O(n) 个基因序列 总空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public: int minMutation(string startGene, string endGene, vector\u0026lt;string\u0026gt;\u0026amp; bank) { // 基因库存入哈希集合 std::unordered_set\u0026lt;std::string\u0026gt; gene; for (std::string\u0026amp; b : bank) gene.insert(b); // BFS 相关数据结构 std::unordered_map\u0026lt;std::string, int\u0026gt; hash; // 记录访问状态及变化次数 std::queue\u0026lt;std::string\u0026gt; q; hash[startGene] = 0; // 起始基因变化次数为 0 q.push(startGene); char ge_ch[4] = {\u0026#39;A\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;G\u0026#39;, \u0026#39;T\u0026#39;}; // 可用字符 while (!q.empty()) { std::string s = q.front(); q.pop(); // 对当前基因序列逐位尝试变换 for (int i = 0; i \u0026lt; s.size(); ++i) { std::string t = s; // 新的基因序列 for (char c : ge_ch) { t[i] = c; // 修改第 i 位 // 若新序列有效且未访问过 if (gene.count(t) \u0026amp;\u0026amp; hash.count(t) == 0) { hash[t] = hash[s] + 1; // 记录变化次数 if (t == endGene) // 找到目标序列 return hash[t]; q.push(t); // 入队继续搜索 } } } } return -1; // 无法到达目标序列 } }; ","date":"2025-01-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-genetic-mutation/","title":"Minimum Genetic Mutation"},{"content":"909. 蛇梯棋 分析 坐标与编号的映射 根据题目中的「转行交替方式」编号规则，从左下角开始生成一个 id 数组，表示棋盘位置与编号的对应关系 还需要记录每个编号对应的二维坐标 cord，便于查找棋盘上的位置 广度优先搜索（BFS） 用队列维护当前可以到达的位置 每次模拟掷骰子，从当前编号 k 出发，考虑 [k+1, \\min(k+6, n^2)] 范围内的目标编号 如果目标编号存在梯子或蛇（即 board[x][y] != -1），直接跳转到指定位置。 终止条件 如果当前编号是 n^2，直接返回当前步数 若队列为空仍未到达终点，返回 -1，表示无法到达终点 时间复杂度 构建编号与坐标映射需要 O(n^2) BFS 遍历每个节点，并检查最多 6 条边，复杂度为 O(n^2) 总时间复杂度 O(n^2)\n空间复杂度 存储坐标映射和队列所需空间均为 O(n^2) 总空间复杂度为 O(n^2)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class Solution { public: #define x first #define y second int snakesAndLadders(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; board) { int n = board.size(); // 记录编号与坐标的映射 std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; id(n, std::vector\u0026lt;int\u0026gt;(n)); using PII = std::pair\u0026lt;int, int\u0026gt;; std::vector\u0026lt;PII\u0026gt; cord(n * n + 1); // 生成棋盘编号与坐标的映射 int k = 1, direct = 0; for (int i = n - 1; i \u0026gt;= 0; --i) { if (direct % 2 == 0) { // 从左到右 for (int j = 0; j \u0026lt; n; ++j) { id[i][j] = k; cord[k] = {i, j}; ++k; } } else { // 从右到左 for (int j = n - 1; j \u0026gt;= 0; --j) { id[i][j] = k; cord[k] = {i, j}; ++k; } } ++ direct; } // BFS 寻找最短路径 std::queue\u0026lt;PII\u0026gt; q; std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; dist(n, std::vector\u0026lt;int\u0026gt;(n, 1e9)); q.push({n - 1, 0}); // 从起点开始 dist[n - 1][0] = 0; while (!q.empty()) { PII t = q.front(); q.pop(); int k = id[t.x][t.y]; // 当前编号 if (k == n * n) // 到达终点 return dist[t.x][t.y]; for (int i = k + 1; i \u0026lt;= k + 6 \u0026amp;\u0026amp; i \u0026lt;= n * n; ++i) { int x = cord[i].x, y = cord[i].y; if (board[x][y] == -1) { // 无蛇无梯子 if (dist[x][y] \u0026gt; dist[t.x][t.y] + 1) { dist[x][y] = dist[t.x][t.y] + 1; q.push({x, y}); } } else { // 存在蛇或梯子 int ladder = board[x][y]; x = cord[ladder].x, y = cord[ladder].y; if (dist[x][y] \u0026gt; dist[t.x][t.y] + 1) { dist[x][y] = dist[t.x][t.y] + 1; q.push({x, y}); } } } } return -1; // 无法到达终点 } }; ","date":"2025-01-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/snakes-and-ladders/","title":"Snakes And Ladders"},{"content":"133. 克隆图 分析 哈希表存储原节点与克隆节点的映射： 使用 hash 保存原图节点到克隆节点的映射关系，用于快速找到已克隆的节点，避免重复克隆 DFS 遍历图的所有节点： 对每个未访问过的节点，创建其克隆节点并存入 hash 遍历当前节点的所有邻居，如果邻居未被克隆，则递归处理 复制邻接关系： 遍历所有节点（已在 hash 中），将每个原节点的邻居对应的克隆节点添加到克隆节点的邻居列表中 最终返回起始节点的克隆版本，即为完整的深拷贝图 时间复杂度 图的所有节点和边都会被访问一次，因此时间复杂度为 O(N + E)，其中 N 是节点数，E 是边数\n空间复杂度 递归调用栈的深度取决于图的结构，最坏情况下为 O(N)，加上哈希表的存储空间，总体复杂度为 O(N)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: Node* cloneGraph(Node* node) { if (!node) return nullptr; std::unordered_map\u0026lt;Node*, Node*\u0026gt; hash; // 哈希表存储节点映射 dfs(node, hash); // 深度优先搜索创建克隆节点 // 第二次遍历复制邻接关系 for (auto\u0026amp; [origin, copy] : hash) for (Node* neighbor : origin-\u0026gt;neighbors) copy-\u0026gt;neighbors.push_back(hash[neighbor]); // 将原节点的邻接点的克隆节点添加到克隆节点的邻接表中 return hash[node]; // 返回克隆图的起点 } void dfs(Node* node, std::unordered_map\u0026lt;Node*, Node*\u0026gt;\u0026amp; hash) { // 克隆当前节点 hash[node] = new Node(node-\u0026gt;val); // 递归克隆邻居节点 for (Node* neighbor : node-\u0026gt;neighbors) if (hash.count(neighbor) == 0) // 如果邻居未被克隆 dfs(neighbor, hash); // 递归处理 } }; ","date":"2025-01-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/clone-graph/","title":"Clone Graph"},{"content":"530. 二叉搜索树的最小绝对差 分析 中序遍历： 按左子树 → 根节点 → 右子树的顺序遍历树，使得节点值按从小到大的顺序访问 计算最小差值： 在遍历过程中，记录上一个节点值 last 对当前节点值 root-\u0026gt;val 和 last 计算差值，并更新最小差值 res 初始化： 使用一个布尔变量 firsted 标记是否是第一次访问节点，用于跳过第一个节点的差值计算 返回结果： 中序遍历完成后，res 即为任意两节点值之间的最小差值 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(logn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: int res = INT_MAX; // 最小差值的初始化 int last; // 上一个访问的节点值 bool firsted = true; // 是否是第一个访问的节点 int getMinimumDifference(TreeNode* root) { dfs(root); // 中序遍历 return res; } void dfs(TreeNode* root) { if (!root) return; // 遍历左子树 dfs(root-\u0026gt;left); // 访问当前节点 if (firsted) firsted = false; // 标记第一个节点 else res = std::min(res, root-\u0026gt;val - last); // 更新最小差值 last = root-\u0026gt;val; // 更新上一个节点值 // 遍历右子树 dfs(root-\u0026gt;right); } }; ","date":"2025-01-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-absolute-difference-in-bst/","title":"Minimum Absolute Difference In BST"},{"content":"130. 被围绕的区域 分析 标记无法被包围的区域： 从矩阵的边界出发，对边界的 \u0026lsquo;O\u0026rsquo; 及其连通区域用 \u0026lsquo;*\u0026rsquo; 标记，表示这些 \u0026lsquo;O\u0026rsquo; 无法被包围。 填充被围绕的区域： 遍历矩阵，将未被标记的 \u0026lsquo;O\u0026rsquo; 替换为 \u0026lsquo;X\u0026rsquo;，表示这些区域已被包围。 还原未被包围的区域： 将标记为 \u0026lsquo;*\u0026rsquo; 的位置还原为 \u0026lsquo;O\u0026rsquo;。 时间复杂度 总时间复杂度 O(n * m)\n空间复杂度 DFS 的递归调用栈深度与矩阵中最长的连通区域有关，空间复杂度为 O(n * m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Solution { public: int n = 0, m = 0; // 矩阵的行数和列数 int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; // 四个方向：右、下、左、上 void solve(vector\u0026lt;vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; board) { n = board.size(), m = board[0].size(); if (n == 0 || m == 0) return; // 从边界出发，标记无法被包围的区域 for (int i = 0; i \u0026lt; n; ++i) { if (board[i][0] == \u0026#39;O\u0026#39;) dfs(board, i, 0); // 左边界 if (board[i][m - 1] == \u0026#39;O\u0026#39;) dfs(board, i, m - 1); // 右边界 } for (int i = 0; i \u0026lt; m; ++i) { if (board[0][i] == \u0026#39;O\u0026#39;) dfs(board, 0, i); // 上边界 if (board[n - 1][i] == \u0026#39;O\u0026#39;) dfs(board, n - 1, i); // 下边界 } // 遍历矩阵，处理未被标记的区域 for (int i = 0; i \u0026lt; n; ++i) { for (int j = 0; j \u0026lt; m; ++j) { if (board[i][j] == \u0026#39;*\u0026#39;) board[i][j] = \u0026#39;O\u0026#39;; // 还原被标记的区域 else board[i][j] = \u0026#39;X\u0026#39;; // 填充被包围的区域 } } } void dfs(std::vector\u0026lt;std::vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; board, int x, int y) { board[x][y] = \u0026#39;*\u0026#39;; // 标记当前区域 for (int i = 0; i \u0026lt; 4; ++i) { int a = x + dx[i], b = y + dy[i]; // 计算相邻位置 if (a \u0026gt;= 0 \u0026amp;\u0026amp; a \u0026lt; n \u0026amp;\u0026amp; b \u0026gt;= 0 \u0026amp;\u0026amp; b \u0026lt; m \u0026amp;\u0026amp; board[a][b] == \u0026#39;O\u0026#39;) dfs(board, a, b); // 递归标记连通区域 } } }; ","date":"2025-01-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/surrounded-regions/","title":"Surrounded Regions"},{"content":"222. 完全二叉树的节点个数 分析 递归终止条件：如果当前节点为空，则返回 0，表示没有节点 递归步骤：对当前节点，递归计算其左子树和右子树的节点个数，然后返回左右子树节点数的和加上当前节点本身 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(logn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public: int countNodes(TreeNode* root) { // 如果树为空，节点个数为 0 if (!root) return 0; // 递归计算左子树和右子树的节点个数，并加上当前节点 return countNodes(root-\u0026gt;left) + countNodes(root-\u0026gt;right) + 1; } }; ","date":"2025-01-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/count-complete-tree-nodes/","title":"Count Complete Tree Nodes"},{"content":"129. 求根节点到叶节点数字之和 分析 递归遍历二叉树：使用深度优先搜索遍历树，从根节点开始，到达每个叶节点时，记录下从根到当前节点的路径形成的数字 路径数字的累加：在遍历过程中，将当前节点的值添加到路径上，形成当前路径的数字。在到达叶节点时，将该数字加入总和 返回结果：最终返回计算得到的所有路径之和 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(h)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: int sumNumbers(TreeNode* root) { int res = 0; // 如果根节点非空，开始递归 if (root) dfs(root, res, 0); return res; } void dfs(TreeNode* root, int\u0026amp; res, int cur) { // 将当前节点的值加到当前路径的数字中 cur = cur * 10 + root-\u0026gt;val; // 如果当前节点是叶节点，将路径数字加到结果中 if (!root-\u0026gt;left \u0026amp;\u0026amp; !root-\u0026gt;right) res += cur; // 继续递归遍历左子树 if (root-\u0026gt;left) dfs(root-\u0026gt;left, res, cur); // 继续递归遍历右子树 if (root-\u0026gt;right) dfs(root-\u0026gt;right, res, cur); } }; ","date":"2025-01-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/sum-root-to-leaf-numbers/","title":"Sum Root To Leaf Numbers"},{"content":"106. 从中序和后序遍历序列构造二叉树 分析 构建哈希表： 遍历 inorder 数组，用哈希表记录每个节点值的位置。 这样可以快速查找某个节点在中序遍历中的索引。 递归划分左右子树： 后序遍历的最后一个元素是根节点。 在中序遍历中找到根节点的位置，从而确定左子树和右子树的范围。 递归构造树： 通过子数组的范围递归构造左右子树。 递归的边界条件是子数组范围为空。 返回结果： 最终返回根节点，即整棵树的构造结果 时间复杂度 构造哈希表需要 O(n) 每次递归通过哈希表查找根节点位置是 O(1)，递归调用总次数为 n，因此递归部分的时间复杂度为 O(n) 总时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: std::unordered_map\u0026lt;int, int\u0026gt; hash; // 记录中序遍历中每个节点值的位置 TreeNode* buildTree(vector\u0026lt;int\u0026gt;\u0026amp; inorder, vector\u0026lt;int\u0026gt;\u0026amp; postorder) { // 构建哈希表，快速查找中序遍历中每个节点的位置 for (int i = 0; i \u0026lt; inorder.size(); ++i) hash[inorder[i]] = i; // 调用递归函数构造二叉树 return build(inorder, postorder, 0, inorder.size() - 1, 0, postorder.size() - 1); } TreeNode* build(std::vector\u0026lt;int\u0026gt;\u0026amp; inorder, std::vector\u0026lt;int\u0026gt;\u0026amp; postorder, int il, int ir, int pl, int pr) { // 如果子树范围为空，返回空节点 if (il \u0026gt; ir) return nullptr; // 获取当前子树的根节点 TreeNode* root = new TreeNode(postorder[pr]); int mid = hash[root-\u0026gt;val]; // 在中序遍历中找到根节点的位置 // 递归构造左子树 root-\u0026gt;left = build(inorder, postorder, il, mid - 1, pl, pl + mid - 1 - il); // 递归构造右子树 root-\u0026gt;right = build(inorder, postorder, mid + 1, ir, pl + mid - 1 - il + 1, pr - 1); return root; } }; ","date":"2025-01-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/construct-binary-tree-from-inorder-and-postorder-traversal/","title":"Construct Binary Tree From Inorder And Postorder Traversal"},{"content":"116. 填充每个节点的下一个右侧节点指针 分析 虚拟头节点 在每一层，用一个虚拟的头节点 head 来连接下一层的节点。head 节点的 next 指针指向当前层已连接的节点，以便后续填充下一层的 next 指针 遍历当前层的节点 对于当前层的每一个节点，我们依次处理它的左右子节点。如果左子节点存在，将其通过 next 指针连接到当前层的 tail 上；然后更新 tail。同样地，处理右子节点 更新指针指向下一层 完成当前层的处理后，更新 cur 指向下一层的第一个节点（即虚拟头节点 head 的 next 指针）。然后继续处理下一层，直到所有层都被处理 返回结果 最后返回处理后的根节点 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: Node* connect(Node* root) { Node* cur = root; // 当前层存在节点时，继续遍历 while (cur) { // 创建一个虚拟头节点，用于连接下一层的节点 Node* head = new Node(-1); Node* tail = head; // tail 用于指向当前层已连接的最后一个节点 // 遍历当前层的所有节点 for (Node* p = cur; p; p = p-\u0026gt;next) { // 如果左子节点存在，将其连接到当前层的 tail if (p-\u0026gt;left) tail = tail-\u0026gt;next = p-\u0026gt;left; // 如果右子节点存在，将其连接到当前层的 tail if (p-\u0026gt;right) tail = tail-\u0026gt;next = p-\u0026gt;right; } // 当前层遍历完成后，更新 cur 指向下一层的第一个节点 cur = head-\u0026gt;next; // head-\u0026gt;next 是下一层的第一个节点 } return root; // 返回处理后的根节点 } }; ","date":"2025-01-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/populating-next-right-pointers-in-each-node/","title":"Populating Next Right Pointers In Each Node"},{"content":"117. 填充每个节点的下一个右侧节点指针II 分析 虚拟头节点 在每一层，用一个虚拟的头节点 head 来连接下一层的节点。head 节点的 next 指针指向当前层已连接的节点，以便后续填充下一层的 next 指针 遍历当前层的节点 对于当前层的每一个节点，我们依次处理它的左右子节点。如果左子节点存在，将其通过 next 指针连接到当前层的 tail 上；然后更新 tail。同样地，处理右子节点 更新指针指向下一层 完成当前层的处理后，更新 cur 指向下一层的第一个节点（即虚拟头节点 head 的 next 指针）。然后继续处理下一层，直到所有层都被处理 返回结果 最后返回处理后的根节点 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: Node* connect(Node* root) { Node* cur = root; // 当前层存在节点时，继续遍历 while (cur) { // 创建一个虚拟头节点，用于连接下一层的节点 Node* head = new Node(-1); Node* tail = head; // tail 用于指向当前层已连接的最后一个节点 // 遍历当前层的所有节点 for (Node* p = cur; p; p = p-\u0026gt;next) { // 如果左子节点存在，将其连接到当前层的 tail if (p-\u0026gt;left) tail = tail-\u0026gt;next = p-\u0026gt;left; // 如果右子节点存在，将其连接到当前层的 tail if (p-\u0026gt;right) tail = tail-\u0026gt;next = p-\u0026gt;right; } // 当前层遍历完成后，更新 cur 指向下一层的第一个节点 cur = head-\u0026gt;next; // head-\u0026gt;next 是下一层的第一个节点 } return root; // 返回处理后的根节点 } }; ","date":"2025-01-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/populating-next-right-pointers-in-each-node-ii/","title":"Populating Next Right Pointers In Each Node II"},{"content":"100. 相同的树 分析 递归判断根节点：首先检查两个根节点 p 和 q 的值是否相等，如果它们相等，就继续递归地检查它们的左右子树 递归判断左右子树：分别递归比较 p 的左子树与 q 的左子树，p 的右子树与 q 的右子树 递归的终止条件： 如果两个节点都为空，说明它们相等 如果两个节点其中一个为空，或者它们的值不相等，返回 false 最终结果：只有当根节点和左右子树都完全相同的情况下，两棵树才是相同的 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(h)，h 是树的高度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public: bool isSameTree(TreeNode* p, TreeNode* q) { // 如果 p 和 q 都为空，说明它们是相同的 if (!p \u0026amp;\u0026amp; !q) return true; // 如果 p 和 q 其中一个为空，或它们的值不同，返回 false if (!p || !q || p-\u0026gt;val != q-\u0026gt;val) return false; // 递归地比较左右子树 return isSameTree(p-\u0026gt;left, q-\u0026gt;left) \u0026amp;\u0026amp; isSameTree(p-\u0026gt;right, q-\u0026gt;right); } }; ","date":"2025-01-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/same-tree/","title":"Same Tree"},{"content":"224. 基本计数器 分析 栈处理运算：使用两个栈： nums 栈：存储操作数 op 栈：存储操作符 遍历字符串： 如果是数字，将数字读完整并压入 nums 栈 如果是操作符，比较当前操作符与 op 栈顶操作符的优先级： 如果栈顶优先级更高或相同，执行计算并将结果压入 nums 栈 否则，将当前操作符压入 op 栈 如果是左括号 (，直接压入 op 栈 如果是右括号 )，弹出并计算直到遇到左括号 清算剩余操作：遍历结束后，计算栈中剩余的操作符，最终返回结果 时间复杂度 遍历字符串 s 的每个字符：O(n) 每个操作符最多计算一次：O(n) 总时间复杂度为 O(n)\n空间复杂度 使用了两个栈 nums 和 op，最坏情况下同时存储 n 个字符（例如全为数字或括号），空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Solution { public: std::stack\u0026lt;int\u0026gt; nums; // 存储操作数 std::stack\u0026lt;char\u0026gt; op; // 存储操作符 // 执行栈顶一次运算 void eval() { int b = nums.top(); nums.pop(); int a = nums.top(); nums.pop(); char c = op.top(); op.pop(); if (c == \u0026#39;+\u0026#39;) nums.push(a + b); else if (c == \u0026#39;-\u0026#39;) nums.push(a - b); else if (c == \u0026#39;*\u0026#39;) nums.push(a * b); else nums.push(a / b); } // 字符串替换函数 void replace(std::string\u0026amp; s, std::string src, std::string dest) { int pos = s.find(src), n = src.size(); while (pos != std::string::npos) { s.replace(pos, n, dest); pos = s.find(src); } } // 主函数 int calculate(string s) { // 去掉空格，处理括号中的正负号 replace(s, \u0026#34; \u0026#34;, \u0026#34;\u0026#34;); replace(s, \u0026#34;(+\u0026#34;, \u0026#34;(0+\u0026#34;); replace(s, \u0026#34;(-\u0026#34;, \u0026#34;(0-\u0026#34;); nums.push(0); // 初始化 nums 栈，避免边界条件 // 定义操作符优先级 std::unordered_map\u0026lt;char, int\u0026gt; priority_op{{\u0026#39;-\u0026#39;, 1}, {\u0026#39;+\u0026#39;, 1}, {\u0026#39;/\u0026#39;, 2}, {\u0026#39;*\u0026#39;, 2}}; // 遍历表达式 for (int i = 0; i \u0026lt; s.size(); ++i) { if (isdigit(s[i])) { // 处理数字 int num = 0, j = i; while (j \u0026lt; s.size() \u0026amp;\u0026amp; isdigit(s[j])) num = num * 10 + (s[j++] - \u0026#39;0\u0026#39;); nums.push(num); // 数字入栈 i = j - 1; // 更新索引 } else if (s[i] == \u0026#39;(\u0026#39;) { // 左括号直接入栈 op.push(s[i]); } else if (s[i] == \u0026#39;)\u0026#39;) { // 右括号 while (op.size() \u0026amp;\u0026amp; op.top() != \u0026#39;(\u0026#39;) eval(); // 计算括号内表达式 op.pop(); // 弹出左括号 } else { // 遇到操作符 while (op.size() \u0026amp;\u0026amp; op.top() != \u0026#39;(\u0026#39; \u0026amp;\u0026amp; priority_op[op.top()] \u0026gt;= priority_op[s[i]]) eval(); // 优先级高于当前操作符时，计算 op.push(s[i]); // 当前操作符入栈 } } // 清算剩余操作 while (op.size()) eval(); return nums.top(); } }; ","date":"2025-01-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/basic-calculator/","title":"Basic Calculator"},{"content":"147. 对链表进行插入排序 分析 使用一个虚拟头节点 dummy 来方便构建有序链表 遍历原链表： 对于当前节点 p，从虚拟头节点 dummy 开始查找它应插入的位置 插入后调整指针以确保链表仍然连接正确 继续处理下一节点，直到原链表遍历结束 返回排序后的链表 时间复杂度 外层遍历链表，每个节点插入有序链表的过程中需查找插入位置。 最坏情况下（链表反序），插入每个节点需要遍历整个有序链表，时间复杂度为 O(n^2)，其中 n 是链表的长度 空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: ListNode* insertionSortList(ListNode* head) { // 创建虚拟头节点 ListNode* dummy = new ListNode(-1); ListNode* p = head; // 遍历原链表 while (p) { // 找到插入点：从 dummy 开始找到第一个大于当前节点值的位置 ListNode* cur = dummy; while (cur-\u0026gt;next \u0026amp;\u0026amp; cur-\u0026gt;next-\u0026gt;val \u0026lt;= p-\u0026gt;val) cur = cur-\u0026gt;next; // 插入当前节点到有序链表 ListNode* next = p-\u0026gt;next; // 保存下一个节点 p-\u0026gt;next = cur-\u0026gt;next; // 当前节点指向插入位置后的节点 cur-\u0026gt;next = p; // 插入当前节点到有序链表 p = next; // 继续处理下一个节点 } // 返回排序后的链表 return dummy-\u0026gt;next; } }; ","date":"2025-01-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/insertion-sort-list/","title":"Insertion Sort List"},{"content":"86. 分隔链表 分析 使用两个虚拟头节点 small_head 和 big_head 分别构建两条子链表：\n遍历原链表，对于每个节点： 如果值小于 x，将该节点添加到以 small_head 为头的链表 如果值大于或等于 x，将该节点添加到以 big_head 为头的链表 遍历结束后，将小链表的尾部连接到大链表的头部 返回合并后的链表（从 small_head-\u0026gt;next 开始） 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public: ListNode* partition(ListNode* head, int x) { // 初始化两个虚拟头节点和尾指针 auto small_head = new ListNode(-1), big_head = new ListNode(-1); auto small_tail = small_head, big_tail = big_head; // 遍历链表，分隔节点 for (auto p = head; p; p = p-\u0026gt;next) { if (p-\u0026gt;val \u0026lt; x) small_tail = small_tail-\u0026gt;next = p; // 添加到小链表 else big_tail = big_tail-\u0026gt;next = p; // 添加到大链表 } // 合并两部分链表 small_tail-\u0026gt;next = big_head-\u0026gt;next; big_tail-\u0026gt;next = NULL; // 确保大链表尾部没有残留节点 return small_head-\u0026gt;next; // 返回分隔后的链表 } }; ","date":"2025-01-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/partition-list/","title":"Partition List"},{"content":"150. 逆波兰表达式求值 分析 遍历 tokens 数组，对于每个元素：\n如果是数字，将其转换为整数并压入栈中 如果是运算符，弹出栈顶的两个数字进行运算，并将结果压回栈中 最后，栈中会留下一个元素，这个元素即为整个表达式的结果\n时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: int evalRPN(vector\u0026lt;string\u0026gt;\u0026amp; tokens) { std::stack\u0026lt;int\u0026gt; stk; // 用来存储操作数 for (std::string\u0026amp; token : tokens) { // 如果是运算符，弹出两个元素进行运算 if (token == \u0026#34;+\u0026#34; || token == \u0026#34;-\u0026#34; || token == \u0026#34;/\u0026#34; || token == \u0026#34;*\u0026#34;) { int b = stk.top(); // 弹出栈顶元素作为右操作数 stk.pop(); int a = stk.top(); // 弹出新的栈顶元素作为左操作数 stk.pop(); // 进行相应的运算，并将结果压回栈中 if (token == \u0026#34;+\u0026#34;) stk.push(a + b); else if (token == \u0026#34;-\u0026#34;) stk.push(a - b); else if (token == \u0026#34;/\u0026#34;) stk.push(a / b); else stk.push(a * b); } else // 如果是数字，直接将其转换为整数压入栈中 { stk.push(stoi(token)); } } return stk.top(); // 返回栈中的唯一元素，即计算结果 } }; ","date":"2025-01-18T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/evaluate-reverse-polish-notation/","title":"Evaluate Reverse Polish Notation"},{"content":"57. 插入区间 分析 遍历并处理不重叠区间（在 newInterval 左侧）：\n将所有结束小于 newInterval 开始的区间直接加入结果集 合并重叠区间（与 newInterval 重叠）：\n从当前位置开始，合并所有与 newInterval 有重叠的区间。合并时更新 newInterval 的左右边界： 左边界取两者最小值 l = min(l, intervals[k][0]) 右边界取两者最大值 r = max(r, intervals[k][1]) 插入合并后的新区间：\n将合并后的新区间 [l, r] 插入结果集。 添加剩余区间（在 newInterval 右侧）：\n将剩下的、与 newInterval 不重叠的区间加入结果集。 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; insert(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; intervals, vector\u0026lt;int\u0026gt;\u0026amp; newInterval) { std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; int k = 0, n = intervals.size(); int l = newInterval[0], r = newInterval[1]; // 1. 将 newInterval 左侧不重叠的区间加入结果 while (k \u0026lt; n \u0026amp;\u0026amp; intervals[k][1] \u0026lt; l) { res.push_back(intervals[k]); ++k; } // 2. 合并所有与 newInterval 重叠的区间 if (k \u0026lt; n) { l = std::min(l, intervals[k][0]); // 更新合并区间的左边界 while (k \u0026lt; n \u0026amp;\u0026amp; intervals[k][0] \u0026lt;= r) { r = std::max(r, intervals[k][1]); // 更新合并区间的右边界 ++k; } } res.push_back({l, r}); // 3. 插入合并后的区间 // 4. 将 newInterval 右侧不重叠的区间加入结果 while (k \u0026lt; n) { res.push_back(intervals[k]); ++k; } return res; } }; ","date":"2025-01-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/insert-interval/","title":"Insert Interval"},{"content":"71. 简化路径 分析 追加斜杠便于分割：\n为避免手动处理路径末尾的文件夹，先在路径末尾添加一个 '/'。这样每个目录（或命令）都会被完整处理 逐字符遍历构建当前目录：\n遇到非 '/' 字符时，累积到 cur，表示当前目录名 遇到 '/' 时，处理当前累积的 cur： \u0026quot;..\u0026quot;：回退到上一级目录 \u0026quot;.\u0026quot; 或空字符串：忽略 普通目录名：追加到结果路径 res 处理返回结果：\n如果结果字符串 res 为空，返回根目录 '/'；否则返回简化后的路径 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public: string simplifyPath(string path) { std::string res, cur; // 1. 确保末尾有 \u0026#39;/\u0026#39;，方便处理最后一个目录 if (path.back() != \u0026#39;/\u0026#39;) path += \u0026#39;/\u0026#39;; // 2. 遍历路径 for (char c : path) { if (c != \u0026#39;/\u0026#39;) // 累积目录名 cur += c; else // 遇到 \u0026#39;/\u0026#39;，处理当前目录 { if (cur == \u0026#34;..\u0026#34;) // 返回上一级目录 { // 删除上一级目录 while (!res.empty() \u0026amp;\u0026amp; res.back() != \u0026#39;/\u0026#39;) res.pop_back(); // 删除目录名 if (!res.empty()) res.pop_back(); // 删除 \u0026#39;/\u0026#39; } else if (cur != \u0026#34;.\u0026#34; \u0026amp;\u0026amp; !cur.empty()) // 忽略 \u0026#34;.\u0026#34; 和空字符串 { res += \u0026#34;/\u0026#34; + cur; // 加入新目录 } cur.clear(); // 重置当前目录 } } // 3. 返回结果 return res.empty() ? \u0026#34;/\u0026#34; : res; } }; ","date":"2025-01-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/simplify-path/","title":"Simplify Path"},{"content":"228. 汇总区间 分析 遍历数组：用双指针 i 和 j 遍历数组，识别连续递增的数字区间\n扩展区间：如果当前数字和前一个数字连续 nums[j] == nums[j-1] + 1，继续扩大区间\n记录区间：\n如果区间内只有一个数字 j == i + 1，直接记录该数字 如果区间内有多个数字 j \u0026gt; i + 1，记录区间 \u0026ldquo;起始值-\u0026gt;结束值\u0026rdquo; 跳过已处理的区间：将 i 更新为区间的最后一个元素 i = j - 1，继续查找下一个区间\n时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public: vector\u0026lt;string\u0026gt; summaryRanges(vector\u0026lt;int\u0026gt;\u0026amp; nums) { std::vector\u0026lt;std::string\u0026gt; res; // 存储结果 int n = nums.size(); for (int i = 0; i \u0026lt; n; ++ i) { int j = i + 1; // 扩展区间，找到连续的数字 while (j \u0026lt; n \u0026amp;\u0026amp; nums[j] == nums[j - 1] + 1) ++ j; std::ostringstream oss; // 用于拼接字符串 if (j == i + 1) // 区间内只有一个数字 oss \u0026lt;\u0026lt; nums[i]; else // 区间内有多个连续数字 oss \u0026lt;\u0026lt; nums[i] \u0026lt;\u0026lt; \u0026#34;-\u0026gt;\u0026#34; \u0026lt;\u0026lt; nums[j - 1]; res.push_back(oss.str()); // 将区间结果加入结果集 i = j - 1; // 跳过已处理的区间 } return res; } }; ","date":"2025-01-17T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/summary-ranges/","title":"Summary Ranges"},{"content":"219. 存在重复元素II 分析 哈希表记录元素索引\n用一个哈希表来记录每个元素上一次出现的索引。 遍历数组并检查条件\n每次遍历到当前元素时，检查该元素是否已经存在于哈希表中： 如果存在，比较当前索引和之前索引的差值是否小于等于 k，满足则返回 true 如果不存在，或差值大于 k，则更新该元素的索引为当前索引 遍历结束仍未找到符合条件的元素，则返回 false\n时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: bool containsNearbyDuplicate(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { std::unordered_map\u0026lt;int, int\u0026gt; hash; // 用于存储元素及其最后一次出现的索引 for (int i = 0; i \u0026lt; nums.size(); ++ i) { // 如果当前元素之前出现过，且索引之差小于等于 k if (hash.count(nums[i]) \u0026amp;\u0026amp; i - hash[nums[i]] \u0026lt;= k) return true; // 满足条件，直接返回 true hash[nums[i]] = i; // 更新当前元素的索引 } return false; // 遍历结束未找到符合条件的元素，返回 false } }; ","date":"2025-01-16T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/contains-duplicate/","title":"Contains Duplicate"},{"content":"202. 快乐数 分析 计算每位数字的平方和 创建一个函数 happy(n)，计算整数 n 每位数字的平方和 检测循环 使用哈希集合记录每次计算的结果，防止无限循环 如果平方和结果 重复出现，说明进入了死循环，返回 false 如果平方和结果变成 1，返回 true 时间复杂度 时间复杂度 O(logn)\n空间复杂度 空间复杂度为 O(logn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: // 辅助函数：计算一个数的每位数字的平方和 int happy(int n) { int sum = 0; while (n) { int t = n % 10; // 取出最低位数字 sum += t * t; // 平方后累加 n /= 10; // 去掉最低位 } return sum; } // 主函数：判断是否为快乐数 bool isHappy(int n) { std::unordered_set\u0026lt;int\u0026gt; hash; // 记录已出现的平方和结果 while (n != 1) { n = happy(n); // 计算平方和 if (hash.count(n)) // 如果结果重复出现，说明进入循环 return false; hash.insert(n); // 记录当前结果 } return true; // 最终变成 1，返回 true } }; ","date":"2025-01-16T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/happy-number/","title":"Happy Number"},{"content":"242. 有效的字母异位词 分析 使用哈希表统计字符频次 分别遍历 s 和 t，统计每个字符的出现次数 使用两个哈希表 hashS 和 hashT 存储字符频次 比较两个哈希表是否相等 如果两个哈希表相等，说明两个字符串是字母异位词 如果不相等，说明字符种类或字符次数不同，返回 false 时间复杂度：O(n) 遍历两个字符串各一次 空间复杂度：O(n) 使用哈希表存储字符频次 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: bool isAnagram(string s, string t) { std::unordered_map\u0026lt;char, int\u0026gt; hashS, hashT; // 1. 统计字符串 s 的字符频次 for (char c : s) ++hashS[c]; // 2. 统计字符串 t 的字符频次 for (char c : t) ++hashT[c]; // 3. 比较两个哈希表是否相等 return hashS == hashT; } }; ","date":"2025-01-16T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/valid-anagram/","title":"Valid Anagram"},{"content":"205. 同构字符串 分析 双向映射： 用两个哈希表建立双向映射关系。 st：记录从 s 到 t 的映射 ts：记录从 t 到 s 的映射 逐字符映射检查： 遍历字符串，检查每一对字符是否满足映射规则： 如果 s[i] 已经映射到某个字符，但不是 t[i]，则返回 false 如果 t[i] 已经映射到某个字符，但不是 s[i]，则返回 false 合法映射： 如果遍历结束都没有冲突，返回 true 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: bool isIsomorphic(string s, string t) { // 1. 创建两个哈希表：s -\u0026gt; t 和 t -\u0026gt; s std::unordered_map\u0026lt;char, char\u0026gt; st, ts; // 2. 遍历字符串 for (int i = 0; i \u0026lt; s.size(); ++i) { char a = s[i], b = t[i]; // 3. 检查 s -\u0026gt; t 的映射关系是否冲突 if (st.count(a) \u0026amp;\u0026amp; st[a] != b) return false; // 4. 建立 s -\u0026gt; t 的映射 st[a] = b; // 5. 检查 t -\u0026gt; s 的映射关系是否冲突 if (ts.count(b) \u0026amp;\u0026amp; ts[b] != a) return false; // 6. 建立 t -\u0026gt; s 的映射 ts[b] = a; } // 7. 映射无冲突，返回 true return true; } }; ","date":"2025-01-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/isomorphic-strings/","title":"Isomorphic Strings"},{"content":"383. 赎金信 分析 构建字符频率表\n遍历 magazine，统计每个字符出现的次数。 逐字符匹配\n如果 hash[c] \u0026gt; 0，说明字符可用，使用后数量减 1 如果 hash[c] == 0，说明字符不足，返回 false 全部匹配成功\n如果遍历完 ransomNote 没有提前返回 false，则说明可以成功构造，返回 true 时间复杂度 时间复杂度 O(n + m)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: bool canConstruct(string ransomNote, string magazine) { // 1. 统计 magazine 中字符的频率 std::unordered_map\u0026lt;char, int\u0026gt; hash; for (char c : magazine) ++ hash[c]; // 每个字符的出现次数 // 2. 遍历 ransomNote，检查字符是否足够 for (char c : ransomNote) { if (hash[c] \u0026gt; 0) --hash[c]; // 使用一个字符，数量减少 else return false; // 不足时，直接返回 false } // 3. 所有字符都有足够数量，返回 true return true; } }; ","date":"2025-01-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/ransom-note/","title":"Ransom Note"},{"content":"290. 单词规律 分析 分割字符串：\n将字符串 s 按空格分割成单词列表 words 长度判断：\n如果 pattern 的长度 ≠ words 的数量，直接返回 false 建立双向映射：\n使用两个哈希表： ps：pattern 的字符 → s 的单词 sp：s 的单词 → pattern 的字符 遍历检查：\n遍历 pattern 和 words： 如果 ps 中字符的映射与当前单词不一致，返回 false 如果 sp 中单词的映射与当前字符不一致，返回 false 否则建立新的映射关系。 全部检查通过，返回 true\n时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public: bool wordPattern(string pattern, string s) { std::vector\u0026lt;std::string\u0026gt; words; // 1. 将 s 按空格分割成单词 for (int i = 0; i \u0026lt; s.size(); ++i) { int j = i + 1; while (j \u0026lt; s.size() \u0026amp;\u0026amp; s[j] != \u0026#39; \u0026#39;) // 找到单词的结束位置 ++ j; words.push_back(s.substr(i, j - i)); // 提取单词 i = j; // 移动到下一个单词 } // 2. 长度不一致，直接返回 false if (pattern.size() != words.size()) return false; // 3. 建立双向映射 std::unordered_map\u0026lt;char, std::string\u0026gt; ps; // pattern → word std::unordered_map\u0026lt;std::string, char\u0026gt; sp; // word → pattern // 4. 遍历 pattern 和 words for (int i = 0; i \u0026lt; pattern.size(); ++i) { char a = pattern[i]; std::string b = words[i]; // 5. 检查 pattern → word 映射 if (ps.count(a) \u0026amp;\u0026amp; ps[a] != b) return false; ps[a] = b; // 6. 检查 word → pattern 映射 if (sp.count(b) \u0026amp;\u0026amp; sp[b] != a) return false; sp[b] = a; } // 7. 映射关系正确，返回 true return true; } }; ","date":"2025-01-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/word-pattern/","title":"Word Pattern"},{"content":"289. 生命游戏 分析 原地更新（不额外开辟空间）：\n直接在原矩阵 board 上更新，但在更新过程中要避免影响到邻居细胞的判断 状态编码（利用位运算存储两种状态）：\n最低位（第 0 位）：当前细胞的状态（0 = 死，1 = 活） 次低位（第 1 位）：细胞的下一状态（0 = 死，1 = 活） 计算邻居活细胞数：\n邻居遍历（避免越界） x ∈ [max(0, i - 1), min(i + 1, n - 1)] y ∈ [max(0, j - 1), min(j + 1, m - 1)] 跳过自己：(x != i || y != j) 遍历细胞的八个邻居，board[i][j] \u0026amp; 1 提取当前状态，统计当前活细胞数 按规则更新下一状态：\n活细胞 → 存活（2~3 个邻居活细胞），否则死亡 死细胞 → 恰好 3 个邻居活细胞复活 board[i][j] |= (next \u0026lt;\u0026lt; 1) 将下一状态存入次低位 更新完成后右移一位：\nboard[i][j] \u0026gt;\u0026gt;= 1，将下一状态更新为当前状态 时间复杂 总时间复杂度 O(n * m)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Solution { public: void gameOfLife(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; board) { int n = board.size(), m = board[0].size(); if (n == 0 || m == 0) return; // 遍历每个细胞，计算下一状态 for (int i = 0; i \u0026lt; n; ++i) { for (int j = 0; j \u0026lt; m; ++j) { int live = 0; // 统计周围活细胞数 // 遍历周围 8 个方向 for (int x = max(0, i - 1); x \u0026lt;= min(i + 1, n - 1); ++x) { for (int y = max(0, j - 1); y \u0026lt;= min(j + 1, m - 1); ++y) { if ((x != i || y != j) \u0026amp;\u0026amp; (board[x][y] \u0026amp; 1)) ++live; // 只看当前状态（最低位） } } int cur = board[i][j]; // 当前状态 int next = 0; // 下一状态 // 按规则更新下一状态 if (cur \u0026amp; 1) { // 当前是活细胞 if (live == 2 || live == 3) next = 1; // 继续存活 else next = 0; // 死亡 } else { // 当前是死细胞 if (live == 3) next = 1; // 复活 else next = 0; // 继续死亡 } board[i][j] |= (next \u0026lt;\u0026lt; 1); // 更新下一状态到次低位 } } // 右移 1 位，更新到当前状态 for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; m; ++j) board[i][j] \u0026gt;\u0026gt;= 1; } }; ","date":"2025-01-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/game-of-life/","title":"Game of Life"},{"content":"30. 串联所有单词的子串 分析 窗口长度固定：\n每个单词长度相同，假设单词长度为 w，words 长度为 m，则窗口长度为 m * w 哈希表记录单词频次：\n使用哈希表 total 统计 words 中每个单词出现的次数 窗口内部用另一个哈希表 window 统计窗口内单词频次 多起点滑动窗口：\n从 s 的下标 0 ~ w - 1 依次滑动，确保不会遗漏跨单词边界的情况 窗口内单词匹配：\n每次从当前位置提取长度为 w 的单词，更新窗口哈希表 如果窗口内单词频次超过 words 中的频次，则收缩窗口 当窗口内单词频次与 words 完全一致时，记录窗口的起始位置 时间复杂度 外层循环 w 次（单词长度） 内层遍历 n / w 次，每次提取单词和更新哈希表，代价为 O(1) 总时间复杂度 O(n * w)\n空间复杂度 空间复杂度为 O(m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Solution { public: vector\u0026lt;int\u0026gt; findSubstring(string s, vector\u0026lt;string\u0026gt;\u0026amp; words) { std::vector\u0026lt;int\u0026gt; res; if (words.empty()) // 边界情况：words为空 return res; int n = s.size(); // 字符串 s 的长度 int m = words.size(); // 单词个数 int w = words[0].size(); // 单词长度 // 统计 words 中每个单词出现的次数 std::unordered_map\u0026lt;std::string, int\u0026gt; total; for (std::string\u0026amp; word : words) ++ total[word]; // 遍历所有可能的起始位置（避免跨单词边界） for (int i = 0; i \u0026lt; w; ++i) { std::unordered_map\u0026lt;std::string, int\u0026gt; window; // 窗口内单词频次 int cnt = 0; // 匹配到的单词数 // 滑动窗口，步长为 w（单词长度） for (int j = i; j + w \u0026lt;= n; j += w) { // 当前窗口右侧加入新单词 std::string rightWord = s.substr(j, w); ++ window[rightWord]; if (window[rightWord] \u0026lt;= total[rightWord]) // 新单词未超额 ++ cnt; // 如果窗口超过了大小 m * w，滑动窗口左侧 if (j \u0026gt;= i + m * w) { std::string leftWord = s.substr(j - m * w, w); -- window[leftWord]; if (window[leftWord] \u0026lt; total[leftWord]) // 如果减少后不匹配 -- cnt; } // 匹配到所有单词，记录起始索引 if (cnt == m) res.push_back(j - (m - 1) * w); } } return res; } }; ","date":"2025-01-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/substr-with-concatenation-of-all-words/","title":"Substr with Concatenation of All Words"},{"content":"36. 有效的数独 分析 行检查：\n遍历每一行，使用布尔数组记录数字是否出现过 列检查：\n遍历每一列，使用布尔数组记录数字是否出现过 3×3 宫格检查：\n每个 3×3 宫格起点分别是 (0,0)、(0,3)、(0,6)、(3,0)… 遍历每个小宫格，判断是否有重复数字 数字映射：\n将字符 '1'~'9' 转换成索引 0 ~ 8，方便标记 发现重复直接返回 false，全部通过返回 true\n时间复杂度 总时间复杂度 O(1)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class Solution { public: bool isValidSudoku(vector\u0026lt;vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; board) { std::vector\u0026lt;bool\u0026gt; st(9); // 标记数组，记录数字是否出现 // 1. 检查每一行 for (int i = 0; i \u0026lt; 9; ++i) { st = std::vector\u0026lt;bool\u0026gt;(9, false); // 重置标记 for (int j = 0; j \u0026lt; 9; ++j) { if (board[i][j] != \u0026#39;.\u0026#39;) { int num = board[i][j] - \u0026#39;1\u0026#39;; // 映射到 0~8 if (st[num]) // 数字重复 return false; st[num] = true; // 标记出现过 } } } // 2. 检查每一列 for (int i = 0; i \u0026lt; 9; ++i) { st = std::vector\u0026lt;bool\u0026gt;(9, false); // 重置标记 for (int j = 0; j \u0026lt; 9; ++j) { if (board[j][i] != \u0026#39;.\u0026#39;) { int num = board[j][i] - \u0026#39;1\u0026#39;; if (st[num]) // 数字重复 return false; st[num] = true; } } } // 3. 检查每个 3×3 宫格 for (int i = 0; i \u0026lt; 9; i += 3) for (int j = 0; j \u0026lt; 9; j += 3) { st = std::vector\u0026lt;bool\u0026gt;(9, false); // 重置标记 for (int x = 0; x \u0026lt; 3; ++x) for (int y = 0; y \u0026lt; 3; ++y) { if (board[i + x][j + y] != \u0026#39;.\u0026#39;) { int num = board[i + x][j + y] - \u0026#39;1\u0026#39;; if (st[num]) // 数字重复 return false; st[num] = true; } } } return true; // 所有检查通过 } }; ","date":"2025-01-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/valid-sudoku/","title":"Valid Sudoku"},{"content":"209. 长度最小的子数组 分析 定义窗口区间： 使用两个指针 i 和 j 表示滑动窗口的左右边界\n窗口扩展：从左到右遍历数组，不断将元素加入当前窗口 sum += nums[i]\n窗口收缩：\n当窗口内的子数组和 target，尝试通过移动左指针 j 来缩小窗口，寻找更短的子数组 每次收缩时更新最小长度：res = min(res, i - j + 1) 返回结果：\n如果没有找到符合条件的子数组，返回 0 否则，返回记录的最短长度 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public: int minSubArrayLen(int target, vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = INT_MAX; // 初始化最小长度为无穷大 int sum = 0; // 窗口内子数组的和 int j = 0; // 左指针 for (int i = 0; i \u0026lt; nums.size(); ++ i) // 遍历数组 { sum += nums[i]; // 扩大窗口（右指针右移） // 收缩窗口（左指针右移），直到和 \u0026lt; target while (sum - nums[j] \u0026gt;= target) { sum -= nums[j]; // 移除窗口左边的元素 ++ j; // 左指针右移 } // 如果当前窗口和 \u0026gt;= target，更新最小长度 if (sum \u0026gt;= target) res = std::min(res, i - j + 1); } // 如果没有符合条件的子数组，返回 0 return res == INT_MAX ? 0 : res; } }; ","date":"2025-01-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-size-subarray-sum/","title":"Minimum Size Subarray Sum"},{"content":"167. 两数之和II 分析 初始化两个指针： 左指针 i = 0（指向数组开头） 右指针 j = n - 1（指向数组末尾） 双指针移动规则： 计算 numbers[i] + numbers[j]： 等于 target → 返回 [i + 1, j + 1]（下标从 1 开始） 大于 target → 右指针左移（减小和） 小于 target → 左指针右移（增大和） 终止条件： 当 i \u0026gt;= j 时，搜索结束 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: vector\u0026lt;int\u0026gt; twoSum(vector\u0026lt;int\u0026gt;\u0026amp; numbers, int target) { for (int i = 0, j = numbers.size() - 1; i \u0026lt; j; ++i) // 初始化双指针 { while (i \u0026lt; j \u0026amp;\u0026amp; numbers[i] + numbers[j] \u0026gt; target) // 如果和大于目标，右指针左移 -- j; if (i \u0026lt; j \u0026amp;\u0026amp; numbers[i] + numbers[j] == target) // 找到目标和 return {i + 1, j + 1}; // 返回结果（下标从1开始） } return {}; // 如果没有找到，返回空数组 } }; ","date":"2025-01-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/two-sum-ii/","title":"Two Sum II"},{"content":"125. 验证回文串 分析 字符校验 check 函数：\n判断字符是否在 '0'-'9'、'a'-'z' 或 'A'-'Z' 范围内 双指针遍历：\n左指针 i 从前往后，右指针 j 从后往前 遇到非字母数字字符，分别跳过 ++i 或 --j 比较两端字符（忽略大小写），如果不同，则返回 false 结束条件：\n如果所有字符都匹配，则返回 true 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public: // 辅助函数：判断字符是否是字母或数字 bool check(char c) { return (c \u0026gt;= \u0026#39;0\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;9\u0026#39;) || (c \u0026gt;= \u0026#39;a\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;z\u0026#39;) || (c \u0026gt;= \u0026#39;A\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;Z\u0026#39;); } bool isPalindrome(string s) { // 双指针初始化 for (int i = 0, j = s.size() - 1; i \u0026lt; j; ++i, --j) { // 左指针跳过非字母数字字符 while (i \u0026lt; j \u0026amp;\u0026amp; !check(s[i])) ++ i; // 右指针跳过非字母数字字符 while (i \u0026lt; j \u0026amp;\u0026amp; !check(s[j])) -- j; // 比较左右字符（忽略大小写） if (i \u0026lt; j \u0026amp;\u0026amp; ::tolower(s[i]) != ::tolower(s[j])) return false; } return true; } }; ","date":"2025-01-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/valid-palindrome/","title":"Valid Palindrome"},{"content":"28. 找出字符串中第一个匹配项的下标 分析 解题思路：KMP 算法\n为了高效地查找子串，KMP 算法（Knuth-Morris-Pratt）通过构建部分匹配表 next 数组，在匹配失败时跳过不必要的字符比较，避免重复匹配\n构建部分匹配表（next数组）\n含义：next[i] 表示 needle[0...i] 的最长相等前后缀长度 作用：当匹配失败时，needle 可以直接跳到合适的位置，减少比较次数 构建过程：\n用两个指针：i 遍历 needle，j 记录当前最长前后缀长度 如果 needle[i] == needle[j]，则 j++，更新 next[i] = j 如果不相等，回溯 j = next[j - 1] 主串匹配\n用两个指针：i 遍历 haystack，j 遍历 needle 如果字符相等，i ++，j ++ 如果不相等，则 j 回溯到 next[j - 1] 当 j == needle.size()，表示完全匹配，返回起始下标 i - m + 1 时间复杂度 构建 next 数组：O(m) 主串匹配：O(n) 总时间复杂度 O(n + m)\n空间复杂度 空间复杂度为 O(m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: int strStr(string haystack, string needle) { int n = haystack.size(), m = needle.size(); if (m == 0) // 特殊情况：needle为空，返回0 return 0; // 构建next数组 std::vector\u0026lt;int\u0026gt; next(m); for (int i = 1, j = 0; i \u0026lt; m; ++i) { while (j \u0026gt; 0 \u0026amp;\u0026amp; needle[i] != needle[j]) // 回退 j = next[j - 1]; if (needle[i] == needle[j]) // 匹配成功 ++j; next[i] = j; // 更新next数组 } // 匹配过程 for (int i = 0, j = 0; i \u0026lt; n; ++i) { while (j \u0026gt; 0 \u0026amp;\u0026amp; haystack[i] != needle[j]) // 回退 j = next[j - 1]; if (haystack[i] == needle[j]) // 匹配成功 ++j; if (j == m) // 完全匹配 return i - m + 1; } return -1; // 匹配失败 } }; ","date":"2025-01-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-the-index-of-the-first-occurrence-in-a-string/","title":"Find the Index of the First Occurrence In a String"},{"content":"58. 最后一个单词的长度 分析 从后往前遍历： 从字符串末尾开始，跳过尾随空格 遇到第一个非空格字符，开始计数，直到遇到空格或遍历结束 计数计算： 遇到空格时，说明最后一个单词已遍历完，返回单词长度 如果整个字符串遍历完，仍未遇到空格，说明字符串只有一个单词 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: int lengthOfLastWord(string s) { // 从字符串末尾开始遍历 for (int i = s.size() - 1; i \u0026gt;= 0; --i) { if (s[i] == \u0026#39; \u0026#39;) // 跳过尾随空格 continue; int j = i - 1; // 找到当前单词的起始位置 while (j \u0026gt;= 0 \u0026amp;\u0026amp; s[j] != \u0026#39; \u0026#39;) --j; // 单词长度 = 结束位置 - 起始位置 return i - j; } return 0; // 字符串全是空格时返回 0 } }; ","date":"2025-01-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/length-of-last-word/","title":"Length of Last Word"},{"content":"68. 文本左右对齐 分析 分组单词（贪心）： 用指针 i 表示当前行的起始单词，j 表示下一个单词 不断尝试加入下一个单词，直到超出 maxWidth 构建每一行： 左对齐（最后一行或只有一个单词）：单词之间加 1 个空格，末尾补空格 普通行： 空格平均分配：空格总数 = maxWidth - 总单词长度 多余空格优先分配到左侧。 使用 std::string(n, ' ') 插入空格 更新索引： 处理完当前行后，将 i 移动到下一行的起始单词 时间复杂度 遍历所有单词，每个单词最多被遍历两次（分组和拼接），时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Solution { public: vector\u0026lt;string\u0026gt; fullJustify(vector\u0026lt;string\u0026gt;\u0026amp; words, int maxWidth) { std::vector\u0026lt;std::string\u0026gt; res; // 存储结果 for (int i = 0; i \u0026lt; words.size(); ++i) { int j = i + 1; // 下一个单词的索引 int len = words[i].size(); // 当前行的总长度 // 将尽可能多的单词放入当前行 while (j \u0026lt; words.size() \u0026amp;\u0026amp; len + 1 + words[j].size() \u0026lt;= maxWidth) { len += 1 + words[j].size(); // 单词加空格 ++j; } std::string line; // 构建当前行内容 // 情况1：最后一行或当前行只有一个单词，左对齐 if (j == i + 1 || j == words.size()) { line += words[i]; // 加入第一个单词 for (int k = i + 1; k \u0026lt; j; ++k) // 后续单词前加空格 line += \u0026#39; \u0026#39; + words[k]; // 补齐剩余空格 while (line.size() \u0026lt; maxWidth) line += \u0026#39; \u0026#39;; } else { // 情况2：普通行（多单词，需均匀分配空格） int count = j - i - 1; // 单词间隔数 int space = maxWidth - len + count; // 总空格数 line += words[i]; // 加入第一个单词 int k = 0; // 多余空格优先分配到左侧 while (k \u0026lt; (space % count)) { line += std::string(space / count + 1, \u0026#39; \u0026#39;) + words[i + k + 1]; ++k; } // 均匀分配剩余空格 while (k \u0026lt; count) { line += std::string(space / count, \u0026#39; \u0026#39;) + words[i + k + 1]; ++k; } } res.push_back(line); // 保存当前行 i = j - 1; // 更新索引，处理下一行 } return res; } }; ","date":"2025-01-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/text-justification/","title":"Text Justification"},{"content":"6. Z字形变换 分析 特殊情况处理： 当 numRows = 1 时，Z 字形退化成一行，直接返回原字符串 分行遍历： 首行和尾行：字符之间的间隔是固定的 cycleLen = 2 * numRows - 2 中间行：每个周期内有两个字符，需要分别计算两个位置 结果拼接： 按行依次将字符加入结果字符串 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: string convert(string s, int numRows) { if (numRows == 1) // 特殊情况处理 return s; std::string res; int cycleLen = 2 * numRows - 2; // 每个周期的长度 for (int i = 0; i \u0026lt; numRows; ++i) // 遍历每一行 { if (i == 0 || i == numRows - 1) // 首行和尾行 { for (int j = i; j \u0026lt; s.size(); j += cycleLen) res += s[j]; } else // 中间行 { for (int j = i, k = cycleLen - i; j \u0026lt; s.size() || k \u0026lt; s.size(); j += cycleLen, k += cycleLen) { if (j \u0026lt; s.size()) // 第一个字符 res += s[j]; if (k \u0026lt; s.size()) // 第二个字符（对称位置） res += s[k]; } } } return res; } }; ","date":"2025-01-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/zigzag-conversion/","title":"Zigzag Conversion"},{"content":"135. 分发糖果 分析 递归+记忆化搜索：\n使用一个数组 candy 记录每个孩子应该分到的糖果数，初始值全为 1 对于每个孩子，递归判断左右相邻的孩子评分关系，更新糖果数量。 记忆化优化：\n如果某个孩子的糖果数已计算过 candy[i] != 1，直接返回，避免重复计算 左右两侧递归更新：\n如果左边孩子评分比当前孩子低，则当前孩子糖果数应该比左边多 1 如果右边孩子评分比当前孩子低，则当前孩子糖果数应该比右边多 1 累加结果：\n遍历所有孩子，将糖果总数累加得到最少糖果数 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: int candy(vector\u0026lt;int\u0026gt;\u0026amp; ratings) { int n = ratings.size(); std::vector\u0026lt;int\u0026gt; candy(n, 1); // 初始化每个孩子分 1 个糖果 int res = 0; for (int i = 0; i \u0026lt; n; ++i) res += minCandy(ratings, candy, i); // 递归更新糖果数 return res; // 返回总糖果数 } // 递归函数：计算第 child 个孩子最少需要的糖果数 int minCandy(std::vector\u0026lt;int\u0026gt;\u0026amp; ratings, std::vector\u0026lt;int\u0026gt;\u0026amp; candy, int child) { if (candy[child] != 1) // 如果已计算过，直接返回 return candy[child]; // 左边孩子评分更低，当前孩子糖果+1 if (child \u0026gt; 0 \u0026amp;\u0026amp; ratings[child - 1] \u0026lt; ratings[child]) candy[child] = std::max(candy[child], minCandy(ratings, candy, child - 1) + 1); // 右边孩子评分更低，当前孩子糖果+1 if (child + 1 \u0026lt; ratings.size() \u0026amp;\u0026amp; ratings[child] \u0026gt; ratings[child + 1]) candy[child] = std::max(candy[child], minCandy(ratings, candy, child + 1) + 1); return candy[child]; // 返回计算结果 } }; ","date":"2025-01-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/candy/","title":"Candy"},{"content":"134. 加油站 分析 模拟遍历： 从每个加油站 i 出发，模拟一圈是否能顺利到达终点 剩余油量判断： 每经过一个加油站，更新剩余油量 oil = oil + gas[k] - cost[k] 如果油量不足 oil \u0026lt; 0，说明从当前起点 i 无法完成一圈 跳过不可行区间： 如果从 i 出发失败，则从 i + step + 1 开始继续尝试，因为 i 到 i + step 之间的起点也无法成功 终止条件： 如果某次尝试走满了 n 步（即 step == n），说明该起点 i 能绕一圈，返回 i 如果所有加油站都尝试失败，则返回 -1 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: int canCompleteCircuit(vector\u0026lt;int\u0026gt;\u0026amp; gas, vector\u0026lt;int\u0026gt;\u0026amp; cost) { int n = gas.size(), step = 0; // n 为加油站数量 for (int i = 0; i \u0026lt; n;) // 遍历每个加油站作为起点 { int oil = 0; // 当前剩余油量 for (step = 0; step \u0026lt; n; ++step) { int k = (i + step) % n; // 环形遍历加油站 oil += gas[k] - cost[k]; // 更新剩余油量 if (oil \u0026lt; 0) // 如果油量不足，停止尝试 break; } if (step == n) // 成功绕一圈 return i; i = i + step + 1; // 跳过失败区间，尝试下一个起点 } return -1; // 没有找到可行解 } }; ","date":"2025-01-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/gas-station/","title":"Gas Station"},{"content":"380. 时间插入、删除和获取随机元素 插入操作：\n使用哈希表来存储元素及其索引，在 O(1) 时间内检查元素是否存在，并在数组末尾插入新元素 删除操作：\n对于删除操作，不直接从哈希表中删除元素，而是用数组末尾的元素替代被删除的元素。这样可以在 O(1) 时间内移除元素，而不需要移动数组中的其它元素 随机访问操作：\n使用一个数组来存储所有元素，借助数组的随机访问特性，可以在 O(1) 时间内获取一个随机元素 时间复杂度 insert(val)：哈希表的查找和插入操作都是 O(1)，数组的末尾插入也是 O(1)\nremove(val)：删除操作中的查找、替换和哈希表更新都是 O(1)，数组末尾删除是 O(1)\ngetRandom()：通过 rand() 和数组索引的方式随机获取元素是 O(1)\n空间复杂度 空间复杂度为 O(n)\nC++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class RandomizedSet { public: std::unordered_map\u0026lt;int, int\u0026gt; hash; // 存储元素及其索引 std::vector\u0026lt;int\u0026gt; nums; // 存储元素，支持随机访问 // 初始化 RandomizedSet 对象 RandomizedSet() {} // 插入元素 val bool insert(int val) { if (hash.count(val) == 0) { nums.push_back(val); // 将 val 加入 nums 数组 hash[val] = nums.size() - 1; // 记录 val 在 nums 数组中的索引 return true; } return false; } // 移除元素 val bool remove(int val) { if (hash.count(val)) { int tail = nums.back(); // 获取数组末尾的元素 int val_index = hash[val]; // 获取 val 在 nums 数组中的索引 int tail_index = hash[tail]; // 获取数组末尾元素的索引 // 将 val 和数组末尾元素交换 std::swap(nums[val_index], nums[tail_index]); std::swap(hash[val], hash[tail]); nums.pop_back(); // 移除数组末尾元素 hash.erase(val); // 从哈希表中移除 val return true; } return false; } // 随机返回一个元素 int getRandom() { return nums[rand() % nums.size()]; // 随机返回 nums 数组中的一个元素 } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import random class RandomizedSet: def __init__(self): self.val_to_idx = {} self.nums = [] def insert(self, val: int) -\u0026gt; bool: if val in self.val_to_idx: return False self.nums.append(val) self.val_to_idx[val] = len(self.nums) - 1 return True def remove(self, val: int) -\u0026gt; bool: if val not in self.val_to_idx: return False idx = self.val_to_idx[val] last_val = self.nums[-1] self.nums[idx] = last_val self.val_to_idx[last_val] = idx self.nums.pop() del self.val_to_idx[val] return True def getRandom(self) -\u0026gt; int: return random.choice(self.nums) Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package main import ( \u0026#34;math/rand\u0026#34; ) type RandomizedSet struct { valToIdx map[int]int nums []int } func Constructor() RandomizedSet { return RandomizedSet{ valToIdx: make(map[int]int), nums: []int{}, } } func (this *RandomizedSet) Insert(val int) bool { if _, exists := this.valToIdx[val]; exists { return false } this.nums = append(this.nums, val) this.valToIdx[val] = len(this.nums) - 1 return true } func (this *RandomizedSet) Remove(val int) bool { idx, exists := this.valToIdx[val]; if !exists { return false } last := this.nums[len(this.nums) - 1] this.nums[idx] = last this.valToIdx[last] = idx this.nums = this.nums[:len(this.nums) - 1] delete(this.valToIdx, val) return true } func (this *RandomizedSet) GetRandom() int { return this.nums[rand.Intn(len(this.nums))] } ","date":"2025-01-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/insert-delete-get-random/","title":"Insert Delete Get Random"},{"content":"122. 买卖股票的最佳时机II 分析 如果今天的股票价格比昨天高，就可以将这部分差价计入利润。这样相当于在每个上升区间都进行买入和卖出操作。将所有上涨的差价累加，即可获得最大利润\n为什么这样是最优的？\n股票可以在同一天买入和卖出，相当于在每个上涨区间及时买卖。 比如：[1, 2, 3, 4, 5]，买入 1 卖出 5 的利润和每天买卖 (1→2，2→3，3→4，4→5) 是相等的 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public: int maxProfit(vector\u0026lt;int\u0026gt;\u0026amp; prices) { int res = 0; // 初始化总利润 for (int i = 1; i \u0026lt; prices.size(); ++i) // 遍历价格数组 if (prices[i] \u0026gt; prices[i - 1]) // 如果今天比昨天贵 res += prices[i] - prices[i - 1]; // 累加差价利润 return res; // 返回总利润 } }; ","date":"2025-01-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/best-time-to-buy-and-sell-stock-ii/","title":"Best Time to Buy and Sell Stock II"},{"content":"274. H指数 算法 将引用次数降序排序。排序后，论文的引用次数是递减的，若第 i 篇论文（索引为 i - 1）的引用次数大于等于 i，则前面 i - 1 篇论文的引用次数也大于等于 i，，那么就有至少 i 篇论文的引用次数大于等于 i，即满足 H 指数条件\n降序排序： 将引用次数从大到小排序，便于直接比较引用次数与当前索引 i 遍历判断： 从 i = n ~ 1（n 为论文总数），逐步判断 如果第 i 篇论文的引用次数 citations[i - 1] \u0026gt;= i，则满足 H 指数定义 返回当前 i 即为最大 H 指数 未找到满足条件的 H 指数： 如果遍历结束都没有找到，返回 0 复杂度分析 排序需要 O(nlogn) 时间，遍历一次数组 O(n)，总体时间复杂度为 O(nlogn) 空间复杂度为 O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public: int hIndex(vector\u0026lt;int\u0026gt;\u0026amp; citations) { // 1. 将引用次数降序排序 std::sort(citations.begin(), citations.end(), std::greater\u0026lt;int\u0026gt;()); // 2. 从大到小遍历，找到满足条件的最大 i for (int i = citations.size(); i \u0026gt; 0; -- i) if (citations[i - 1] \u0026gt;= i) return i; // 找到最大 i，直接返回 // 3. 如果没有满足条件的 i，返回 0 return 0; } }; Python 代码 1 2 3 4 5 6 7 8 class Solution: def hIndex(self, citations: List[int]) -\u0026gt; int: citations.sort(reverse=True) n = len(citations) for i in range(n, 0, -1): if citations[i - 1] \u0026gt;= i: return i return 0 Go 代码 1 2 3 4 5 6 7 8 9 10 11 func hIndex(citations []int) int { sort.Slice(citations, func(i, j int) bool { return citations[i] \u0026gt; citations[j] }) for i := len(citations); i \u0026gt; 0; i -- { if citations[i - 1] \u0026gt;= i { return i } } return 0 } ","date":"2025-01-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/h-index/","title":"H Index"},{"content":"88. 合并两个有序数组 分析 核心思路：从后往前双指针合并\n由于 nums1 后面有足够的空间，可以从后往前填充最大元素，避免频繁移动数据 使用双指针分别指向两个数组的末尾，逐步比较并将较大的数放到 nums1 的最后 初始化指针： i = m - 1 ：指向 nums1 有效部分的末尾 j = n - 1 ：指向 nums2 的末尾 k = m + n - 1 ：指向 nums1 的末尾（总长度） 双指针比较： 比较 nums1[i] 和 nums2[j]，将较大值放到 nums1[k]，然后指针向前移动 处理剩余元素： 如果 nums2 还有剩余元素，继续填充到 nums1 中（nums1 剩下的部分已经是有序的，无需处理） 复杂度分析 时间复杂度：O(m + n) 空间复杂度：O(1) C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: void merge(vector\u0026lt;int\u0026gt;\u0026amp; nums1, int m, vector\u0026lt;int\u0026gt;\u0026amp; nums2, int n) { int i = m - 1; // 指向 nums1 有效部分的末尾 int j = n - 1; // 指向 nums2 的末尾 int k = m + n - 1; // 指向合并后 nums1 的末尾 // 从后往前比较，填充较大元素 while (i \u0026gt;= 0 \u0026amp;\u0026amp; j \u0026gt;= 0) if (nums1[i] \u0026gt;= nums2[j]) nums1[k--] = nums1[i--]; // nums1 较大，放到末尾 else nums1[k--] = nums2[j--]; // nums2 较大，放到末尾 // 如果 nums2 还有剩余元素，继续填充 while (j \u0026gt;= 0) nums1[k--] = nums2[j--]; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution: def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Do not return anything, modify nums1 in-place instead. \u0026#34;\u0026#34;\u0026#34; i, j, k = m - 1, n - 1, n + m - 1 while i \u0026gt;= 0 and j \u0026gt;= 0: if nums1[i] \u0026lt; nums2[j]: nums1[k] = nums2[j] j -= 1 else: nums1[k] = nums1[i] i -= 1 k -= 1 while j \u0026gt;= 0: nums1[k] = nums2[j] j -= 1 k -= 1 Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func merge(nums1 []int, m int, nums2 []int, n int) { i, j, k := m - 1, n - 1, n + m - 1 for i \u0026gt;= 0 \u0026amp;\u0026amp; j \u0026gt;= 0 { if nums1[i] \u0026lt; nums2[j] { nums1[k] = nums2[j] j -- } else { nums1[k] = nums1[i] i -- } k -- } for j \u0026gt;= 0 { nums1[k] = nums2[j] j -- k -- } } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /** * @param {number[]} nums1 * @param {number} m * @param {number[]} nums2 * @param {number} n * @return {void} Do not return anything, modify nums1 in-place instead. */ var merge = function (nums1, m, nums2, n) { let i = m - 1, j = n - 1, k = n + m - 1; while (i \u0026gt;= 0 \u0026amp;\u0026amp; j \u0026gt;= 0) { if (nums1[i] \u0026lt; nums2[j]) { nums1[k--] = nums2[j--]; } else { nums1[k--] = nums1[i--]; } } while (j \u0026gt;= 0) { nums1[k--] = nums2[j--]; } }; ","date":"2025-01-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/merge-sorted-array/","title":"Merge Sorted Array"},{"content":"452. 用最少数量的箭引爆气球 算法 要保证最少的弓箭数，我们可以采用如下贪心策略： 按区间的结束点升序排序：优先处理结束点较小的区间，这样可以最大化覆盖更多的区间 每次发射箭时，记录当前箭能够覆盖的结束点 r 。如果后续某个气球的开始点 x_start 大于当前箭的覆盖范围 r，则需要发射一支新的箭 将所有气球的直径区间按照结束点升序排序 初始化箭的数量 res 为 1，并将第一支箭的覆盖范围设为第一个区间的结束点 遍历排序后的区间： 如果当前区间的开始点大于箭的覆盖范围，说明需要一支新的箭来覆盖当前气球 更新箭的覆盖范围为当前区间的结束点 复杂度分析 排序的时间复杂度为 O(nlogn)，其中 n 是气球的数量；遍历区间的复杂度为 O(n)；总时间复杂度为 O(nlogn) 空间复杂度为 O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: int findMinArrowShots(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; points) { // 如果没有气球，返回 0 if (points.empty()) return 0; // 按区间结束点升序排序 std::sort(points.begin(), points.end(), [](std::vector\u0026lt;int\u0026gt; a, std::vector\u0026lt;int\u0026gt; b) { return a[1] \u0026lt; b[1]; }); // 初始化箭的数量和当前箭的覆盖范围 int res = 1; // 至少需要一支箭 int r = points[0][1]; // 第一支箭的覆盖范围 // 遍历剩余的区间 for (int i = 1; i \u0026lt; points.size(); ++i) { // 如果当前区间的起始点在当前箭的覆盖范围之外 if (r \u0026lt; points[i][0]) { ++res; // 增加一支箭 r = points[i][1]; // 更新箭的覆盖范围 } } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 class Solution: def findMinArrowShots(self, points: List[List[int]]) -\u0026gt; int: points.sort(key = lambda point: point[1]) res, r = 1, points[0][1] for i in range(1, len(points)): if r \u0026lt; points[i][0]: r = points[i][1] res += 1 return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import \u0026#34;sort\u0026#34; func findMinArrowShots(points [][]int) int { sort.Slice(points, func(i, j int) bool { return points[i][1] \u0026lt; points[j][1] }) res, r := 1, points[0][1] for i := 1; i \u0026lt; len(points); i += 1 { if r \u0026lt; points[i][0] { r = points[i][1] res += 1 } } return res } ","date":"2025-01-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-number-of-arrows-to-burst-balloons/","title":"Minimum Number Of Arrows To Burst Balloons"},{"content":"435. 无重叠区间 分析 要保证最多的不重叠区间数，可以采用如下贪心策略： 按区间的结束点升序排序：结束点越早，剩余空间越多，更容易为后续区间预留空间 遍历排序后的区间，选择与前一个区间不重叠的区间，将其加入到保留的集合中 先按结束点排序。 初始化计数变量 count，记录当前不wq重叠区间的数量，初始值为 1 遍历排序后的区间： 如果当前区间的起始点 start 不小于上一个保留区间的结束点 cur，说明它与前面的区间不重叠，更新 cur 并增加计数 最后，用总区间数减去 count 即为需要移除的区间数 时间复杂度 排序的时间复杂度为 O(nlogn) ，其中 n 是区间的数量 遍历区间的复杂度为 O(n) 总时间复杂度为 O(nlogn)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public: int eraseOverlapIntervals(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; intervals) { // 如果没有区间，直接返回 0 if (intervals.empty()) return 0; // 按区间的结束点升序排序 std::sort(intervals.begin(), intervals.end(), [](std::vector\u0026lt;int\u0026gt; a, std::vector\u0026lt;int\u0026gt; b) { return a[1] \u0026lt; b[1]; }); // 初始化计数器和当前区间结束点 int count = 1; // 至少有一个区间不重叠 int cur = intervals[0][1]; // 遍历剩余的区间 for (int i = 1; i \u0026lt; intervals.size(); ++i) { // 如果当前区间不重叠，则更新结束点并增加计数 if (cur \u0026lt;= intervals[i][0]) { ++count; cur = intervals[i][1]; } } // 总区间数 - 不重叠区间数 = 需要移除的区间数 return intervals.size() - count; } }; ","date":"2025-01-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/non-overlapping-intervals/","title":"Non Overlapping Intervals"},{"content":"901. 股票价格跨度 分析 维护单调递减栈： 栈中存储之前的股票价格以及对应的日期索引 保证栈中从栈底到栈顶的股票价格递减。这样可以快速找到比当前价格大的最近日期 核心操作： 对于当前股价 price： 不断弹出栈顶元素，直到栈顶价格大于当前价格 此时栈顶对应的索引即为最近一个比当前价格高的日期 当前跨度为：当前日期索引减去栈顶索引 边界处理： 初始化时，将一个虚拟价格（如 10^6 ）和一个虚拟日期（如 -1）入栈，方便处理第一天的数据 时间复杂度 每个价格最多会被栈操作一次（入栈和出栈各一次），因此所有操作的总时间复杂度为 O(n) ，每次调用 next 的均摊时间复杂度为 O(1)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class StockSpanner { public: std::stack\u0026lt;int\u0026gt; prices; // 单调栈存储股票价格 std::stack\u0026lt;int\u0026gt; day; // 单调栈存储对应的日期索引 int cur = 0; // 当前的日期索引 StockSpanner() { // 初始化栈：存入虚拟的最大价格和虚拟日期 prices.push(1e6); day.push(-1); } int next(int price) { // 弹出所有小于等于当前价格的栈顶元素 while (prices.top() \u0026lt;= price) { prices.pop(); day.pop(); } // 计算跨度：当前日期索引减去栈顶索引 int res = cur - day.top(); // 将当前价格和索引入栈 prices.push(price); day.push(cur++); return res; } }; ","date":"2025-01-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/online-stock-span/","title":"Online Stock Span"},{"content":"338. 比特位计数 分析 状态定义\n定义 f[i]：表示数字 i 的二进制表示中 1 的个数 状态转移\n将数字 i 右移一位（即 i \u0026gt;\u0026gt; 1 ），相当于去掉最低位的二进制位 数字 i 中的 1 的个数可以表示为：f[i] = f[i \u0026gt;\u0026gt; 1] + (i \u0026amp; 1) f[i \u0026gt;\u0026gt; 1] ：前一个数字中 1 的个数 i \u0026amp; 1：判断最低位是否为 1（若最低位为 1 ，加 1 ；否则加 0 ） 初始状态\nf[0] = 0 ：数字 0 的二进制表示中没有 1 时间复杂度 遍历从 1 到 n 的所有数字，动态规划计算 f[i] 每次计算的复杂度为 O(1) ，总复杂度为 O(n) 空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public: vector\u0026lt;int\u0026gt; countBits(int n) { // 定义结果数组，长度为 n + 1 std::vector\u0026lt;int\u0026gt; f(n + 1); // 动态规划填充数组 for (int i = 1; i \u0026lt;= n; ++i) // 根据状态转移方程计算 f[i] = f[i \u0026gt;\u0026gt; 1] + (i \u0026amp; 1); return f; } }; ","date":"2025-01-08T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/count-bits/","title":"Count Bits"},{"content":"1318. 或运算的最小翻转次数 分析 位运算分析\n对每一位（从低位到高位）分别判断 a 、b 、c 的二进制表示： x = a \u0026gt;\u0026gt; i \u0026amp; 1：表示 a 的第 i 位 y = b \u0026gt;\u0026gt; i \u0026amp; 1：表示 b 的第 i 位 z = c \u0026gt;\u0026gt; i \u0026amp; 1：表示 c 的第 i 位 根据 z 的值，分情况讨论：\n如果 z = 0 ：\n需要 x = 0 且 y = 0，因此：需要翻转的次数 = x + y 如果 z = 1 ：\n至少需要 x = 1 或 y = 1 。若 x = 0 且 y = 0 ，则需要翻转 1 循环计算\n逐位处理 a 、b 和 c ，根据上述规则累加所需的翻转次数\n终止条件 由于题目限制 a, b, c 小于 1e9，其最大值在 2^30 以内，因此只需处理前 30 位即可\n时间复杂度 O(1)\n空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: int minFlips(int a, int b, int c) { int res = 0; // 遍历 30 位 for (int i = 0; i \u0026lt; 30; ++i) { int x = a \u0026gt;\u0026gt; i \u0026amp; 1; // a 的第 i 位 int y = b \u0026gt;\u0026gt; i \u0026amp; 1; // b 的第 i 位 int z = c \u0026gt;\u0026gt; i \u0026amp; 1; // c 的第 i 位 if (z == 0) // 如果 z 为 0 res += x + y; // 需要翻转 x 和 y 中的所有 1 else if (!x \u0026amp;\u0026amp; !y) // 如果 z 为 1 且 x 和 y 都为 0 ++ res; // 至少需要翻转一个 0 为 1 } return res; // 返回总翻转次数 } }; ","date":"2025-01-08T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-flips-to-make-a-or-b-equal-to-c/","title":"Minimum Flips To Make A or B Equal to C"},{"content":"1268. 搜索推荐系统 分析 排序产品数组\n为了能够快速按字典序找到前缀匹配的产品，首先对 products 按字典序排序 前缀匹配\n对 searchWord 的每个前缀： 使用一个变量 cur 保存当前前缀（即累加输入的字母） 使用一个指针 k 维护当前搜索范围的起点。对于排序后的数组，可以跳过所有小于 cur 的字符串，加速搜索。 遍历从 k 开始的最多三个字符串，检查它们是否以 cur 为前缀，若满足条件则加入推荐结果 提前结束\n对于排序后的数组，如果某个字符串不再满足当前前缀条件，后续的字符串也不可能满足，可以提前结束内层循环 时间复杂度 排序：O(mlogm)，其中 m 是 products 的长度 前缀匹配：对于 searchWord 的每个字符，我们最多遍历 products 中 3 个字符串，复杂度为 O(n * avgLen)，其中 n 是 searchWord 的长度，avgLen 是产品的平均长度 总复杂度：O(mlogm + n * avgLen\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public: vector\u0026lt;vector\u0026lt;string\u0026gt;\u0026gt; suggestedProducts(vector\u0026lt;string\u0026gt;\u0026amp; products, string searchWord) { // 结果数组 std::vector\u0026lt;std::vector\u0026lt;std::string\u0026gt;\u0026gt; res; // 按字典序排序产品数组 std::sort(products.begin(), products.end()); // 当前前缀和产品数组的起点指针 std::string cur; int n = products.size(), k = 0; // 遍历 searchWord 的每个字母 for (char c : searchWord) { cur += c; // 更新当前前缀 // 移动指针 k 跳过所有小于当前前缀的字符串 while (k \u0026lt; n \u0026amp;\u0026amp; products[k] \u0026lt; cur) ++k; // 收集符合条件的最多三个产品 std::vector\u0026lt;std::string\u0026gt; tmp; for (int i = k; i \u0026lt; n \u0026amp;\u0026amp; i \u0026lt; k + 3; ++i) { // 判断当前字符串是否以 cur 为前缀 if (products[i].substr(0, cur.size()) == cur) tmp.push_back(products[i]); else break; // 剪枝：后续字符串不会匹配，提前退出 } // 将当前前缀的推荐结果加入结果数组 res.push_back(tmp); } return res; } }; ","date":"2025-01-08T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/search-suggestions-system/","title":"Search Suggestions System"},{"content":"714. 买卖股票的最佳时机含手续费 分析 状态定义\n定义 f[i][0] ：表示第 i 天结束时，手中没有持有股票的最大利润 定义 f[i][1] ：表示第 i 天结束时，手中持有股票的最大利润 状态转移\n在第 i 天，可以有以下两种状态转移： 手中没有股票： 可能来自前一天也没有股票，即 f[i-1][0] 或者今天卖出股票，产生利润 f[i-1][1] + prices[i-1] - fee（卖出价格减去手续费）。 转移公式：f[i][0] = max(f[i-1][0], f[i-1][1] + prices[i-1] - fee) 手中持有股票： 可能来自前一天也持有股票，即 f[i-1][1] 或者今天买入股票，产生利润 f[i-1][0] - prices[i-1] 转移公式：f[i][1] = max(f[i-1][1], f[i-1][0] - prices[i-1]) 初始状态\n第 0 天没有股票：f[0][0] = 0（没有任何利润） 结果\n最后一天手中没有股票的最大利润 f[n][0] 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: int maxProfit(vector\u0026lt;int\u0026gt;\u0026amp; prices, int fee) { int n = prices.size(), INF = 0x3f3f3f3f; // 动态规划数组，初始化为负无穷（表示无效状态） std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f(n + 1, std::vector\u0026lt;int\u0026gt;(2, -INF)); f[0][0] = 0; // 第 0 天未持有股票的利润为 0 int res = 0; // 最大利润 for (int i = 1; i \u0026lt;= n; ++i) { // 第 i 天未持有股票的最大利润 f[i][0] = std::max(f[i - 1][0], f[i - 1][1] + prices[i - 1] - fee); // 第 i 天持有股票的最大利润 f[i][1] = std::max(f[i - 1][1], f[i - 1][0] - prices[i - 1]); // 更新最大利润 res = std::max(res, f[i][0]); } return res; } }; ","date":"2025-01-07T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/best-time-to-buy-and-sell-stock-with-transaction-fee/","title":"Best Time to Buy and Sell Stock With Transaction Fee"},{"content":"790. 多米诺和托米诺平铺 分析 状态表示： 用 f[i][j] 表示覆盖 2 * i 面板且第i列状态为 j 的方法数。这里的状态 j 表示第 i 列的覆盖情况，具体含义如下： j = 0 ：第 i 列已完全覆盖 j = 1 ：第 i 列的上方未覆盖，下方已覆盖 j = 2 ：第 i 列的下方未覆盖，上方已覆盖 j = 3 ：第 i 列完全未覆盖 状态转移： 从第 i-1 列的状态 j 转移到第 i 列的状态 k 。需要考虑瓷砖的放置规则，以及状态变化的可行性 定义 w[j][k] 为从状态 j 转移到状态 k 的合法性： w[j][k] = 1 ：表示可以从 j 转移到 k w[j][k] = 0 ：表示不可以从 j 转移到 k 初始状态： f[0][0] = 1 ：初始时第 0 列完全覆盖 其他状态初始化为 0。 目标： 返回 f[n][0]，即第 n 列完全覆盖的方案数 时间复杂度 外层遍历列数 n ，内层枚举状态 j 和 k 各为常数 4 ，因此时间复杂度为 O(16n) = O(n)\n空间复杂度 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: int numTilings(int n) { const int MOD = 1e9 + 7; // 转移矩阵，定义状态转移规则 int w[4][4] = { {1, 1, 1, 1}, {0, 0, 1, 1}, {0, 1, 0, 1}, {1, 0, 0, 0} }; // 动态规划数组，f[i][j] 表示覆盖到第 i 列状态为 j 的方法数 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; f(n + 1, vector\u0026lt;int\u0026gt;(4)); // 初始状态：第 0 列完全覆盖 f[0][0] = 1; // 动态规划求解 for (int i = 0; i \u0026lt; n; i++) for (int j = 0; j \u0026lt; 4; j++) for (int k = 0; k \u0026lt; 4; k++) // 如果状态 j 可以转移到状态 k，则更新 f[i+1][k] f[i + 1][k] = (f[i + 1][k] + f[i][j] * w[j][k]) % MOD; // 返回最终结果：覆盖到第 n 列且状态为 0 的方法数 return f[n][0]; } }; ","date":"2025-01-07T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/domino-and-tromino-tiling/","title":"Domino And Tromino Tiling"},{"content":"216. 组合总和III 分析 采用深度优先搜索（DFS）+ 回溯来解决问题：\n递归函数设计： 参数： 当前搜索起点 start（避免重复选择数字） 当前目标和 n 当前还需要选的数字个数 k 终止条件： 如果 n == 0 且 k == 0 ，说明找到了一组符合条件的组合，将其加入结果列表 如果 n != 0 且 k == 0 ，或者 n \u0026lt; 0 ，直接返回（剪枝） 递归遍历： 从 start 开始，依次尝试数字 i （范围 start 到 9 每次选择 i 后递归：将 i 加入路径，更新 n = n - i ，更新 k = k - 1 递归返回后撤销选择 i（回溯） 剪枝优化： 如果 i \u0026gt; n ，则跳过剩余数字，直接剪枝，因为后续数字只会更大 时间复杂度 最多有 9 / k 个组合，其中 n / k = n! / k!(n-k)! 每次递归需要 O(k) 的时间来复制路径 总时间复杂度为 O(k * 9 / k)\n空间复杂度 路径 path 的最大深度为 k ，递归调用栈的深度也为 k 空间复杂度为 O(k)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储结果 std::vector\u0026lt;int\u0026gt; path; // 当前路径 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; combinationSum3(int k, int n) { dfs(1, n, k); // 从数字1开始搜索 return res; } void dfs(int start, int n, int k) { if (n == 0) { if (k == 0) res.push_back(path); // 找到一个合法组合 return; } if (k == 0) return; // 数字用完，但和未达到目标 for (int i = start; i \u0026lt;= 9; ++i) { if (i \u0026gt; n) break; // 剪枝：当前数字大于目标和 path.push_back(i); // 选择数字i dfs(i + 1, n - i, k - 1); // 递归搜索 path.pop_back(); // 回溯，撤销选择 } } }; ","date":"2025-01-06T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/combination-sum-iii/","title":"Combination Sum III"},{"content":"875. 爱吃香蕉的珂珂 分析 定义区间： 吃香蕉速度的下界 l 为 1（最低速度） 吃香蕉速度的上界 r 为 10^9（最大速度，假设所有香蕉都在一堆中且时间充足） 判断函数 getTime(piles, k) ： 计算以速度 k 吃完所有香蕉需要的时间 遍历每堆香蕉 pile ，需要的时间为 pile / k 上取整，可以通过 (pile + k - 1) / k 实现 二分查找： 计算中间值 mid = (l + r) / 2 ： 如果 getTime(piles, mid) \u0026lt;= h ，说明速度 mid 可以满足要求，但可能还有更小的速度满足条件，因此缩小上界 r = mid 如果 getTime(piles, mid)} \u0026gt; h ，说明速度 mid 不够快，需要增大速度，更新下界 l = mid + 1 结束条件： 当 l == r 时，搜索范围缩小到单一值，返回 l 或 r 即为最小速度 时间复杂度 二分查找最多进行 O(log(10^9)) 次 每次计算 getTime(piles, k) 需要 O(n)，其中 n 是香蕉堆的数量 总复杂度为 O(n * log(10^9))\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: // 判断以速度 k 吃完所有香蕉需要的时间 int getTime(std::vector\u0026lt;int\u0026gt;\u0026amp; piles, int k) { int res = 0; for (int pile : piles) res += (pile + k - 1) / k; // 等价于 ceil(pile / k) return res; } int minEatingSpeed(vector\u0026lt;int\u0026gt;\u0026amp; piles, int h) { int l = 1, r = 1e9; // 定义速度的搜索区间 while (l \u0026lt; r) { int mid = (l + r) \u0026gt;\u0026gt; 1; if (getTime(piles, mid) \u0026lt;= h) // 如果当前速度满足条件 r = mid; // 尝试更小的速度 else l = mid + 1; // 增大速度 } return r; // 返回最小速度 } }; ","date":"2025-01-06T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/koko-eating-bananas/","title":"Koko Eating Bananas"},{"content":"746. 使用最小花费爬楼梯 分析 状态定义： 定义 f[i] 为到达第 i 个台阶的最低花费 状态转移： 每次可以从前一个台阶爬上来，也可以从前两个台阶爬上来，选择花费较小的一种：f[i] = min(f[i-1] + cost[i-1], f[i-2] + cost[i-2]) 初始状态： f[0] = 0 ：第 0 台阶为起点，不需要任何花费 f[1] = 0 ：第 1 台阶为起点，也不需要任何花费 目标： 返回 f[n] ，即爬到楼梯顶部的最小花费 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public: int minCostClimbingStairs(vector\u0026lt;int\u0026gt;\u0026amp; cost) { int n = cost.size(); std::vector\u0026lt;int\u0026gt; f(n + 1); // DP 数组，大小为 n+1 // 从第 2 个台阶开始计算最低花费 for (int i = 2; i \u0026lt;= n; ++i) f[i] = std::min(f[i - 1] + cost[i - 1], f[i - 2] + cost[i - 2]); return f[n]; // 返回到达顶部的最小花费 } }; ","date":"2025-01-06T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/min-cost-climbing-stairs/","title":"Min Cost Climbing Stairs"},{"content":"1137. 第N个泰波那契数 分析 状态转移： 当前值 T_n 由前三项的值 T_{n-1}、T_{n-2}、T_{n-3} 相加得出 初始化： a 表示 T_{n-3} b 表示 T_{n-2} c 表示 T_{n-1} 初始值为 a = 0、b = 1、c = 1 迭代计算： 按照递推公式依次计算 T_3 到 T_n，并更新 a、b、c 的值 返回结果： 经过 n 次迭代后，a 存储的就是第 n 个泰波那契数 时间复杂度 计算第 n 个泰波那契数需要迭代 n 次，每次计算仅涉及常数操作 时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: int tribonacci(int n) { int a = 0, b = 1, c = 1; // 初始化 T0, T1, T2 while (n--) // 循环 n 次，计算第 n 个泰波那契数 { long long d = (long long)a + b + c; // 计算当前值 a = b; // 更新 T(n-3) 为 T(n-2) b = c; // 更新 T(n-2) 为 T(n-1) c = d; // 更新 T(n-1) 为 T(n) } return a; // 返回结果 } }; ","date":"2025-01-06T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/nth-tribonacci-number/","title":"Nth Tribonacci Number"},{"content":"162. 寻找峰值 分析 定义区间： 使用两个指针 l 和 r，分别指向数组的起点和终点，表示当前搜索的范围 二分查找： 计算中间位置 mid = (l + r) / 2 比较 nums[mid] 和 nums[mid + 1] ： 如果 nums[mid] \u0026gt; nums[mid + 1] ：说明峰值可能在左侧（包括当前 mid），将右边界 r 移到 mid 如果 nums[mid] \u0026lt; nums[mid + 1] ：说明峰值可能在右侧，更新左边界 l 为 mid + 1 结束条件： 当 l == r 时，区间缩小为一个位置，此时该位置即为峰值索引 时间复杂度 时间复杂度 O(logn)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: int findPeakElement(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int l = 0, r = nums.size() - 1; while (l \u0026lt; r) { int mid = (l + r) \u0026gt;\u0026gt; 1; if (nums[mid] \u0026gt; nums[mid + 1]) r = mid; // 峰值在左侧或当前 mid else l = mid + 1; // 峰值在右侧 } return r; // 或返回 l，最终 l == r } }; ","date":"2025-01-05T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-peak-element/","title":"Find Peak Element"},{"content":"374. 猜数字大小 分析 初始化搜索范围： 定义变量 l 和 r 分别表示搜索范围的左右边界 初始搜索范围为 [1, n] 二分查找： 计算中间值 mid = (l + r) / 2，避免溢出可以写为 (l + r) \u0026gt;\u0026gt; 1 调用 guess(mid) 判断中间值与目标值的关系： 如果返回 -1，说明目标值更小，将右边界缩小为 r = mid 如果返回 1，说明目标值更大，将左边界缩小为 l = mid + 1 终止条件： 当左右边界相遇时，搜索结束，返回 r 时间复杂度 每次查找都会将搜索范围缩小一半，直到范围缩小到 1 时间复杂度为 O(logn)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public: int guessNumber(int n) { int l = 1, r = n; // 初始化左右边界 while (l \u0026lt; r) { int mid = (long long)l + r \u0026gt;\u0026gt; 1; // 计算中间值 if (guess(mid) \u0026lt;= 0) // 若目标值小于等于 mid r = mid; // 缩小右边界 else // 若目标值大于 mid l = mid + 1; // 缩小左边界 } return r; // 返回结果 } }; ","date":"2025-01-05T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/guess-number-higher-or-lower/","title":"Guess Number Higher Or Lower"},{"content":"2300. 咒语和药水的成功对数 分析 排序药水数组： 为了快速确定满足条件的药水，我们对药水数组 potions 进行升序排序 二分查找： 对于每个咒语 spells[i]，计算所需药水的最小能量值：target = (success + spells[i] - 1) / spells[i] 使用二分查找找到第一个大于等于 target 的药水位置 pos 满足条件的药水数量为 m - pos，其中 m 是药水数组的长度 返回结果： 对每个咒语计算成功组合的药水数量，并存储在结果数组 res 中 时间复杂度 药水数组排序：O(mlogm) 对每个咒语进行二分查找：O(nlogm) 总时间复杂度：O(mlogm + nlogm)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: // 二分查找：找到第一个大于等于 target 的药水位置 int search(std::vector\u0026lt;int\u0026gt;\u0026amp; potions, long long target) { int l = 0, r = potions.size(); while (l \u0026lt; r) { int mid = (l + r) \u0026gt;\u0026gt; 1; if (potions[mid] \u0026gt;= target) r = mid; else l = mid + 1; } return r; } vector\u0026lt;int\u0026gt; successfulPairs(vector\u0026lt;int\u0026gt;\u0026amp; spells, vector\u0026lt;int\u0026gt;\u0026amp; potions, long long success) { int n = spells.size(), m = potions.size(); std::sort(potions.begin(), potions.end()); // 排序药水数组 std::vector\u0026lt;int\u0026gt; res(n); for (int i = 0; i \u0026lt; n; ++i) { // 计算当前咒语的 target 值 int pos = search(potions, (success + spells[i] - 1) / spells[i]); res[i] = m - pos; // 成功组合数量 } return res; } }; ","date":"2025-01-05T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/successful-pairs-of-spells-and-potions/","title":"Successful Pairs Of Spells And Potions"},{"content":"2542. 最大子序列的分数 分析 贪心策略： 优先选择 nums2 值较大的元素，以确保当前子序列的最小值较大，即 min(nums2) 尽可能大，进而最大化分数 降序排序： 将 nums2 和 nums1 的对应元素组合成一个二元组 nums2[i], nums1[i]，并按 nums2 降序排列，这样我们可以从最大值开始依次考虑子序列 优先队列维护和： 使用一个最小堆 min_heap 来动态维护当前选定子序列中 nums1 的 k 个元素的和 如果堆的大小超过 k，移除堆顶（即当前最小的元素），从而保持堆的大小不超过 k 这样可以保证始终以较大的元素尽量填满子序列 更新最大分数： 每当堆的大小达到 k ，更新最大分数 时间复杂度 排序操作：O(n \\log n)，其中 n 是数组的长度 遍历 combine 并维护堆：O(nlogk)，每次插入或删除堆顶操作需要 O(logk) 总时间复杂度：O(nlogn + nlogk)\n空间复杂度 使用了一个最小堆，大小为 O(k) 存储组合后的数组，空间为 O(n) 总空间复杂度：O(n + k)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { public: long long maxScore(vector\u0026lt;int\u0026gt;\u0026amp; nums1, vector\u0026lt;int\u0026gt;\u0026amp; nums2, int k) { using PII = std::pair\u0026lt;int, int\u0026gt;; std::vector\u0026lt;PII\u0026gt; combine; // 将 nums1 和 nums2 的元素组合成二元组 for (int i = 0; i \u0026lt; nums1.size(); ++i) combine.push_back({nums2[i], nums1[i]}); // 按 nums2 降序排列 std::sort(combine.rbegin(), combine.rend()); long long nums1_sum = 0, max_score = 0; std::priority_queue\u0026lt;int, std::vector\u0026lt;int\u0026gt;, std::greater\u0026lt;int\u0026gt;\u0026gt; min_heap; // 遍历每个元素，维护一个大小为 k 的子序列 for (auto t : combine) { int num2 = t.first, num1 = t.second; nums1_sum += num1; min_heap.push(num1); // 如果堆大小超过 k，则移除最小值 if (min_heap.size() \u0026gt; k) { nums1_sum -= min_heap.top(); min_heap.pop(); } // 更新最大分数 if (min_heap.size() == k) max_score = std::max(max_score, nums1_sum * num2); } return max_score; } }; ","date":"2025-01-04T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximum-subsequence-score/","title":"Maximum Subsequence Score"},{"content":"2336. 无限集中的最小数字 分析 使用有序集合 std::set 来维护集合 s，其特性是自动按升序排列元素： popSmallest() 可以通过获取集合中的第一个元素*s.begin() 来实现最小值的提取，时间复杂度为 O(log n) addBack(num) 通过插入操作，将数字加入集合，同时自动维护顺序，时间复杂度为 O(log n) 由于题目给出 1 \u0026lt;= num \u0026lt;= 1000，因此初始化集合时，可以预先将 s 初始化为 1 ~ 1000 时间复杂度 popSmallest()： 获取最小值 *s.begin(): O(1) 删除操作 s.erase(): O(\\log n) 总时间复杂度：O(logn) addBack(num)： 插入操作 s.insert()：O(logn) 初始化操作： 插入 n 个初始值：总复杂度为 O(nlogn) 空间复杂度 主要存储集合 s，大小为集合中的元素个数，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class SmallestInfiniteSet { public: std::set\u0026lt;int\u0026gt; s; // 构造函数，初始化集合 SmallestInfiniteSet() { // 集合的初始值为 [1, 2, ..., 1000] for (int i = 1; i \u0026lt;= 1000; ++i) s.insert(i); } // 移除并返回最小值 int popSmallest() { // 获取集合中最小值 int t = *s.begin(); // 从集合中删除最小值 s.erase(s.begin()); return t; } // 将数字添加回集合 void addBack(int num) { // 插入数字，std::set 会自动去重 s.insert(num); } }; ","date":"2025-01-04T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/smallest-number-in-infinite-set/","title":"Smallest Number In Infinite Set"},{"content":"2462. 雇佣k位工人的总代价 分析 双端选择策略： 使用两个最小堆分别管理两端候选工人的代价： 左堆 left_heap：存储前 candidates 个工人 右堆 right_heap：存储后 candidates 个工人 每轮从两堆中选择代价最小的工人。如果代价相同，优先选择左堆的工人（即下标较小的工人） 动态维护候选集合： 每次从堆中弹出一个工人后： 如果是左堆弹出，在左堆加入新的候选工人（如果剩余工人足够） 如果是右堆弹出，在右堆加入新的候选工人（如果剩余工人足够） 模拟雇佣过程： 进行 k 轮雇佣，每轮计算当前最小代价并累加到总成本 时间复杂度 初始化堆：最多处理 2 * candidates 个工人，复杂度为 O(candidates * log candidates) 每次雇佣需要从堆中弹出元素并可能插入一个新元素，复杂度为 O(log candidates) 总共雇佣 k 轮，因此雇佣过程的复杂度为 O(k * log candidates) 总时间复杂度：O((k + candidates) * log candidates)\n空间复杂度 使用两个最小堆，每个堆最多存储 candidates 个元素 总空间复杂度：O(candidates)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public: long long totalCost(vector\u0026lt;int\u0026gt;\u0026amp; costs, int k, int candidates) { // 左、右堆分别维护前 candidates 和后 candidates 个工人的代价 std::priority_queue\u0026lt;int, std::vector\u0026lt;int\u0026gt;, std::greater\u0026lt;int\u0026gt;\u0026gt; left_heap, right_heap; int l = 0, r = costs.size() - 1; // 初始化左堆 for (int i = 0; i \u0026lt; candidates \u0026amp;\u0026amp; l \u0026lt;= r; ++i, ++l) left_heap.push(costs[l]); // 初始化右堆 for (int i = 0; i \u0026lt; candidates \u0026amp;\u0026amp; l \u0026lt;= r; ++i, --r) right_heap.push(costs[r]); long long totalCost = 0; // 总代价 for (int i = 0; i \u0026lt; k; ++i) { // 从两堆中选择代价最小的工人 if (left_heap.size() \u0026amp;\u0026amp; (right_heap.empty() || left_heap.top() \u0026lt;= right_heap.top())) { totalCost += left_heap.top(); // 累加代价 left_heap.pop(); // 移除工人 if (l \u0026lt;= r) // 更新左堆 left_heap.push(costs[l++]); } else { totalCost += right_heap.top(); // 累加代价 right_heap.pop(); // 移除工人 if (l \u0026lt;= r) // 更新右堆 right_heap.push(costs[r--]); } } return totalCost; // 返回总代价 } }; ","date":"2025-01-04T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/total-cost-to-hire-k-workers/","title":"Total Cost To Hire K Workers"},{"content":"399. 除法求值 分析 构建图： 遍历 equations 和 values，构建邻接表表示的加权有向图 graph 如果 A / B = v，则添加边 A -\u0026gt; B 和 B -\u0026gt; A Floyd-Warshall 算法： 利用三重循环，尝试通过任意中间节点 k 更新任意两节点 i 和 j 间的比值 如果 i -\u0026gt; k 和 k -\u0026gt; j 的路径存在，则更新 i -\u0026gt; j 的权重为 graph[i][k] * graph[k][j] 处理查询： 对于每个查询 [C, D]： 如果 C 和 D 不在图中，或者两者之间无路径，则返回 -1.0 否则返回图中存储的 C -\u0026gt; D 的权重 时间复杂度 构建图：遍历 equations，时间复杂度为 O(E)，其中 E 是等式的数量 Floyd-Warshall 算法：三重循环，时间复杂度为 O(V^3)，其中 V 是变量的数量 处理查询：遍历 queries，时间复杂度为 O(Q)，其中 Q 是查询的数量 总体时间复杂度为 O(V^3 + E + Q)\n空间复杂度 总的空间复杂度为 O(V^2 + Q)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public: vector\u0026lt;double\u0026gt; calcEquation(vector\u0026lt;vector\u0026lt;string\u0026gt;\u0026gt;\u0026amp; equations, vector\u0026lt;double\u0026gt;\u0026amp; values, vector\u0026lt;vector\u0026lt;string\u0026gt;\u0026gt;\u0026amp; queries) { // 构建图 std::unordered_map\u0026lt;std::string, std::unordered_map\u0026lt;std::string, double\u0026gt;\u0026gt; graph; std::unordered_set\u0026lt;std::string\u0026gt; variables; for (int i = 0; i \u0026lt; equations.size(); ++i) { std::string A = equations[i][0], B = equations[i][1]; variables.insert(A); variables.insert(B); graph[A][B] = values[i]; // A / B = values[i] graph[B][A] = 1 / values[i]; // B / A = 1 / values[i] } // Floyd-Warshall算法：更新所有节点对的路径比值 for (std::string k : variables) for (std::string i : variables) for (std::string j : variables) if (graph[i][k] \u0026amp;\u0026amp; graph[k][j]) // 如果 i-\u0026gt;k 和 k-\u0026gt;j 存在路径 graph[i][j] = graph[i][k] * graph[k][j]; // 处理查询 std::vector\u0026lt;double\u0026gt; res; for (auto query : queries) { std::string A = query[0], B = query[1]; if (graph[A][B]) // 如果 A-\u0026gt;B 存在路径 res.push_back(graph[A][B]); else res.push_back(-1.0); // 不存在路径 } return res; } }; ","date":"2025-01-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/evaluate-division/","title":"Evaluate Division"},{"content":"1926. 迷宫中离入口最近的出口 分析 初始化一个二维数组 dist，用来记录从入口到每个位置的步数，初始值为无穷大 INF 将入口位置的步数置为 0，并将其加入队列。 使用 BFS 依次扩展当前格子，尝试向上下左右 4 个方向移动： 如果目标格子在边界上并且是出口，返回步数。 如果目标格子可达（空格子，未访问过），更新步数并加入队列 如果队列为空，说明无法找到出口，返回 -1 时间复杂度 BFS 遍历迷宫每个空格子一次，每个格子最多处理 4 个方向 总时间复杂度为 O(n * m)，其中 n 和 m 分别为迷宫的行数和列数\n空间复杂度 需要存储距离矩阵 dist 和 BFS 的队列，均为 O(n * m) 总空间复杂度为 O(n * m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public: int nearestExit(vector\u0026lt;vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; maze, vector\u0026lt;int\u0026gt;\u0026amp; entrance) { int n = maze.size(), m = maze[0].size(); int INF = 0x3f3f3f3f; // 表示无穷大 std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; dist(n, std::vector\u0026lt;int\u0026gt;(m, INF)); dist[entrance[0]][entrance[1]] = 0; // 入口步数初始化为 0 using PII = std::pair\u0026lt;int, int\u0026gt;; std::queue\u0026lt;PII\u0026gt; q; q.push({entrance[0], entrance[1]}); // 定义四个移动方向 int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; while (q.size()) { PII t = q.front(); q.pop(); int x = t.first, y = t.second; for (int i = 0; i \u0026lt; 4; ++ i) // 尝试上下左右四个方向 { int a = x + dx[i], b = y + dy[i]; // 检查新位置是否有效 if (a \u0026gt;= 0 \u0026amp;\u0026amp; a \u0026lt; n \u0026amp;\u0026amp; b \u0026gt;= 0 \u0026amp;\u0026amp; b \u0026lt; m \u0026amp;\u0026amp; maze[a][b] == \u0026#39;.\u0026#39; \u0026amp;\u0026amp; dist[a][b] \u0026gt; dist[x][y] + 1) { dist[a][b] = dist[x][y] + 1; // 更新步数 // 如果新位置是出口（在边界上） if (a == 0 || a == n - 1 || b == 0 || b == m - 1) return dist[a][b]; q.push({a, b}); // 加入队列 } } } return -1; // 无法找到出口 } }; ","date":"2025-01-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/nearest-exit-from-entrance-in-maze/","title":"Nearest Exit From Entrance In Maze"},{"content":"1466. 重新规划路线 分析 建图： 将每条路线转化为一个无向图，并为每条边添加方向信息： 如果路线从城市 a -\u0026gt; b，则图中添加边 (a, b, true)（表示这条边需要变更方向） 同时添加反向边 (b, a, false)（表示这条边方向正确，无需变更） 深度优先搜索（DFS）： 从城市 0 开始遍历整个图，记录需要变更方向的边数： 如果当前边方向不正确（change == true），则需要修改方向，计数器 res 加 1 继续递归访问相邻的未访问城市，确保整个图的连通性 返回结果： 最终 res 的值即为需要变更方向的最小路线数 时间复杂度 图中包含 n - 1 条边，DFS 遍历每条边一次，时间复杂度为 O(n)\n空间复杂度 存储图的邻接表需要 O(n) 的空间 访问标记数组 visited 和递归栈的空间复杂度为 O(n) 总空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public: int res = 0; // 记录需要变更方向的路线数 // 深度优先搜索函数 void dfs(int city, std::vector\u0026lt;std::vector\u0026lt;std::pair\u0026lt;int, bool\u0026gt;\u0026gt;\u0026gt;\u0026amp; graph, std::vector\u0026lt;bool\u0026gt;\u0026amp; visited) { visited[city] = true; // 标记当前城市为已访问 for (auto t : graph[city]) { // 遍历所有相邻城市 int neighbor = t.first; bool change = t.second; if (!visited[neighbor]) { // 如果相邻城市未访问 if (change == true) // 如果需要改变方向 ++res; dfs(neighbor, graph, visited); // 递归访问相邻城市 } } } // 主函数 int minReorder(int n, vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; connections) { // 构建图 std::vector\u0026lt;std::vector\u0026lt;std::pair\u0026lt;int, bool\u0026gt;\u0026gt;\u0026gt; graph(n); for (auto conn : connections) { graph[conn[0]].push_back({conn[1], true}); // 原方向 graph[conn[1]].push_back({conn[0], false}); // 反方向 } // 初始化访问标记数组 std::vector\u0026lt;bool\u0026gt; visited(n, false); // 从城市 0 开始深度优先搜索 dfs(0, graph, visited); return res; // 返回需要变更方向的最小数目 } }; ","date":"2025-01-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reorder-routes-to-make-all-paths-lead-to-the-city-zero/","title":"Reorder Routes To Make All Paths Lead to The City Zero"},{"content":"450. 删除二叉搜索树中的节点 分析 节点为叶子节点： 如果删除的节点没有子节点，直接将其置为 nullptr 节点只有一个子节点： 如果删除的节点只有左子树或右子树，则将左子树或右子树上提 节点有两个子节点： 在这种情况下，删除节点无法直接处理。需要找到节点的后继节点（右子树中最小的节点） 找到右子树中的最小节点，将其值赋给当前删除节点，然后递归删除该后继节点（变为第二种情况） 时间复杂度 最坏情况下，树的高度为 O(h)，需要进行递归查找和删除节点操作，因此时间复杂度为 O(h)\n对于平衡的二叉搜索树，时间复杂度为 O(log n)；对于退化为链表的情况，时间复杂度为 O(n)\n空间复杂度 递归栈的空间复杂度为 O(h)，其中 h 是树的高度。对于平衡二叉搜索树，空间复杂度为 O(log n)；对于退化为链表的情况，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Solution { public: // 主函数，调用删除逻辑并返回根节点 TreeNode* deleteNode(TreeNode* root, int key) { del(root, key); // 调用删除操作 return root; // 返回更新后的根节点 } // 辅助函数：递归删除节点 void del(TreeNode* \u0026amp;root, int key) { if (!root) // 如果节点为空，直接返回 return; // 根据 key 和当前节点值的大小关系决定递归方向 if (root-\u0026gt;val \u0026gt; key) del(root-\u0026gt;left, key); // 在左子树中查找 else if (root-\u0026gt;val \u0026lt; key) del(root-\u0026gt;right, key); // 在右子树中查找 else // 找到目标节点 { // 情况 1：节点没有子节点，直接删除 if (!root-\u0026gt;left \u0026amp;\u0026amp; !root-\u0026gt;right) root = nullptr; // 情况 2：节点只有右子树 else if (!root-\u0026gt;left) root = root-\u0026gt;right; // 情况 3：节点只有左子树 else if (!root-\u0026gt;right) root = root-\u0026gt;left; // 情况 4：节点有两个子树 else { // 找右子树中最小的节点（后继节点） TreeNode* p = root-\u0026gt;right; while (p-\u0026gt;left) p = p-\u0026gt;left; // 将后继节点的值赋给当前节点 root-\u0026gt;val = p-\u0026gt;val; // 删除后继节点 del(root-\u0026gt;right, p-\u0026gt;val); } } } }; ","date":"2025-01-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/delete-node-in-a-bst/","title":"Delete Node In a BST"},{"content":"841. 钥匙和房间 分析 转化为图的遍历问题： 将每个房间看作一个节点，钥匙之间的关系看作图的边 我们需要从节点 0 开始，遍历整个图，检查是否能够访问所有的节点 深度优先搜索（DFS）： 使用递归的方式遍历图，从起点 0 开始访问 每访问一个节点（房间），将其标记为已访问 对于当前房间中的钥匙（指向其他房间的边），检查对应的房间是否已访问，未访问则递归继续 检查结果： 遍历完成后，检查是否所有房间都被访问过。如果有任何房间未被访问，则返回 false；否则返回 true 时间复杂度 每个房间和每把钥匙都会被访问一次，因此时间复杂度为 O(n + m)，其中 n 是房间的数量，m 是所有钥匙的总数量\n空间复杂度 图的存储需要 O(n + m) 的空间 访问标记数组占用 O(n) 的空间 递归栈的最大深度为 O(n) 总空间复杂度为 O(n + m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public: void dfs(int room, std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; rooms, std::vector\u0026lt;bool\u0026gt;\u0026amp; visited) { visited[room] = true; // 标记房间 room 已访问 for (int x : rooms[room]) // 遍历房间 room 中的钥匙 if (!visited[x]) // 如果钥匙指向的房间尚未访问 dfs(x, rooms, visited); // 递归访问该房间 } bool canVisitAllRooms(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; rooms) { int n = rooms.size(); // 房间数量 std::vector\u0026lt;bool\u0026gt; visited(n, false); // 初始化访问标记数组 dfs(0, rooms, visited); // 从房间 0 开始进行深度优先搜索 // 检查是否所有房间都被访问 for (int i = 0; i \u0026lt; n; ++ i) if (!visited[i]) // 如果有房间未访问，返回 false return false; return true; // 所有房间都被访问，返回 true } }; ","date":"2025-01-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/keys-and-rooms/","title":"Keys And Rooms"},{"content":"547. 省份数量 分析 图的模型： 每个城市看作图中的一个节点。 isConnected[i][j] = 1 表示城市 i 和 j 之间存在一条边 深度优先搜索（DFS）： 使用一个布尔数组 visited 来记录每个城市是否已访问 从某个未访问的城市出发，遍历与其直接或间接相连的所有城市，并标记为已访问 每次启动新的 DFS，意味着发现了一个新的省份 计数省份： 遍历所有城市，当发现一个未访问的城市时，启动一次 DFS，同时将省份数量加 1 时间复杂度 每个城市和每条边都会被访问一次，因此时间复杂度为 O(n^2)，其中 n 是城市数量\n空间复杂度 访问标记数组需要 O(n) 的空间 递归栈的深度最多为 O(n) 总空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: // 深度优先搜索函数 void dfs(int city, std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; isConnected, std::vector\u0026lt;bool\u0026gt;\u0026amp; visited) { visited[city] = true; // 标记当前城市为已访问 for (int neighbor = 0; neighbor \u0026lt; isConnected.size(); ++neighbor) if (isConnected[city][neighbor] == 1 \u0026amp;\u0026amp; !visited[neighbor]) dfs(neighbor, isConnected, visited); // 递归访问相邻城市 } // 主函数 int findCircleNum(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; isConnected) { int n = isConnected.size(); // 城市数量 std::vector\u0026lt;bool\u0026gt; visited(n, false); // 初始化访问标记数组 int provinces = 0; // 省份计数 // 遍历每个城市 for (int i = 0; i \u0026lt; n; ++i) if (!visited[i]) // 如果城市未被访问 { ++provinces; // 新发现一个省份 dfs(i, isConnected, visited); // 启动深度优先搜索 } return provinces; // 返回省份数量 } }; ","date":"2025-01-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/number-of-provinces/","title":"Number Of Provinces"},{"content":"1372. 二叉树中的最长交错路径 分析 路径的定义： 选择一个起始节点和方向： 如果前进方向是右，则移动到右子节点 如果前进方向是左，则移动到左子节点 每次移动后，改变方向继续前进 递归处理： 使用深度优先搜索（DFS）遍历整棵树 每次递归返回以：当前节点为起点的最长交错路径长度 方向参数： 每次递归调用带上方向参数 direction： 0 表示上一次路径方向为左 1 表示上一次路径方向为右 路径长度计算： 若当前节点移动方向为左，则当前路径长度为右子树返回值 + 1 若当前节点移动方向为右，则当前路径长度为左子树返回值 + 1 更新全局最大值 res 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(h)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public: int res = 0; // 全局变量记录最大交错路径长度 int longestZigZag(TreeNode* root) { dfs(root, 0); // 从根节点开始递归，初始方向为左 return res; } int dfs(TreeNode* root, int direction) { if (!root) // 空节点返回 0 return 0; // 递归计算左右子树的交错路径 int left = dfs(root-\u0026gt;left, 0); // 进入左子树，方向为左 int right = dfs(root-\u0026gt;right, 1); // 进入右子树，方向为右 // 更新最长交错路径 res = std::max(res, std::max(left, right)); // 返回以当前节点为起点的路径长度 if (direction == 0) // 当前方向为左 return right + 1; return left + 1; // 当前方向为右 } }; ","date":"2025-01-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-zigzag-path-in-a-binary-tree/","title":"Longest Zigzag Path In a Binary Tree"},{"content":"1161. 最大层内元素和 分析 层序遍历： 使用队列实现二叉树的层序遍历，从上到下逐层处理节点 每次处理完一层后，将当前层的所有子节点加入队列，供下一层处理 记录每层的总和： 对于每层的节点，计算该层所有节点值的总和 如果当前层的总和大于之前记录的最大总和，则更新最大总和，并记录当前层号 返回结果： 遍历完成后，返回总和最大的最小层号 时间复杂度 每个节点仅被访问一次，因此时间复杂度为 O(n)，其中 n 是二叉树的节点数\n空间复杂度 队列中最多存储一层的节点数，在最坏情况下（完全二叉树），空间复杂度为 O(w)，其中 w 是二叉树的最大宽度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public: int maxLevelSum(TreeNode* root) { if (!root) return 0; std::queue\u0026lt;TreeNode*\u0026gt; q; // 队列用于层序遍历 q.push(root); // 将根节点入队 int res = 0; // 保存最大总和对应的层号 int layer = 0; // 当前层号 int sum = INT_MIN; // 初始化最大总和为最小整数 while (q.size()) // 遍历队列直到为空 { layer ++ ; // 进入下一层，层号加 1 int cur = 0; // 当前层的总和 int len = q.size(); // 当前层的节点数 // 遍历当前层的所有节点 while (len -- ) { TreeNode* t = q.front(); q.pop(); if (t-\u0026gt;left) // 将左子节点加入队列 q.push(t-\u0026gt;left); if (t-\u0026gt;right) // 将右子节点加入队列 q.push(t-\u0026gt;right); cur += t-\u0026gt;val; // 累加当前节点的值 } // 如果当前层的总和大于之前的最大总和，更新 if (sum \u0026lt; cur) { sum = cur; // 更新最大总和 res = layer; // 更新最大总和对应的层号 } } return res; // 返回层号 } }; ","date":"2025-01-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximum-level-sum-of-a-binary-tree/","title":"Maximum Level Sum Of A Binary Tree"},{"content":"700. 二叉搜索树中的搜索 分析 递归查找： 如果当前节点为空，返回 null，表示未找到目标节点 如果当前节点的值等于目标值 val，返回该节点 如果当前节点的值大于 val，递归查找其左子树 如果当前节点的值小于 val，递归查找其右子树 返回结果： 如果找到目标节点，返回其为根的子树 如果未找到目标节点，返回 null 时间复杂度 最坏情况下为树的高度 O(h)，其中 h 是树的高度。对于平衡二叉搜索树，时间复杂度为 O(log n)\n对于退化为链表的情况，时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(h)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: TreeNode* searchBST(TreeNode* root, int val) { // 基本情况：当前节点为空，返回 nullptr if (!root) return nullptr; // 当前节点的值大于目标值，递归查找左子树 if (root-\u0026gt;val \u0026gt; val) return searchBST(root-\u0026gt;left, val); // 当前节点的值小于目标值，递归查找右子树 if (root-\u0026gt;val \u0026lt; val) return searchBST(root-\u0026gt;right, val); // 找到目标节点，返回该节点 return root; } }; ","date":"2025-01-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/search-in-a-binary-search-tree/","title":"Search In a Binary Search Tree"},{"content":"105. 从前序与中序遍历序列构造二叉树 分析 用一个哈希表 pos 记录中序遍历中每个值的索引，方便快速查找 递归构造子树： 子树的根节点对应前序遍历的第一个节点 根据根节点在中序遍历中的位置，划分左右子树的前序和中序区间 分别递归构造左子树和右子树 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public: std::unordered_map\u0026lt;int, int\u0026gt; pos; // 中序遍历中值到索引的映射 TreeNode* buildTree(vector\u0026lt;int\u0026gt;\u0026amp; preorder, vector\u0026lt;int\u0026gt;\u0026amp; inorder) { // 建立中序遍历的值索引映射，方便快速定位根节点位置 for (int i = 0; i \u0026lt; inorder.size(); ++i) pos[inorder[i]] = i; // 调用递归构造函数 return build(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1); } TreeNode* build(std::vector\u0026lt;int\u0026gt;\u0026amp; preorder, std::vector\u0026lt;int\u0026gt;\u0026amp; inorder, int pl, int pr, int il, int ir) { if (pl \u0026gt; pr) // 如果前序区间为空，返回空节点 return nullptr; // 当前子树的根节点 TreeNode* root = new TreeNode(preorder[pl]); // 根节点在中序遍历中的位置 int k = pos[root-\u0026gt;val]; // 递归构造左子树 root-\u0026gt;left = build(preorder, inorder, pl + 1, pl + 1 + k - 1 - il, il, k - 1); // 递归构造右子树 root-\u0026gt;right = build(preorder, inorder, pl + 1 + k - 1 - il + 1, pr, k + 1, ir); return root; } }; ","date":"2024-12-31T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/construct-binary-tree-from-preorder-and-inorder-traversal/","title":"Construct Binary Tree from Preorder and Inorder Traversal"},{"content":"114. 二叉树展开为链表 分析 遍历二叉树的每个节点 如果当前节点存在左子树： 将左子树的最右节点找到，并连接到当前节点的右子树上 然后将当前节点的右子树替换为它的左子树，同时将左子树置为 null 移动到当前节点的右子节点，继续上述操作，直至遍历完所有节点 时间复杂度 时间复杂度 O(n)，每个节点遍历一次，且寻找左子树最右节点的过程也是线性的\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: void flatten(TreeNode* root) { while (root) // 遍历每个节点 { TreeNode* p = root-\u0026gt;left; // 获取当前节点的左子树 if (p) // 如果存在左子树 { // 找到左子树的最右节点 while (p-\u0026gt;right) p = p-\u0026gt;right; // 将左子树的最右节点与右子树连接 p-\u0026gt;right = root-\u0026gt;right; // 将当前节点的右子树替换为左子树 root-\u0026gt;right = root-\u0026gt;left; // 将左子树置为 null root-\u0026gt;left = nullptr; } // 移动到右子节点 root = root-\u0026gt;right; } } }; ","date":"2024-12-31T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/flatten-binary-tree-to-linked-list/","title":"Flatten Binary Tree to Linked List"},{"content":"199. 二叉树的右视图 分析 层序遍历二叉树： 使用队列（queue）进行二叉树的层序遍历 每次遍历一层节点时，将当前层的所有节点按顺序加入队列，同时将下一层的子节点也加入队列 记录右视图的节点值： 在遍历当前层时，记录当前层的最后一个节点的值，即为该层右侧视图中可见的节点 返回结果： 所有层的最后一个节点值构成二叉树的右视图 时间复杂度 时间复杂度：O(n)，其中 n 是二叉树中的节点数\n空间复杂度 空间复杂度：O(w)，其中 w 是二叉树的最大宽度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public: vector\u0026lt;int\u0026gt; rightSideView(TreeNode* root) { std::vector\u0026lt;int\u0026gt; res; // 存储右视图节点值 std::queue\u0026lt;TreeNode*\u0026gt; q; // 队列用于层序遍历 if (!root) // 如果树为空，返回空结果 return res; q.push(root); // 将根节点加入队列 while (!q.empty()) { int len = q.size(); // 当前层的节点数量 while (len--) { TreeNode* temp = q.front(); // 获取当前层的节点 q.pop(); // 如果是当前层的最后一个节点，加入结果集 if (len == 0) res.push_back(temp-\u0026gt;val); // 将左子节点加入队列 if (temp-\u0026gt;left) q.push(temp-\u0026gt;left); // 将右子节点加入队列 if (temp-\u0026gt;right) q.push(temp-\u0026gt;right); } } return res; // 返回右视图节点值 } }; ","date":"2024-12-30T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/binary-tree-right-side-view/","title":"Binary Tree Right Side View"},{"content":"230. 二叉搜索树中第K小元素 分析 使用栈实现中序遍历 中序遍历按照「左 -\u0026gt; 根 -\u0026gt; 右」的顺序访问节点，因此可以使用显式栈来模拟递归 通过遍历左子树进入更小的节点，将节点依次压栈，直到左子树遍历完成 计数找到第 k 小的元素 每次弹出栈顶节点时，计数器 k 减一，直到 k = 0 时，当前节点即为第 k 小的节点 右子树遍历 在访问根节点后，转向右子树继续重复上述过程 时间复杂度 时间复杂度 O(h + k)，其中 h 是树的高度，k 是目标元素的位置。我们最多遍历 k 个节点，同时需要沿着树的高度走到叶节点\n空间复杂度 空间复杂度：O(h)，栈的最大深度为树的高度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: int kthSmallest(TreeNode* root, int k) { std::stack\u0026lt;TreeNode*\u0026gt; stk; // 显式栈用于模拟递归 while (root || !stk.empty()) { // 遍历左子树，将节点压栈 while (root) { stk.push(root); root = root-\u0026gt;left; } // 处理当前节点 root = stk.top(); // 获取栈顶元素 stk.pop(); if (--k == 0) // 如果 k 减到 0，说明找到第 k 小的节点 return root-\u0026gt;val; // 转向右子树 root = root-\u0026gt;right; } return 0; // 理论上不会执行到这里，保证函数返回值 } }; ","date":"2024-12-30T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/kth-smallest-element-in-a-bst/","title":"Kth Smallest Element In A Bst"},{"content":"236. 二叉树的最近公共祖先 分析 使用递归深度优先搜索（DFS）\n状态定义： 如果某个子树中找到了 p ，则返回状态 1 如果找到了 q ，则返回状态 2 如果同时找到 p 和 q，状态为 3 递归逻辑： 对当前节点的左右子树分别进行递归 将左右子树的状态结果与当前节点的状态进行合并（按位或操作） 如果当前节点的状态等于 3 且最近公共祖先 res 尚未被赋值，则更新 res 终止条件\n当 p 和 q 在同一个子树中时，递归会找到它们最近的公共祖先并停止 如果两个节点在不同子树中，递归逻辑会找到分叉点，即最近公共祖先 时间复杂度 时间复杂度 O(n)，其中 n 是二叉树的节点总数，每个节点访问一次\n空间复杂度 空间复杂度为 O(h)，其中 h 是二叉树的高度，递归调用栈的深度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: TreeNode* res = nullptr; // 存储最近公共祖先节点 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { dfs(root, p, q); // 执行深度优先搜索 return res; // 返回最近公共祖先 } int dfs(TreeNode* root, TreeNode* p, TreeNode* q) { int state = 0; // 当前节点的状态 if (!root) return state; // 如果当前节点为空，返回状态 0 if (root == p) state |= 1; // 如果找到节点 p，更新状态为 1 else if (root == q) state |= 2; // 如果找到节点 q，更新状态为 2 // 递归左右子树 state |= dfs(root-\u0026gt;left, p, q); state |= dfs(root-\u0026gt;right, p, q); // 如果当前状态为 3（即同时找到 p 和 q），且 res 尚未更新，更新 res if (state == 3 \u0026amp;\u0026amp; !res) res = root; return state; // 返回当前节点的状态 } }; ","date":"2024-12-30T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/lowest-common-ancestor-of-a-binary-tree/","title":"Lowest Common Ancestor Of A Binary Tree"},{"content":"124. 二叉树中的最大路径和 分析 递归定义： 对于每个节点，我们通过递归计算以该节点为根的路径的最大贡献值（即该节点能为父节点提供的最大路径和） 贡献值的计算： 对于当前节点 root ： 左子树提供的最大路径和 left = max(0, dfs(root-\u0026gt;left))。取 0 是为了避免负贡献 右子树提供的最大路径和 right = max(0, dfs(root-\u0026gt;right)) 更新全局最大路径和： 对于当前节点，路径和可能包含其左右子树的贡献值，因此计算路径和为：left + root-\u0026gt;val + right 更新全局变量 res：res = max(res, left + root-\u0026gt;val + right) 返回贡献值： 当前节点对父节点的最大贡献值为：root-\u0026gt;val + max(left, right)，只取左右子树中的一个路径，避免形成环 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(h)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public: int res = INT_MIN; // 全局变量，用于记录最大路径和 int maxPathSum(TreeNode* root) { dfs(root); // 调用递归函数 return res; // 返回最终结果 } int dfs(TreeNode* root) { if (!root) // 如果当前节点为空，返回0 return 0; // 递归计算左、右子树的最大贡献值，负值取0 int left = std::max(0, dfs(root-\u0026gt;left)); int right = std::max(0, dfs(root-\u0026gt;right)); // 更新全局最大路径和 res = std::max(res, left + root-\u0026gt;val + right); // 返回当前节点对父节点的最大贡献值 return root-\u0026gt;val + std::max(left, right); } }; ","date":"2024-12-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/binary-tree-maximum-path-sum/","title":"Binary Tree Maximum Path Sum"},{"content":"1448. 统计二叉树中好节点 分析 递归遍历： 使用深度优先搜索 (DFS) 遍历树中所有节点 在遍历过程中，记录从根节点到当前节点路径上的最大值 max_val 判断当前节点是否为好节点： 如果当前节点的值大于或等于 max_val，说明该节点是好节点，计数器加一，并更新路径上的最大值为当前节点的值 继续递归左右子树： 对当前节点的左右子树递归调用，传递更新后的 max_val 累加结果： 将左子树和右子树的好节点数量加上当前节点的结果，返回总计数 时间复杂度 每个节点只访问一次，因此时间复杂度为 O(n)，其中 n 是树中节点的数量\n空间复杂度 递归调用栈的空间复杂度为 O(h)，其中 h 是树的高度。在最坏情况下（单链树），空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: int goodNodes(TreeNode* root) { // 从根节点开始深度优先搜索，初始路径的最大值为最小整数 return dfs(root, INT_MIN); } int dfs(TreeNode* root, int max_val) { // 如果节点为空，返回0（没有好节点） if (!root) return 0; int res = 0; // 判断当前节点是否为好节点 if (root-\u0026gt;val \u0026gt;= max_val) { res = 1; // 当前节点是好节点 max_val = root-\u0026gt;val; // 更新路径上的最大值 } // 递归左右子树并累加好节点数量 return res + dfs(root-\u0026gt;left, max_val) + dfs(root-\u0026gt;right, max_val); } }; ","date":"2024-12-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/count-good-nodes-in-binary-tree/","title":"Count Good Nodes In Binary Tree"},{"content":"437. 路径总和III 分析 使用前缀和优化路径求和\n前缀和定义：到当前节点的路径和（包括当前节点） 公式：假设当前路径的前缀和为 cur，目标和为 targetSum，那么我们需要寻找路径的前缀和 cur - targetSum 若前缀和 cur - targetSum 存在，说明存在一条路径满足路径和等于 targetSum 具体步骤\n使用哈希表 count 存储前缀和及其出现的次数： count[0] = 1 初始化，表示路径和为 0 的路径有 1 条（即从根节点开始时） 深度优先搜索（DFS）遍历树的每个节点，维护当前路径的前缀和： 更新当前节点的前缀和 cur 检查是否存在路径满足 cur - targetSum 将当前路径的前缀和存入哈希表 递归遍历左右子树 回溯时，删除当前节点对前缀和的贡献（将 count[cur]--） 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(h)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public: std::unordered_map\u0026lt;long, int\u0026gt; count; // 哈希表存储前缀和及其出现次数 int res = 0; // 记录满足条件的路径数 int pathSum(TreeNode* root, int targetSum) { count[0] = 1; // 初始化路径和为 0 的路径有 1 条 dfs(root, targetSum, 0); // 开始 DFS 遍历 return res; // 返回结果 } void dfs(TreeNode* root, int targetSum, long cur) { if (!root) return; // 如果当前节点为空，直接返回 cur += root-\u0026gt;val; // 更新当前路径和 res += count[cur - targetSum]; // 检查是否存在前缀和满足条件 count[cur]++; // 更新哈希表，记录当前路径和 dfs(root-\u0026gt;left, targetSum, cur); // 遍历左子树 dfs(root-\u0026gt;right, targetSum, cur); // 遍历右子树 count[cur]--; // 回溯时移除当前节点对前缀和的贡献 } }; ","date":"2024-12-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/path-sum-iii/","title":"Path Sum III"},{"content":"872. 叶子相似的树 分析 深度优先搜索 (DFS)： 使用递归遍历二叉树 如果当前节点是叶子节点（没有左子节点和右子节点），将其值加入到结果序列中 如果不是叶子节点，则递归遍历其左子树和右子树 提取叶值序列： 对 root1 和 root2 分别进行深度优先搜索，提取两个叶值序列 比较序列： 判断两个序列是否相等，如果相等则返回 true，否则返回 false 时间复杂度 深度优先搜索： 对每棵树的所有节点遍历一次，时间复杂度为 O(n) ，其中 n 是节点数量 比较序列： 比较两个序列，时间复杂度为 O(min(n, m)) ，其中 n 和 m 分别为两棵树叶子节点的数量 总体时间复杂度为 O(n)\n空间复杂度 递归调用栈： 在最坏情况下（树的深度为树的高度），递归调用栈需要 O(h) 的空间，其中 h 是树的高度 存储叶值序列： 每棵树的叶子节点最多为树节点总数的一半，存储序列的空间复杂度为 O(n) 总体空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: // 深度优先搜索，提取叶子节点的值 void dfs(TreeNode* root, std::vector\u0026lt;int\u0026gt;\u0026amp; res) { // 如果当前节点为空，直接返回 if (!root) return; // 如果当前节点是叶子节点，记录其值 if (!root-\u0026gt;left \u0026amp;\u0026amp; !root-\u0026gt;right) res.push_back(root-\u0026gt;val); // 递归遍历左子树和右子树 dfs(root-\u0026gt;left, res); dfs(root-\u0026gt;right, res); } bool leafSimilar(TreeNode* root1, TreeNode* root2) { // 分别存储两棵树的叶值序列 std::vector\u0026lt;int\u0026gt; a, b; // 获取第一棵树的叶值序列 dfs(root1, a); // 获取第二棵树的叶值序列 dfs(root2, b); // 比较两棵树的叶值序列 return a == b; } }; ","date":"2024-12-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/leaf-similar-trees/","title":"Leaf Similar Trees"},{"content":"2130. 链表最大孪生和 分析 遍历链表： 第一次遍历链表计算长度 n 在第一次遍历的同时，将前 n/2 个节点的值压入栈中 计算孪生和： 从第 n/2 个节点开始，第二次遍历链表 弹出栈顶元素，与当前节点值相加计算孪生和，更新最大值 返回结果： 遍历完成后，返回最大孪生和 时间复杂度 计算长度：遍历链表一次，时间复杂度为 O(n) 计算孪生和：再次遍历后半部分链表，时间复杂度为 O(n/2) 总体时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: int pairSum(ListNode* head) { // 栈用于存储前半部分链表节点的值 std::stack\u0026lt;int\u0026gt; stk; // 第一次遍历计算链表长度 int len = 0; for (ListNode* p = head; p; p = p-\u0026gt;next) ++len; // 第二次遍历，将前半部分节点值压入栈中 ListNode* p = head; for (int i = 0; i \u0026lt; len / 2; ++i) { stk.push(p-\u0026gt;val); p = p-\u0026gt;next; } // 计算最大孪生和 int res = 0; while (p) { // 弹出栈顶值，与当前节点值相加 res = std::max(res, p-\u0026gt;val + stk.top()); stk.pop(); p = p-\u0026gt;next; } return res; } }; ","date":"2024-12-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximum-twin-sum-of-a-linked-list/","title":"Maximum Twin Sum of A Linked List"},{"content":"328. 奇偶链表 分析 特例处理： 如果链表为空，或只有一个节点，则无需操作，直接返回原链表 分离奇偶链表： 定义两个链表头指针： odd_head：奇数节点的头指针，初始为 head even_head：偶数节点的头指针，初始为 head-\u0026gt;next 遍历链表： 用 odd_tail 和 even_tail 分别维护奇数链表和偶数链表的尾节点 根据节点的索引位置，交替更新奇数链表和偶数链表的尾节点 拼接奇偶链表： 将奇数链表的尾节点连接到偶数链表的头节点 将偶数链表的尾节点指向 nullptr 返回结果： 返回奇数链表的头节点 odd_head 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public: ListNode* oddEvenList(ListNode* head) { // 特例处理：链表为空或只有一个节点 if (!head || !head-\u0026gt;next) return head; // 初始化奇数链表和偶数链表的头尾指针 ListNode *odd_head = head, *odd_tail = odd_head; ListNode *even_head = head-\u0026gt;next, *even_tail = even_head; // 遍历链表，交替处理奇数节点和偶数节点 for (ListNode* p = head-\u0026gt;next-\u0026gt;next; p;) { // 处理奇数节点 odd_tail = odd_tail-\u0026gt;next = p; p = p-\u0026gt;next; // 处理偶数节点 if (p) { even_tail = even_tail-\u0026gt;next = p; p = p-\u0026gt;next; } } // 拼接奇数链表和偶数链表 odd_tail-\u0026gt;next = even_head; even_tail-\u0026gt;next = nullptr; return odd_head; } }; ","date":"2024-12-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/odd-even-linked-list/","title":"Odd Even Linked List"},{"content":"2095. 删除链表的中间节点 分析 特例处理： 如果链表为空，直接返回 nullptr 如果链表只有一个节点，删除后返回 nullptr 如果链表只有两个节点，删除第二个节点，直接将 head-\u0026gt;next 设为 nullptr 计算链表长度： 遍历链表，计算其长度 n 定位中间节点的前一个节点： 根据 n / 2 ，找到中间节点的前一个节点 将该节点的 next 指针指向中间节点的下一个节点，从而跳过中间节点 返回结果： 返回删除中间节点后的链表头节点 时间复杂度 计算链表长度：需要遍历链表一次，时间复杂度为 O(n) 定位中间节点的前一个节点：需要遍历链表一次，时间复杂度为 O(n) 总时间复杂度：O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: ListNode* deleteMiddle(ListNode* head) { // 如果链表只有一个节点 if (!head-\u0026gt;next) return nullptr; // 如果链表只有两个节点 if (!head-\u0026gt;next-\u0026gt;next) { head-\u0026gt;next = nullptr; return head; } // 计算链表长度 int len = 0; for (ListNode* p = head; p; p = p-\u0026gt;next) ++len; // 找到中间节点的前一个节点 ListNode* p = head; for (int i = 0; i \u0026lt; len / 2 - 1; ++i) p = p-\u0026gt;next; // 删除中间节点 p-\u0026gt;next = p-\u0026gt;next-\u0026gt;next; return head; } }; ","date":"2024-12-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/delete-the-middle-node-of-a-linked-list/","title":"Delete The Middle Node of A Linked List"},{"content":"649. Dota2参议院 分析 使用两个队列分别存储两派参议员的索引位置，模拟投票过程：\n遍历输入字符串 senate： 将 'R' 的索引存入队列 r 将 'D' 的索引存入队列 d 模拟投票过程： 每轮从两个队列中各取出一个参议员的索引 索引较小的一方（出场顺序在前）可以禁言对方阵营的参议员，并将自己放回队列末尾，索引增加 n（表示其下一轮出场的顺序） 索引较大的参议员失去投票权，从队列中移除 重复上述过程，直到某一队列为空 判断结果： 如果 r 非空，返回 \u0026quot;Radiant\u0026quot; 如果 d 非空，返回 \u0026quot;Dire\u0026quot; 时间复杂度 初始化队列：O(n)，遍历字符串一次 模拟投票：每次至少移除一个参议员，最多进行 O(n) 次 总时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: string predictPartyVictory(string senate) { std::queue\u0026lt;int\u0026gt; r, d; // 分别存储两派参议员的索引 int n = senate.size(); // 初始化队列 for (int i = 0; i \u0026lt; n; ++i) if (senate[i] == \u0026#39;R\u0026#39;) r.push(i); else d.push(i); // 模拟投票过程 while (!r.empty() \u0026amp;\u0026amp; !d.empty()) { int r_index = r.front(); int d_index = d.front(); r.pop(); d.pop(); // 索引较小的参议员胜出，加入下一轮 if (r_index \u0026lt; d_index) r.push(r_index + n); // Radiant 禁言 Dire else d.push(d_index + n); // Dire 禁言 Radiant } // 判断获胜阵营 return r.empty() ? \u0026#34;Dire\u0026#34; : \u0026#34;Radiant\u0026#34;; } }; ","date":"2024-12-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/dota-2-senate/","title":"Dota 2 Senate"},{"content":"933. 最近的请求次数 分析 利用队列的先进先出特性来存储请求的时间点，并维护一个滑动窗口：\n存储请求时间： 每次调用 ping(t)，将时间 t 加入队列 移除过期请求： 从队列头部开始检查，如果某个时间点不在 [t-3000, t] 范围内（即 t - q.front() \u0026gt; 3000），则将其从队列中移除 统计有效请求： 最后，队列的大小即为过去 3000 毫秒内的请求数量 时间复杂度 时间复杂度 O(1)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class RecentCounter { public: std::queue\u0026lt;int\u0026gt; q; // 用于存储请求时间点的队列 RecentCounter() { // 构造函数初始化，无需额外操作 } int ping(int t) { q.push(t); // 将当前请求时间添加到队列 // 移除不在 [t-3000, t] 范围内的请求 while (t - q.front() \u0026gt; 3000) q.pop(); return q.size(); // 返回队列大小，即为有效请求数 } }; ","date":"2024-12-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/number-of-recent-calls/","title":"Number of Recent Calls"},{"content":"735. 小行星碰撞 分析 栈的意义：用一个栈 res 保存当前未爆炸的小行星。栈中元素表示这些小行星在当前状态下可以安全存在 碰撞处理逻辑： 遍历数组中的每颗小行星： 如果小行星向右移动（正数），直接将其加入栈中 如果小行星向左移动（负数），检查栈顶元素： 如果栈顶是向右移动的小行星且比当前小行星小（绝对值），栈顶小行星爆炸，当前小行星继续向左检测 如果栈顶是向右移动的小行星且与当前小行星大小相等，两者都爆炸 如果栈为空或栈顶为负数（同向移动），当前小行星直接入栈 最终状态： 遍历完成后，栈中的小行星即为剩下的未爆炸小行星 时间复杂度 每颗小行星最多入栈和出栈一次 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: vector\u0026lt;int\u0026gt; asteroidCollision(vector\u0026lt;int\u0026gt;\u0026amp; asteroids) { std::vector\u0026lt;int\u0026gt; res; // 用作栈 for (int x : asteroids) { if (x \u0026gt; 0) res.push_back(x); // 向右移动的小行星直接入栈 else { // 检测栈顶的小行星是否与当前小行星碰撞 while (res.size() \u0026amp;\u0026amp; res.back() \u0026gt; 0 \u0026amp;\u0026amp; res.back() \u0026lt; -x) res.pop_back(); // 栈顶小行星爆炸 // 检查是否当前小行星与栈顶大小相等 if (res.size() \u0026amp;\u0026amp; res.back() == -x) res.pop_back(); // 两颗小行星同时爆炸 // 栈为空或栈顶为负数，当前小行星入栈 else if (res.empty() || res.back() \u0026lt; 0) res.push_back(x); } } return res; } }; ","date":"2024-12-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/asteroid-collision/","title":"Asteroid Collision"},{"content":"2390. 从字符串中移除星号 分析 利用栈的性质： 遍历字符串 s，将每个字符逐个处理 如果遇到非星号字符，将其加入结果字符串 res 中（模拟入栈操作） 如果遇到星号 *，移除结果字符串 res 的最后一个字符（模拟出栈操作） 最终结果： 遍历完成后，结果字符串 res 即为移除所有星号及其左侧字符后的字符串 时间复杂度 每个字符只被处理一次 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: string removeStars(string s) { std::string res; // 用作结果栈 for (auto c : s) { if (c == \u0026#39;*\u0026#39;) res.pop_back(); // 移除最后一个字符 else res.push_back(c); // 添加当前字符 } return res; } }; ","date":"2024-12-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/removing-stars-from-a-string/","title":"Removing Stars from a String"},{"content":"1657. 确定两个字符串是否接近 分析 长度检查： 如果 word1 和 word2 的长度不同，则它们无法接近，直接返回 false 字符集合一致性检查： 两个字符串需要包含相同的字符集合。例如，word1 = \u0026quot;abc\u0026quot; 和 word2 = \u0026quot;bca\u0026quot; 包含的字符都是 {a, b, c}，可以进行字符交换；而 word1 = \u0026quot;abc\u0026quot; 和 word2 = \u0026quot;bcd\u0026quot; 的字符集合不同，不可能接近 频次匹配： 统计每个字符串中字符的频次，并将频次排序。若两个字符串的字符频次分布相同，则可以通过变换使其互相接近 时间复杂度 统计字符频次的时间为 O(n)，其中 n 是字符串的长度 排序频次数组的时间为 O(26log26)，常数级别可视为 O(1) 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: bool closeStrings(string word1, string word2) { // 如果长度不同，直接返回 false if (word1.size() != word2.size()) return false; // 统计两个字符串中每个字符的频次 std::vector\u0026lt;int\u0026gt; cnt1(26, 0); std::vector\u0026lt;int\u0026gt; cnt2(26, 0); for (int i = 0; i \u0026lt; word1.size(); ++i) { ++cnt1[word1[i] - \u0026#39;a\u0026#39;]; ++cnt2[word2[i] - \u0026#39;a\u0026#39;]; } // 检查两个字符串是否包含相同的字符集合 for (int i = 0; i \u0026lt; 26; ++i) { if ((cnt1[i] \u0026amp;\u0026amp; !cnt2[i]) || (!cnt1[i] \u0026amp;\u0026amp; cnt2[i])) return false; } // 对频次数组排序并检查是否相同 std::sort(cnt1.begin(), cnt1.end()); std::sort(cnt2.begin(), cnt2.end()); return cnt1 == cnt2; } }; ","date":"2024-12-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/determine-if-two-strings-are-close/","title":"Determine If Two Strings Are Close"},{"content":"2352. 相等行列对 分析 逐行逐列比较： 使用两层循环遍历矩阵，外层遍历行，内层遍历列 对每个行列对 (R_i, C_j)，检查对应元素是否相等 逐元素对比： 对行 R_i 和列 C_j 的每个元素进行对比，若所有元素均相等，则计数加 1 计数统计： 遍历结束后返回满足条件的行列对数量 时间复杂度 外层两层循环 O(n^2)，用于遍历所有行列对 内层循环 O(n)，用于比较行和列的元素 总时间复杂度为 O(n^3)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: int equalPairs(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; grid) { int n = grid.size(); int res = 0; // 遍历每一行和每一列 for (int i = 0; i \u0026lt; n; ++i) { for (int j = 0; j \u0026lt; n; ++j) { int k = 0; // 对比行和列的每个元素 while (k \u0026lt; n \u0026amp;\u0026amp; grid[i][k] == grid[k][j]) ++k; // 如果行和列完全匹配 if (k == n) ++res; } } return res; } }; ","date":"2024-12-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/equal-row-and-column-pairs/","title":"Equal Row and Column Pairs"},{"content":"1207. 独一无二的出现次数 分析 统计出现次数： 使用哈希表 cnt 统计数组中每个元素的出现次数，键为元素值，值为该元素的出现次数 判断次数是否唯一： 使用集合 hash 存储所有出现次数。 遍历哈希表 cnt，如果发现某次出现次数已存在于集合中，则返回 false 如果遍历结束后没有重复次数，返回 true 时间复杂度 统计元素出现次数的时间是 O(n)，其中 n 是数组的长度 检查次数唯一性的时间是 O(m)，其中 m 是数组中不同元素的个数 总时间复杂度为 O(n + m)\n空间复杂度 空间复杂度为 O(m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public: bool uniqueOccurrences(vector\u0026lt;int\u0026gt;\u0026amp; arr) { // 统计元素出现次数 std::unordered_map\u0026lt;int, int\u0026gt; cnt; for (int x : arr) ++cnt[x]; // 检查出现次数是否唯一 std::unordered_set\u0026lt;int\u0026gt; hash; for (auto item : cnt) { // 如果某个次数已存在，返回 false if (hash.count(item.second)) return false; hash.insert(item.second); } // 如果所有次数都唯一，返回 true return true; } }; ","date":"2024-12-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/unique-number-of-occurrences/","title":"Unique Number of Occurrences"},{"content":"724. 寻找数组的中心下标 分析 总和计算： 首先计算整个数组的总和 sum 逐个检查下标： 遍历数组，维护一个变量 s，记录当前位置左侧所有元素的和 判断当前下标是否满足条件：左侧和 s 是否等于右侧和 sum - s - nums[i] 如果条件满足，返回当前下标 i 否则，更新左侧和 s 为 s + nums[i] 无解处理： 如果遍历结束仍未找到符合条件的下标，返回 -1 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: int pivotIndex(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int sum = 0; for (int num : nums) // 计算数组总和 sum += num; int s = 0; // 左侧和初始化为 0 for (int i = 0; i \u0026lt; nums.size(); ++i) { // 判断当前下标是否满足条件 if (s == sum - s - nums[i]) return i; // 更新左侧和 s += nums[i]; } return -1; // 未找到中心下标 } }; ","date":"2024-12-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-pivot-index/","title":"Find Pivot Index"},{"content":"2215. 找出两数组的不同 分析 使用哈希集合存储唯一元素： 为了处理数组中重复的元素，我们使用两个集合 hash1 和 hash2 分别存储 nums1 和 nums2 的唯一值 查找差集： 遍历集合 hash1，找出不在 hash2 中的元素，存入 res[0] 遍历集合 hash2，找出不在 hash1 中的元素，存入 res[1] 返回结果： 返回结果列表 res，包含两个子列表 时间复杂度 遍历数组构建集合的时间是 O(n + m) 遍历集合查找差集的时间是 O(n + m) 总时间复杂度：O(n + m)，其中 n 和 m 分别是 nums1 和 nums2 的长度\n空间复杂度 空间复杂度：O(u + v)，其中 u 和 v 是 nums1 和 nums2 中的不同元素个数\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; findDifference(vector\u0026lt;int\u0026gt;\u0026amp; nums1, vector\u0026lt;int\u0026gt;\u0026amp; nums2) { // 使用集合存储唯一元素 std::unordered_set\u0026lt;int\u0026gt; hash1; std::unordered_set\u0026lt;int\u0026gt; hash2; // 将 nums1 的元素加入 hash1 for (int num : nums1) hash1.insert(num); // 将 nums2 的元素加入 hash2 for (int num : nums2) hash2.insert(num); std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res(2); // 找出 nums1 中不在 nums2 中的元素 for (int num : hash1) if (!hash2.count(num)) res[0].push_back(num); // 找出 nums2 中不在 nums1 中的元素 for (int num : hash2) if (!hash1.count(num)) res[1].push_back(num); return res; } }; ","date":"2024-12-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-the-difference-of-two-arrays/","title":"Find The Difference Of Two Arrays"},{"content":"1732. 找到最高海拔 分析 初始海拔 h 为 0，最高海拔 res 也初始化为 0 遍历数组 gain： 累加当前海拔高度 h += g ，其中 g 为当前的海拔差 更新最高海拔 res = max(res, h) 遍历完成后，返回 res 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public: int largestAltitude(vector\u0026lt;int\u0026gt;\u0026amp; gain) { int res = 0; int h = 0; for (int g : gain) { h += g; res = std::max(res, h); } return res; } }; ","date":"2024-12-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-the-highest-altitude/","title":"Find The Highest Altitude"},{"content":"1493. 删掉一个元素以后全为1的最长子数组 分析 定义两个指针 i 和 j，表示滑动窗口的左右边界 使用变量 cnt 记录窗口中 0 的个数 遍历数组 nums ： 如果当前元素是 0，则将 cnt 加 1 如果 cnt \u0026gt; 1 ，说明窗口内多于一个 0，不符合题目要求，需要将左指针 j 向右移动，并根据移出的元素更新 cnt 每次更新窗口长度 res = max(res, i - j + 1) 遍历完成后，结果为 res - 1，因为必须删除一个元素 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: int longestSubarray(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = 0; // 最大连续 1 的长度 int cnt = 0; // 当前窗口中 0 的数量 for (int i = 0, j = 0; i \u0026lt; nums.size(); ++i) { if (nums[i] == 0) ++cnt; // 遇到 0，增加计数 while (cnt \u0026gt; 1) // 如果窗口内 0 的数量超过 1 { if (nums[j] == 0) --cnt; // 左指针移出 0，减少计数 ++j; // 缩小窗口 } res = std::max(res, i - j + 1); // 更新最大长度 } return res - 1; // 因为必须删掉一个元素 } }; ","date":"2024-12-23T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-subarray-of-1-after-deleting-one-element/","title":"Longest Subarray of 1 After Deleting one Element"},{"content":"485. 最大连续1的个数 分析 遍历数组 nums ，逐一检查每个元素： 如果当前元素是 0，跳过，继续处理下一个元素 如果当前元素是 1，启动一个内部循环计算当前连续 1 的长度 记录每次计算到的连续 1 的长度，用变量 res 保持记录最大值 在外部循环中，将索引移动到连续 1 的区间结束位置，避免重复计算 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public: int findMaxConsecutiveOnes(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = 0; // 最大连续 1 的长度 for (int i = 0; i \u0026lt; nums.size(); ++i) // 遍历数组 { if (nums[i] == 0) // 如果当前是 0，跳过 continue; int j = i + 1; // 进入内部循环，计算连续 1 的长度 while (j \u0026lt; nums.size() \u0026amp;\u0026amp; nums[j] == 1) ++j; res = std::max(res, j - i); // 更新最大连续长度 i = j; // 将索引移动到连续 1 的区间结束位置 } return res; } }; ","date":"2024-12-23T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/max-consecutive-ones/","title":"Max Consecutive Ones"},{"content":"1004. 最大连续1的个数III 分析 定义两个指针 i 和 j，表示滑动窗口的左右边界 使用变量 cnt 记录当前窗口中 0 的个数 遍历数组 nums ： 如果遇到 0，增加 cnt 当 cnt \u0026gt; k 时，说明当前窗口内翻转 0 的数量超出了限制，需要将左指针 j 向右移动，并且如果移除的元素是 0，更新 cnt 每次移动右指针 i 时，计算当前窗口的长度 res = max(res, i - j + 1) 遍历完成后，返回最大窗口长度 res 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: int longestOnes(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { int res = 0; // 最大连续 1 的长度 int cnt = 0; // 当前窗口内 0 的数量 for (int i = 0, j = 0; i \u0026lt; nums.size(); ++i) { if (nums[i] == 0) ++cnt; // 遇到 0，增加计数 while (cnt \u0026gt; k) // 如果翻转的 0 超过限制 { if (nums[j] == 0) --cnt; // 左指针移除 0，减少计数 ++j; // 缩小窗口 } res = std::max(res, i - j + 1); // 更新最大长度 } return res; } }; ","date":"2024-12-23T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/max-consecutive-ones-iii/","title":"Max Consecutive Ones III"},{"content":"1679. K和数对的最大数目 分析 使用哈希表记录出现次数： 用一个哈希表 hash 来记录数组中每个整数出现的次数 查找是否能形成和为 k 的配对： 遍历数组中的每个元素 num，计算目标值 target = k - num 如果哈希表中存在目标值 target 且次数大于 0，则找到一对符合条件的整数，计数器 res 增加 1，并减少 target 的计数 如果目标值 target 不存在，将当前元素 num 的计数加到哈希表中 返回最大操作数： 遍历完成后，结果存储在 res 中，表示可以执行的最大操作数 时间复杂度 遍历数组，时间复杂度为 O(n) 每次哈希表的插入与查询时间复杂度为 O(1)，总体复杂度为 O(n) 空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public: int maxOperations(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { std::unordered_map\u0026lt;int, int\u0026gt; hash; // 哈希表记录元素出现次数 int res = 0; // 记录最大操作数 for (int num : nums) // 遍历数组 { int target = k - num; // 计算需要配对的目标值 if (hash[target] \u0026gt; 0) // 如果目标值存在于哈希表中 { --hash[target]; // 配对目标值计数减一 ++res; // 操作数加一 } else { ++hash[num]; // 当前元素加入哈希表 } } return res; } }; ","date":"2024-12-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/max-number-of-k-sum-pairs/","title":"Max Number of K Sum Pairs"},{"content":"1456. 定长子串中元音的最大数目 分析 定义一个固定长度为 k 的窗口，用变量 cur 记录窗口内的元音字母数量 遍历字符串 s 当加入一个新字符时，若其为元音，则增加 cur 若窗口大小超过 k，移除窗口左端字符，若其为元音，则减少 cur 在窗口大小达到 k 时，更新最大元音数量 res 遍历结束后，返回结果 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: int maxVowels(string s, int k) { std::unordered_set\u0026lt;char\u0026gt; hash = {\u0026#39;a\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;i\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;u\u0026#39;}; // 元音集合 int res = 0, cur = 0; // res: 最大元音数量, cur: 当前窗口元音数量 for (int i = 0, j = 0; i \u0026lt; s.size(); ++i) { if (hash.count(s[i])) // 判断当前字符是否为元音 ++cur; if (i - j + 1 \u0026gt; k) // 如果窗口超过长度 k { if (hash.count(s[j])) // 窗口左端字符是否为元音 --cur; ++j; // 移动左端 } if (i \u0026gt;= k - 1) // 当窗口长度达到 k 时，更新结果 res = std::max(res, cur); } return res; } }; ","date":"2024-12-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximum-number-of-vowels-in-a-substring-of-given-length/","title":"Maximum Number of Vowels In a Substring Of Given Length"},{"content":"643. 子数组最大平均数I 分析 用一个变量 sum 表示当前窗口中 k 个数的和 遍历数组，依次更新 sum，当窗口大小达到 k 时，计算当前窗口的平均值，并尝试更新最大平均值 如果窗口大小超过 k，将窗口的左端值从 sum 中减去，同时将窗口右端的新值加上 遍历结束后，返回最大平均值 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public: double findMaxAverage(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { double max_average = INT_MIN; // 用于存储最大平均数 double sum = 0; // 当前窗口的总和 for (int i = 0, j = 0; i \u0026lt; nums.size(); i++) // 遍历数组 { sum += nums[i]; // 将当前元素加入窗口 if (i - j + 1 \u0026gt; k) // 如果窗口大小超过 k sum -= nums[j++]; // 移除窗口左端的值 if (i \u0026gt;= k - 1) // 确保窗口大小恰好为 k 时 max_average = std::max(max_average, sum / (double)k); // 计算平均值并更新最大值 } return max_average; // 返回最大平均值 } }; ","date":"2024-12-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximumu-average-subarray-i/","title":"Maximumu Average Subarray I"},{"content":"筛质数 分析 定义全局变量： primes[N]：存储所有筛出的质数 st[N]：标记每个数是否为合数（true 表示不是质数） cnt：统计质数数量 线性筛法（每个合数只会被它的最小质因子筛去一次） 遍历 i 从 2 到 n 如果 st[i] == false，说明 i 是质数，记录到 primes[cnt ++ ] 枚举当前已记录的质数 primes[j] 将 primes[j] * i 标记为合数 如果 i % primes[j] == 0，说明 primes[j] 是 i 的最小质因子，直接 break，防止重复标记 primes[j] 是 i 的最小质因子 那么 primes[j] * i 的最小质因子仍然是 primes[j] 如果继续用后面的 primes[k] 去乘 i，生成的 primes[k] * i 这个合数，其最小质因子可能不是 primes[k]，而是 primes[j]，违背线性筛只由最小质因子筛的原则 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include \u0026lt;iostream\u0026gt; const int N = 1000010; int primes[N]; // 存储所有质数 bool st[N]; // st[i] 为 true 表示 i 是合数 int cnt = 0; // 质数个数 void get_primes(int n) { for (int i = 2; i \u0026lt;= n; ++ i) { if (!st[i]) primes[cnt ++ ] = i; // i 是质数，存入 primes for (int j = 0; primes[j] * i \u0026lt;= n; ++ j) { st[primes[j] * i] = true; // 用质数 primes[j] 筛掉 i 的倍数 if (i % primes[j] == 0) break; // primes[j] 是 i 的最小质因子，跳出 } } } int main() { int n; std::cin \u0026gt;\u0026gt; n; get_primes(n); // 筛出所有小于等于 n 的质数 std::cout \u0026lt;\u0026lt; cnt \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // 输出质数个数 return 0; } ","date":"2024-12-21T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/get-primes/","title":"Get Primes"},{"content":"392. 判断子序列 分析 初始化指针： 用指针 i 表示字符串 s 的匹配位置，初始为 0 遍历字符串 t： 遍历字符串 t 中的每个字符 c 如果 t 中当前字符与 s[i] 相同，则将指针 i 向后移动 判断结果： 遍历完成后，若 i == s.size()，说明 s 的所有字符都在 t 中按照顺序找到，返回 true 否则返回 false 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: bool isSubsequence(string s, string t) { int i = 0; // 指针 i 表示当前匹配到 s 的第几个字符 for (char c : t) // 遍历 t 中的每个字符 { // 如果当前字符与 s[i] 匹配，则 i 前进 if (i \u0026lt; s.size() \u0026amp;\u0026amp; s[i] == c) i ++; } // 如果所有 s 的字符都匹配到了，返回 true return i == s.size(); } }; ","date":"2024-12-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/is-subsequence/","title":"Is Subsequence"},{"content":"443. 压缩字符串 分析 遍历字符数组：\n使用两个指针，一个 i 遍历原数组，另一个 k 表示压缩后字符存储的位置 记录连续重复字符：\n每次找到一组连续重复的字符，记录其长度 len 压缩字符组：\n将字符写入结果数组。 如果长度 len \u0026gt; 1，将其转为字符串形式逐个写入，需要使用 std::reverse 处理因低位数字先写入导致的顺序问题 更新指针：\n将指针 i 移动到下一个未处理的字符位置 返回结果：\n最终返回数组新长度 k 时间复杂度 遍历整个数组一次，时间复杂度为 O(n) 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public: int compress(vector\u0026lt;char\u0026gt;\u0026amp; s) { int k = 0; // 压缩后存储字符的位置 for (int i = 0; i \u0026lt; s.size(); ++i) { int j = i + 1; // 找到连续重复字符的结尾位置 while (j \u0026lt; s.size() \u0026amp;\u0026amp; s[i] == s[j]) ++j; // 写入当前字符 s[k++] = s[i]; // 计算连续字符的长度 int len = j - i; // 如果长度大于 1，将其转为字符串写入数组 if (len \u0026gt; 1) { int t = k; // 用于临时存储数字的开始位置 while (len) { s[t++] = \u0026#39;0\u0026#39; + len % 10; len /= 10; } // 将数字部分反转以正确顺序写入 std::reverse(s.begin() + k, s.begin() + t); k = t; // 更新压缩后的位置 } // 更新 i 到当前字符组的结束位置 i = j - 1; } return k; // 返回压缩后的数组长度 } }; ","date":"2024-12-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/string-compression/","title":"String Compression"},{"content":"334. 递增的三元子序列 分析 核心思想： 使用一个长度为 2 的数组 q 来记录递增子序列中的前两个元素： q[0] 表示递增子序列中的最小元素 q[1] 表示递增子序列中第二小的元素 遍历数组，根据当前数字和 q 中的元素关系更新状态 遍历逻辑： 如果当前数字 num 小于等于 q[0]，更新 q[0] 为更小的数字 如果当前数字大于 q[0] 且小于等于 q[1]，更新 q[1] 为更小的数字 如果当前数字大于 q[1]，说明找到了满足条件的递增三元组 q[0], q[1], num，返回 true 返回结果： 若遍历结束后仍未找到满足条件的三元组，则返回 false 时间复杂度 遍历数组一次，每次更新双指针数组最多需要 O(1) 的时间\n总时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: bool increasingTriplet(vector\u0026lt;int\u0026gt;\u0026amp; nums) { // 初始化递增子序列的前两个元素为无穷大 std::vector\u0026lt;int\u0026gt; q(2, INT_MAX); for (int num : nums) { int k = 2; // 初始化目标位置为第 3 位 while (k \u0026gt; 0 \u0026amp;\u0026amp; q[k - 1] \u0026gt;= num) // 寻找插入位置 --k; if (k == 2) // 如果找到了递增三元组 return true; q[k] = num; // 更新 q 中对应位置的值 } return false; // 遍历结束仍未找到三元组 } }; ","date":"2024-12-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/increasing-triplet-subsequence/","title":"Increasing Triplet Subsequence"},{"content":"151. 反转字符串中的单词 分析 去除多余空格： 遍历字符串，将所有单词移动到前面，同时移除多余的空格 用一个变量 k 表示当前写入位置 逐个反转单词： 每次遇到非空格字符时，确定单词的起始位置 i 继续遍历直到单词结束，将单词拷贝到字符串前面 对该单词的字符进行局部反转 在单词末尾添加空格，准备下一个单词的处理 清理末尾多余空格： 如果最后多余了一个空格，则将其删除 整体反转字符串： 将整个字符串反转，得到单词顺序颠倒的最终结果 时间复杂度 遍历字符串三次：去除多余空格 O(n)、单词局部反转 O(n)、整体反转 O(n) 总时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public: string reverseWords(string s) { int k = 0; // 表示当前写入位置 for (int i = 0; i \u0026lt; s.size(); ++i) { // 跳过空格 if (s[i] == \u0026#39; \u0026#39;) continue; // 找到单词的起始位置 int j = i, t = k; while (j \u0026lt; s.size() \u0026amp;\u0026amp; s[j] != \u0026#39; \u0026#39;) s[t++] = s[j++]; // 反转单词 std::reverse(s.begin() + k, s.begin() + t); // 在末尾添加一个空格 s[t++] = \u0026#39; \u0026#39;; k = t; // 更新写入位置 i = j; // 跳过当前单词 } // 删除末尾多余的空格 if (k \u0026gt; 0) --k; s.erase(s.begin() + k, s.end()); // 整体反转字符串 std::reverse(s.begin(), s.end()); return s; } }; ","date":"2024-12-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reverse-words-in-a-string/","title":"Reverse Words in a String"},{"content":"605. 种花问题 分析 寻找连续空地： 遍历数组时，如果遇到值为 0 的位置，开始寻找连续空地的终点 空地的长度是终点索引减去起点索引 边界特殊处理： 如果空地段位于数组的开头或结尾，由于没有相邻的种植限制，可以额外种植一朵花 更新累积种植数量： 根据空地长度计算可种植的花数，并累积到 res 中 如果 res 已达到或超过目标值 n，提前返回 true，减少不必要的计算 时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public: bool canPlaceFlowers(vector\u0026lt;int\u0026gt;\u0026amp; flowerbed, int n) { if (!n) return true; // 如果需要种植的花为 0，直接返回 true。 int res = 0; // 已种植的花数。 for (int i = 0; i \u0026lt; flowerbed.size(); ++i) { if (flowerbed[i]) // 当前位置已经种植了花，跳过。 continue; int j = i; // 找到连续空地的终点。 while (j \u0026lt; flowerbed.size() \u0026amp;\u0026amp; flowerbed[j] == 0) ++j; // 计算连续空地的长度。 int k = j - i - 1; // 边界处理：如果在开头或结尾，空地长度增加 1。 if (!i) k += 1; if (j == flowerbed.size()) k += 1; // 根据种植规则计算可以种植的花数。 res += k / 2; // 如果累计种植的花数已达到或超过 n，返回 true。 if (res \u0026gt;= n) return true; // 跳过当前检查的空地段。 i = j; } // 如果遍历结束仍未满足 n，返回 false。 return false; } }; ","date":"2024-12-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/can-place-flowers/","title":"Can Place Flowers"},{"content":"1431. 拥有最多糖果的孩子 分析 找到当前的最大糖果数： 遍历数组 candies，找到当前孩子中糖果数目的最大值 max_candies 判断每个孩子是否可以成为拥有最多糖果的孩子： 对于每个孩子 candies[i]，计算其在分配额外糖果后拥有的糖果数：candies[i] + extraCandies 如果结果大于或等于 max_candies，说明该孩子可以成为拥有最多糖果的孩子之一 时间复杂度 寻找最大值：O(n) 判断是否满足条件：O(n) 总时间复杂度：O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: vector\u0026lt;bool\u0026gt; kidsWithCandies(vector\u0026lt;int\u0026gt;\u0026amp; candies, int extraCandies) { int n = candies.size(); std::vector\u0026lt;bool\u0026gt; res(n); // 找到当前糖果的最大值 int max_candies = 0; for (int i = 0; i \u0026lt; n; ++i) max_candies = std::max(max_candies, candies[i]); // 判断每个孩子在获得额外糖果后是否能成为最多糖果的孩子之一 for (int i = 0; i \u0026lt; n; ++i) if (candies[i] + extraCandies \u0026gt;= max_candies) res[i] = true; return res; } }; ","date":"2024-12-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/kids-with-the-greatest-number-of-candies/","title":"Kids with the Greatest Number of Candies"},{"content":"345. 反转字符串中的元音字母 分析 定义元音集合： 使用一个哈希集合存储所有的元音字母，用于快速判断一个字符是否是元音 双指针法： 初始化两个指针：i 指向字符串的起始位置，j 指向字符串的末尾位置 使用双指针从两端向中间遍历，找到最近的两个元音字母，交换它们的位置 具体步骤： 如果 s[i] 不是元音，则 i 向右移动 如果 s[j] 不是元音，则 j 向左移动 当 s[i] 和 s[j] 都是元音时，交换它们，并同时移动两个指针 重复上述过程，直到两个指针相遇或交错 时间复杂度 双指针最多遍历字符串一次，每次操作的时间为 O(1) 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: string reverseVowels(string s) { // 定义元音字母集合 std::unordered_set\u0026lt;char\u0026gt; hash = { \u0026#39;a\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;i\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;u\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;E\u0026#39;, \u0026#39;I\u0026#39;, \u0026#39;O\u0026#39;, \u0026#39;U\u0026#39; }; // 双指针初始化 for (int i = 0, j = s.size() - 1; i \u0026lt; j; ++i, --j) { // 左指针寻找元音 while (i \u0026lt; j \u0026amp;\u0026amp; !hash.count(s[i])) ++i; // 右指针寻找元音 while (i \u0026lt; j \u0026amp;\u0026amp; !hash.count(s[j])) --j; // 交换两个元音字母 std::swap(s[i], s[j]); } return s; // 返回结果字符串 } }; ","date":"2024-12-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reverse-vowels-of-a-string/","title":"Reverse Vowels of a String"},{"content":"1071. 字符串的最大公因子 分析 判断条件：\n首先，字符串 str1 能否被 str2 整除以及 str2 能否被 str1 整除的关键条件是：str1 + str2 == str2 + str1 如果这个条件不成立，则不存在一个字符串能够同时整除这两个字符串，返回空字符串 最大公因子：\n如果 str1 + str2 == str2 + str1，则可以根据字符串的长度来进一步判断最大公因子 假设 str1 的长度为 n1，str2 的长度为 n2，那么最大公因子字符串的长度应该是 gcd(n1, n2) 利用 gcd 函数计算出 n1 和 n2 的最大公因子，取 str1 或 str2 的前 gcd(n1, n2) 个字符作为最大公因子字符串 时间复杂度 字符串比较的时间复杂度是 O(n1 + n2) gcd(n1, n2) 时间复杂度：O(log min(n1, n2)) 总时间复杂度是 O(n1 + n2)\n空间复杂度 空间复杂度 O(n1 + n2)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public: // 计算两个数的最大公因子 int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } // 返回能够同时整除 str1 和 str2 的最大公因子字符串 string gcdOfStrings(string str1, string str2) { // 如果 str1 + str2 != str2 + str1，则不存在公共因子 if (str1 + str2 != str2 + str1) return \u0026#34;\u0026#34;; // 否则返回 str1 的前 gcd(str1.size(), str2.size()) 长度的子串 return str1.substr(0, gcd(str1.size(), str2.size())); } }; ","date":"2024-12-18T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/greatest-common-divisor-of-strings/","title":"Greatest Common Divisor Of Strings"},{"content":"1768. 交替合并字符串 时间复杂度 时间复杂度 O(max(n, m))\n空间复杂度 空间复杂度为 O(n + m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: string mergeAlternately(string word1, string word2) { std::string res; int n = word1.size(), m = word2.size(); int i = 0, j = 0; while (i \u0026lt; n || j \u0026lt; m) { if (i \u0026lt; n) res += word1[i ++ ]; if (j \u0026lt; m) res += word2[j ++ ]; } return res; } }; ","date":"2024-12-18T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/merge-strings-alternately/","title":"Merge Strings Alternately"},{"content":"最大公约数 分析 使用 **欧几里得算法（即辗转相除法）**来求两个数的最大公约数，其基本原理为：\n当 b != 0 时，不断对 a % b 进行递归； 当 b == 0，说明余数为 0，返回的 a 就是最大公约数 时间复杂度 单次 gcd(a, b) 的时间复杂度为 O(log min(a, b)) 所以总复杂度为 O(n * logN)，其中 N 为输入数据的最大值\n空间复杂度 空间复杂度为 O(logN)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;iostream\u0026gt; // 欧几里得算法实现，递归方式求最大公约数 int gcd(int a, int b) { return b ? gcd(b, a % b) : a; // 如果 b 不为 0，继续递归；否则返回 a } int main() { int n; std::cin \u0026gt;\u0026gt; n; // 读入整数 n，表示有 n 对数字 while (n -- ) { int a, b; std::cin \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b; // 读入一对整数 a 和 b std::cout \u0026lt;\u0026lt; gcd(a, b) \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // 输出这对数的最大公约数 } return 0; } ","date":"2024-12-17T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/great-common-divisor/","title":"Great Common Divisor"},{"content":"121. 买卖股票的最佳时机 分析 维护最低价格： 用变量 min_price 记录当前遍历过程中的最低股票价格，初始化为 INT_MAX 计算当前利润： 对于每一天的价格 price，计算当天卖出时的利润：profit = price - min_price 更新最大利润： 用变量 res 记录遍历过程中的最大利润，初始化为 0 动态更新： 每遍历一天，更新 min_price 和 res，确保始终找到最优解 时间复杂度 遍历一次数组，每个元素只处理一次，时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public: int maxProfit(vector\u0026lt;int\u0026gt;\u0026amp; prices) { int min_price = INT_MAX; // 初始化最低价格 int res = 0; // 初始化最大利润 for (int price : prices) { // 更新最大利润 res = std::max(res, price - min_price); // 更新最低价格 min_price = std::min(min_price, price); } return res; // 返回最大利润 } }; ","date":"2024-12-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/best-time-to-buy-and-sell-stock/","title":"Best Time to Buy and Sell Stock"},{"content":"55. 跳跃游戏 算法 维护最远可达位置： 使用变量 i 表示当前最远可达的位置 初始位置为 0 逐步检查跳跃能力： 遍历数组，当前下标为 j，检查是否可以到达： 如果 i \u0026lt; j，说明无法继续前进，返回 false 更新最远可达位置： i = max(i, j + nums[j] 终止条件： 遍历结束后，若能始终更新 i 使其覆盖整个数组，返回 true 复杂度分析 遍历数组一次，时间复杂度为 O(n) 空间复杂度为 O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public: bool canJump(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int i = 0; // 当前最远可达位置 for (int j = 0; j \u0026lt; nums.size(); ++j) { if (i \u0026lt; j) // 如果无法覆盖当前位置 return false; i = std::max(i, j + nums[j]); // 更新最远可达位置 } return true; // 如果能够遍历完成，返回 true } }; Python 代码 1 2 3 4 5 6 7 8 class Solution: def canJump(self, nums: List[int]) -\u0026gt; bool: i = 0 for j in range(len(nums)): if i \u0026lt; j: return False i = max(i, j + nums[j]) return True Go 代码 1 2 3 4 5 6 7 8 9 10 func canJump(nums []int) bool { i := 0 for j := 0; j \u0026lt; len(nums); j ++ { if i \u0026lt; j { return false } i = max(i, j + nums[j]) } return true } ","date":"2024-12-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/jump-game/","title":"Jump Game"},{"content":"45. 跳跃游戏II 算法 核心思想：使用贪心算法模拟跳跃过程，按层次遍历的思想解决问题 当前位置的覆盖范围：表示当前跳跃次数能够到达的最远位置 下一次跳跃的覆盖范围：表示增加一次跳跃后能够到达的最远位置 关键变量： cur：当前处理的位置。 dist：当前跳跃次数能够到达的最远位置。 next：下一跳能够到达的最远位置。 跳跃过程： 初始化跳跃次数 res = 0，起点为 cur = 0 在当前跳跃的范围内（从 cur 到 dist），计算下一跳的最远位置 next 当 dist 超过或等于数组最后一个位置时，结束跳跃。 步数增加： 每遍历完当前跳跃的范围时，更新到下一跳的最远位置 dist = next，并增加跳跃次数 res 复杂度分析 每个位置最多访问一次，因此时间复杂度为 O(n) 空间复杂度为 O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: int jump(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = 0; // 跳跃次数 int cur = 0; // 当前处理的位置 int dist = 0; // 当前跳跃的覆盖范围 while (dist \u0026lt; nums.size() - 1) { int next = 0; // 下一跳的覆盖范围 // 遍历当前跳跃范围 while (cur \u0026lt;= dist) { next = std::max(next, cur + nums[cur]); // 更新下一跳的最远范围 ++cur; } dist = next; // 更新跳跃范围 ++res; // 跳跃次数加一 } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 class Solution: def jump(self, nums: List[int]) -\u0026gt; int: res = 0 dist, cur = 0, 0 while dist \u0026lt; len(nums) - 1: next_dist = 0 while cur \u0026lt;= dist: next_dist = max(next_dist, cur + nums[cur]) cur += 1 dist = next_dist res += 1 return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func jump(nums []int) int { res := 0 dist, cur := 0, 0 for dist \u0026lt; len(nums) - 1 { next := 0 for cur \u0026lt;= dist { next = max(next, cur + nums[cur]) cur += 1 } dist = next res += 1 } return res } ","date":"2024-12-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/jupm-game-ii/","title":"Jupm Game II"},{"content":"763. 划分字母区间 分析 记录每个字母的最远出现位置： 遍历字符串，记录每个字符最后一次出现的索引，存储在哈希表 hash 中 划分片段： 使用变量 start 表示当前片段的起始位置，end 表示当前片段中字符的最远位置 遍历字符串时，动态更新当前片段的最远位置 end 当当前索引 i 等于 end 时，表示当前片段结束，将片段长度加入结果数组 res 更新起点： 划分一个片段后，将 start 更新为 i + 1，准备划分下一个片段 时间复杂度 记录最远位置: O(n)，需要遍历一次字符串记录字符的最远位置 划分片段: O(n)，第二次遍历字符串进行片段划分 总体时间复杂度为 O(n)\n空间复杂度 哈希表 hash 的空间复杂度为 O(k)，其中 k 是字符集大小（对于英文字母，k \u0026lt; 26） 结果数组 res 需要额外存储片段长度，空间复杂度为 O(p)，其中 p 是片段数量 总体空间复杂度为 O(k + p)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public: vector\u0026lt;int\u0026gt; partitionLabels(string s) { // 记录每个字符的最远出现位置 std::unordered_map\u0026lt;char, int\u0026gt; hash; for (int i = 0; i \u0026lt; s.size(); ++i) hash[s[i]] = i; std::vector\u0026lt;int\u0026gt; res; // 存储结果 int start = 0, end = 0; for (int i = 0; i \u0026lt; s.size(); ++i) { // 更新当前片段的最远边界 end = std::max(end, hash[s[i]]); // 当前片段结束 if (i == end) { res.push_back(end - start + 1); // 保存片段长度 start = end = i + 1; // 更新起点，准备下一个片段 } } return res; } }; ","date":"2024-12-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/partition-lables/","title":"Partition Lables"},{"content":"287. 寻找重复数 分析 将问题抽象为一个带环的链表问题：\n数组中的每个元素都可以看作指向下一个索引的指针：元素的值 nums[i] 指向索引 nums[i] 因为数组中有重复的数字，这意味着某些索引会形成一个环。我们需要找到这个环的入口点，即重复的数字 利用 Floyd 判圈算法（龟兔赛跑算法）可以高效解决这个问题：\n阶段 1：找到快慢指针的相遇点\n定义两个指针：慢指针 a 每次移动一步，快指针 b 每次移动两步 两个指针一定会在环中相遇。 阶段 2：找到环的入口点\n将其中一个指针 a 重新指向起点 0 ，另一个指针 b 保持在相遇点 两个指针每次都移动一步，相遇时的点即为环的入口点，也就是重复的数字 时间复杂度 快慢指针每次只移动一步或两步，最多经过两次遍历（一次寻找环，一次定位入口），时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public: int findDuplicate(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int a = 0, b = 0; // 初始化快慢指针 while (true) { a = nums[a]; // 慢指针每次走一步 b = nums[b]; // 快指针第一次走一步 b = nums[b]; // 快指针第二次走一步 // 快慢指针相遇，退出循环 if (a == b) { a = 0; // 慢指针从起点开始 while (a != b) // 两指针同步移动 { a = nums[a]; b = nums[b]; } return a; // 相遇点即为环入口，即重复数字 } } return -1; // 理论上不可能到达这里 } }; ","date":"2024-12-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-the-duplicate-number/","title":"Find the Duplicate Number"},{"content":"169. 多数元素 分析 使用摩尔投票法（Boyer-Moore Voting Algorithm），通过维护一个候选元素和计数器，在一次遍历中找到多数元素\n将候选元素初始化为空，计数器设为 0 遍历数组： 如果计数器为 0，则更新候选元素为当前元素，计数器设为 1 如果当前元素等于候选元素，计数器加 1 如果当前元素不等于候选元素，计数器减 1 遍历结束后，候选元素即为多数元素 这是因为多数元素的出现次数大于其他元素出现次数的总和，因此在消耗所有计数后，多数元素一定会留下\n时间复杂度 只需一次遍历数组，时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public: int majorityElement(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = 0, cnt = 0; // 初始化候选元素和计数器 for (int num : nums) { if (cnt == 0) // 如果计数器为 0，更新候选元素 res = num, cnt = 1; else if (res == num) // 当前元素等于候选元素，计数器加 1 ++cnt; else // 当前元素不等于候选元素，计数器减 1 --cnt; } return res; // 返回多数元素 } }; ","date":"2024-12-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/majority-element/","title":"Majority Element"},{"content":"75. 颜色分类 分析 经典的荷兰国旗问题，通过双指针和单次遍历的方式完成排序\n将数组分为三个区域：\n左区域（存放红色 0 的区域） 右区域（存放蓝色 2 的区域） 中间区域（存放白色 1 的区域） 使用三个指针：\ni: 指向左区域的边界 j: 当前正在遍历的元素位置 k: 指向右区域的边界 初始化 i = 0、j = 0、k = n-1\n遍历数组，判断 \\text{nums}[j] ：\n如果是 1（白色），直接跳过，j++ 如果是 0（红色），将 nums[j] 和 nums[i] 交换，i++, j++ 如果是 2（蓝色），将 nums}[j] 和 nums}[k] 交换，k–- （注意：此时 j 不前进，继续检查新的 nums[j] 遍历结束后，数组即为排序后的结果\n时间复杂度 整个数组只遍历了一次，时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: void sortColors(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int i = 0, j = 0, k = nums.size() - 1; // 初始化指针 while (j \u0026lt;= k) // 遍历直到中间指针超过右边界 { if (nums[j] == 1) // 当前是白色，跳过 ++j; else if (nums[j] == 0) // 当前是红色，交换到左区域 { std::swap(nums[i], nums[j]); ++i, ++j; } else // 当前是蓝色，交换到右区域 { std::swap(nums[j], nums[k]); --k; } } } }; ","date":"2024-12-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/sort-colors/","title":"Sort Colors"},{"content":"72. 编辑距离 分析 状态定义\n设 f[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少操作数。 状态转移\n如果 word1[i] == word2[j]：不需要额外操作，状态继承自子问题：f[i][j] = f[i-1][j-1] 如果 word1[i] != word2[j]：需要进行操作，从以下三种操作中选择代价最小的一种：f[i][j] = min(f[i-1][j-1] + 1, f[i-1][j] + 1, f[i][j-1] + 1) 替换：f[i-1][j-1] + 1 删除：f[i-1][j] + 1 插入：f[i][j-1] + 1 边界条件\nf[i][0] = i: 将 word1 的前 i 个字符转换为空字符串，需要删除 i 次 f[0][j] = j: 将空字符串转换为 word2 的前 j 个字符，需要插入 j 次 初始化输入：\n在代码中通过向 word1 和 word2 开头添加空格，统一了状态的表示 最终结果：\n返回 f[n][m]，其中 n 是 word1 的长度， m 是 word2 的长度 时间复杂度 外层循环遍历 n，内层循环遍历 m，总时间复杂度为 O(nm)\n空间复杂度 空间复杂度为 O(nm)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public: int minDistance(string word1, string word2) { // 在字符串开头添加空格，便于边界处理 word1 = \u0026#34; \u0026#34; + word1; word2 = \u0026#34; \u0026#34; + word2; int n = word1.size(), m = word2.size(); std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f(n, std::vector\u0026lt;int\u0026gt;(m)); // 初始化边界条件 for (int i = 0; i \u0026lt; n; ++i) f[i][0] = i; for (int j = 0; j \u0026lt; m; ++j) f[0][j] = j; for (int i = 1; i \u0026lt; n; ++i) for (int j = 1; j \u0026lt; m; ++j) { // 删除、插入、替换操作的最优选择 f[i][j] = std::min(f[i - 1][j] + 1, f[i][j - 1] + 1); if (word1[i] == word2[j]) f[i][j] = std::min(f[i][j], f[i - 1][j - 1]); else f[i][j] = std::min(f[i][j], f[i - 1][j - 1] + 1); } // 返回结果 return f[n - 1][m - 1]; } }; ","date":"2024-12-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/edit-distance/","title":"Edit Distance"},{"content":"1143. 最长公共子序列 分析 定义状态：\nf[i][j] 表示字符串 text1 的前 i 个字符与 text2 的前 j 个字符的最长公共子序列的长度 状态转移：\n当 text1[i-1] == text2[j-1]，当前字符匹配，可以延续公共子序列的长度：f[i][j] = f[i-1][j-1] + 1\n当 text1[i-1] != text2[j-1]，当前字符不匹配，最长公共子序列取决于舍弃其中一个字符的结果：f[i][j] = \\max(f[i-1][j], f[i][j-1])\n边界条件：\nf[i][0] = 0 和 f[0][j] = 0，表示任意一个字符串与空字符串的最长公共子序列长度为 0 结果：\n最终结果存储在 f[n][m]，其中 n 和 m 分别是 text1 和 text2 的长度 时间复杂度 外层循环遍历 n，内层循环遍历 m ，时间复杂度为 O(nm)\n空间复杂度 空间复杂度为 O(nm)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: int longestCommonSubsequence(string text1, string text2) { int n = text1.size(), m = text2.size(); std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f(n + 1, std::vector\u0026lt;int\u0026gt;(m + 1)); // 遍历每个字符的组合 for (int i = 1; i \u0026lt;= n; ++i) for (int j = 1; j \u0026lt;= m; ++j) { f[i][j] = std::max(f[i][j - 1], f[i - 1][j]); // 两个字符匹配时 if (text1[i - 1] == text2[j - 1]) f[i][j] = std::max(f[i][j], f[i - 1][j - 1] + 1); } // 返回最终结果 return f[n][m]; } }; ","date":"2024-12-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-common-subsequence/","title":"Longest Common Subsequence"},{"content":"5. 最长回文子串 分析 中心扩展思想： 回文的中心可以是一个字符（如 aba 的中心是 b），也可以是两个字符之间（如 abba 的中心是 bb） 遍历字符串的每一个字符（和每一个字符间隙）作为中心，向两侧扩展判断是否为回文子串 具体步骤： 枚举每一个可能的中心： 单字符中心：如字符 a 双字符中心：如字符对 aa 每次以当前中心向两侧扩展，直到遇到不相等的字符或边界 记录当前最长的回文子串，并与之前记录的结果进行比较，更新最长子串 时间复杂度 遍历字符串每个字符 O(n)，每次从中心向外扩展最多 O(n)，总体时间复杂度为 O(n^2)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: string longestPalindrome(string s) { // 初始化结果变量 std::string res; // 遍历字符串的每一个字符，尝试以其为中心扩展 for (int i = 0; i \u0026lt; s.size(); ++i) { // 第一种情况：中心是单个字符 int l = i - 1, r = i + 1; while (l \u0026gt;= 0 \u0026amp;\u0026amp; r \u0026lt; s.size() \u0026amp;\u0026amp; s[l] == s[r]) --l, ++r; // 更新最长回文子串 if (res.size() \u0026lt; r - l - 1) res = s.substr(l + 1, r - l - 1); // 第二种情况：中心是两个字符之间 l = i, r = i + 1; while (l \u0026gt;= 0 \u0026amp;\u0026amp; r \u0026lt; s.size() \u0026amp;\u0026amp; s[l] == s[r]) --l, ++r; // 更新最长回文子串 if (res.size() \u0026lt; r - l - 1) res = s.substr(l + 1, r - l - 1); } // 返回最长回文子串 return res; } }; ","date":"2024-12-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-palindromic-substring/","title":"Longest Palindromic Substring"},{"content":"136. 只出现一次的数字 分析 异或运算具有以下性质：\nx ^ x = 0: 任何数字与自身异或结果为 0 x ^ 0 = x: 任何数字与 0 异或结果为自身 满足交换律和结合律 通过对数组中的所有数字进行异或操作，成对出现的数字会互相抵消，最终只剩下那个只出现一次的数字\n时间复杂度 总时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 class Solution { public: int singleNumber(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = 0; // 初始化结果为 0 for (int num : nums) // 遍历数组 res ^= num; // 将每个数字与 res 进行异或操作 return res; // 返回结果 } }; ","date":"2024-12-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/single-number/","title":"Single Number"},{"content":"64. 最小路径和 分析 状态定义：\n用 f[i][j] 表示从起点 (0, 0) 到达位置 (i, j) 的路径数字总和的最小值 状态转移：\n如果机器人可以从上方或左方到达 (i, j)，则：f[i][j] = min(f[i-1][j], f[i][j-1]) + grid[i][j]\n边界情况：\n如果位于第一行，则只能从左侧到达：f[0][j] = f[0][j-1] + grid[0][j] 如果位于第一列，则只能从上方到达：f[i][0] = f[i-1][0] + grid[i][0] 初始条件：\n起点 (0, 0) 的路径和等于网格的初始值：f[0][0] = grid[0][0] 时间复杂度 动态规划遍历整个网格，每个位置只需计算一次，时间复杂度为 O(mn)\n空间复杂度 空间复杂度为 O(mn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public: int minPathSum(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; grid) { // 获取网格的行数和列数 int n = grid.size(); if (n == 0) return 0; // 如果网格为空，直接返回 0 int m = grid[0].size(); // 初始化动态规划数组，所有值初始为 INT_MAX std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f(n, std::vector\u0026lt;int\u0026gt;(m, INT_MAX)); // 动态规划计算每个位置的最小路径和 for (int i = 0; i \u0026lt; n; ++i) { for (int j = 0; j \u0026lt; m; ++j) { if (i == 0 \u0026amp;\u0026amp; j == 0) // 起点 f[i][j] = grid[i][j]; else { // 从上方到达 if (i) f[i][j] = std::min(f[i][j], f[i - 1][j] + grid[i][j]); // 从左方到达 if (j) f[i][j] = std::min(f[i][j], f[i][j - 1] + grid[i][j]); } } } // 返回右下角的最小路径和 return f[n - 1][m - 1]; } }; ","date":"2024-12-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-path-sum/","title":"Minimum Path Sum"},{"content":"416. 分割等和子集 分析 典型的 01 背包问题，我们需要判断是否存在一个子集，其元素和为 sum / 2，其中 sum 是数组 nums 的元素总和\n特殊情况处理： 如果数组总和 sum 是奇数，则无法分成两个相等的子集，直接返回 false 动态规划定义： 定义一个布尔数组 f，其中 f[j] 表示是否可以从数组中选取一些元素，使得这些元素的和为 j 初始化 f[0] = true，表示总和为 0 的情况总是成立（即不选任何元素） 状态转移方程： 遍历数组 nums 中的每个数 nums[i]，从目标值 target 开始往下更新： f[j] = f[j] || f[j - nums[i]] 表示如果在之前能凑出 j - nums[i]，则加入当前数字后也能凑出 j 最终结果： 如果能凑出目标值 sum / 2，即 f[target] == true，则可以分成两个相等的子集 时间复杂度 外层循环遍历数组 n 次 内层循环遍历目标值 target 次 总时间复杂度 O(n * target)\n空间复杂度 空间复杂度为 O(target)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public: bool canPartition(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int sum = 0; for (int num : nums) // 计算数组的总和 sum += num; if (sum % 2) // 如果总和是奇数，无法分割成两个相等的子集 return false; int target = sum / 2; // 目标子集的和 int n = nums.size(); std::vector\u0026lt;bool\u0026gt; f(target + 1, false); // 定义状态数组，f[j] 表示是否可以凑出总和 j f[0] = true; // 总和为 0 的情况成立 for (int i = 0; i \u0026lt; n; ++i) // 遍历每个数 for (int j = target; j \u0026gt;= nums[i]; --j) // 从 target 向下遍历，避免状态覆盖 f[j] = f[j] || f[j - nums[i]]; return f[target]; // 返回是否可以凑出目标总和 } }; ","date":"2024-12-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/partition-equal-subset-sum/","title":"Partition Equal Subset Sum"},{"content":"62. 不同路径 分析 状态定义： 用 f[i][j] 表示从起点 (0, 0) 到达网格位置 (i, j) 的不同路径数 状态转移： 如果机器人能够从上方或左方到达 (i, j)： 从上方到达：路径数为 f[i-1][j] 从左方到达：路径数为 f[i][j-1] 因此，状态转移方程为：f[i][j] = f[i-1][j] + f[i][j-1] 初始条件： 起点 (0, 0) 的路径数为 1，即 f[0][0] = 1 第一行和第一列的位置只能从一个方向到达： 第一行：只能从左到右 第一列：只能从上到下 时间复杂度 需要遍历整个网格，时间复杂度为 O(mn)\n空间复杂度 空间复杂度为 O(mn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: int uniquePaths(int m, int n) { // 创建一个二维数组 f，用于记录每个位置的路径数 std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f(n, std::vector\u0026lt;int\u0026gt;(m)); // 遍历每个位置 for (int i = 0; i \u0026lt; n; ++i) { for (int j = 0; j \u0026lt; m; ++j) { // 初始化起点 if (i == 0 \u0026amp;\u0026amp; j == 0) f[i][j] = 1; else { // 从上方到达 if (i) f[i][j] += f[i - 1][j]; // 从左方到达 if (j) f[i][j] += f[i][j - 1]; } } } // 返回终点的路径数 return f[n - 1][m - 1]; } }; ","date":"2024-12-12T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/unique-paths/","title":"Unique Paths"},{"content":"322. 零钱兑换 分析 定义状态： f[j]：表示凑成金额 j 所需的最少硬币个数 状态转移方程： 对于每个硬币面值 coins[i]，如果选择该硬币，那么金额 j 的状态可以由金额 j - coins[i] 转移而来：f[j] = min(f[j], f[j - coins[i]] + 1)，即当前金额的最优解为不选择当前硬币的解和选择当前硬币的解的较小值 初始化： f[0] = 0：凑成金额 0 所需的硬币数为 0 其他状态 f[j] 初始化为无穷大（设为一个较大的值 INF），表示尚未凑成 结果判断： 如果 f[amount] = INF ，表示无法凑成金额 amount，返回 -1 否则返回 f[amount] 时间复杂度 外层循环遍历硬币种类 O(n) ，内层循环遍历金额 O(amount) ，总复杂度为 O(n * amount)\n空间复杂度 空间复杂度为 O(amount)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: int coinChange(vector\u0026lt;int\u0026gt;\u0026amp; coins, int amount) { int INF = 0x3f3f3f3f; // 定义一个较大的值表示无穷大 std::vector\u0026lt;int\u0026gt; f(amount + 1, INF); // 初始化 DP 数组 f[0] = 0; // 金额为 0 时需要 0 个硬币 for (int i = 0; i \u0026lt; coins.size(); ++i) // 遍历每个硬币面值 { for (int j = coins[i]; j \u0026lt;= amount; ++j) // 遍历所有可能金额 { f[j] = std::min(f[j], f[j - coins[i]] + 1); // 状态转移 } } if (f[amount] == INF) // 如果金额无法凑成 return -1; return f[amount]; // 返回最优解 } }; ","date":"2024-12-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/coin-change/","title":"Coin Change"},{"content":"300. 最长递增子序列 分析 思路\n使用一个数组 q 来维护当前已知的递增子序列。这个数组不一定是真实的子序列，而是用于动态记录递增子序列的长度信息 遍历数组 nums 时，依次将当前数字插入到 q 中，保证 q 中的元素始终满足递增条件 处理策略：\n如果当前数字 x 大于 q 的最后一个元素，则直接追加到 q 的末尾 否则，使用 二分查找 找到 q 中第一个大于或等于 x 的位置，并用 x 替换该位置的值这相当于更新子序列，使其在同样长度的情况下更容易扩展 返回结果：\n最终数组 q 的长度即为最长递增子序列的长度 时间复杂度 遍历数组时，每个元素需要执行一次二分查找，二分查找的复杂度为 O(logn)\n总时间复杂度 O(nlogn)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: int lengthOfLIS(vector\u0026lt;int\u0026gt;\u0026amp; nums) { std::vector\u0026lt;int\u0026gt; q; // 用于记录递增子序列的数组 for (int x : nums) { if (q.empty() || x \u0026gt; q.back()) // 如果当前数字大于子序列末尾元素 q.push_back(x); // 直接追加 else { // 使用二分查找定位 int l = 0, r = q.size() - 1; while (l \u0026lt; r) { int mid = (l + r) \u0026gt;\u0026gt; 1; // 中间位置 if (x \u0026lt;= q[mid]) // 如果当前数字小于等于 q[mid] r = mid; // 缩小右边界 else l = mid + 1; // 缩小左边界 } q[r] = x; // 替换第一个大于等于 x 的位置 } } return q.size(); // q 的长度即为最长递增子序列的长度 } }; ","date":"2024-12-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-increasing-subsequence/","title":"Longest Increasing Subsequence"},{"content":"139. 单词拆分 分析 状态定义： 定义 f[i]：表示从第 i 个字符到字符串结尾的子串能否被字典中的单词拼接而成 状态转移： 对于子串 s[i:j]，若 s[i:j] 出现在字典中，且 f[j] = true，则可以拼接出 s[i:n]，即 f[i] = true 初始化： f[n] = true：空字符串可以被成功拼接 结果： 返回 f[0]，即判断整个字符串是否可以被拼接 时间复杂度 外层循环遍历字符串 O(n) ，内层循环遍历每个子串的结尾 O(n) ，检查子串是否在字典中平均耗时 O(k) ，其中 k 是子串的平均长度 总时间复杂度为 O(n^2 * k)\n空间复杂度 动态规划数组 f 占用 O(n) 哈希表占用 O(l) ，其中 l 是字典中单词的总长度 总空间复杂度为 O(n + l)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: bool wordBreak(string s, vector\u0026lt;string\u0026gt;\u0026amp; wordDict) { std::unordered_set\u0026lt;std::string\u0026gt; hash; // 使用哈希表存储字典中的单词 for (std::string word : wordDict) hash.insert(word); int n = s.size(); std::vector\u0026lt;bool\u0026gt; f(n + 1, false); // 动态规划数组 f[n] = true; // 初始化空字符串为可拼接 // 从后向前遍历字符串 for (int i = n - 1; i \u0026gt;= 0; --i) { std::string str; // 记录当前子串 for (int j = i; j \u0026lt; n; ++j) { str += s[j]; // 扩展子串 if (hash.count(str) \u0026amp;\u0026amp; f[j + 1]) // 检查是否满足条件 { f[i] = true; break; } } } return f[0]; // 返回结果 } }; ","date":"2024-12-11T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/word-break/","title":"Word Break"},{"content":"70. 爬楼梯 分析 典型的斐波那契数列问题。到达第 n 阶的方法数等于到达第 n-1 阶和第 n-2 阶的方法数之和 状态转移方程：f(n) = f(n-1) + f(n-2)\n初始条件：\nf(0) = 1 ：到达第 1 阶的方法只有一种 f(1) = 1 ：到达第 1 阶的方法只有一种 f(2) = 2 ：到达第 2 阶的方法是两种 1 + 1 通过观察状态转移公式，问题可以转化为计算斐波那契数列的第 n 项 使用两个变量来存储状态值，优化空间复杂度： a ：表示到达前一阶的方法数 b ：表示到达当前阶的方法数 迭代更新 a 和 b 的值，直到计算到第 n 阶 时间复杂度 循环执行 n - 1 次，时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public: int climbStairs(int n) { int a = 1, b = 1; // 初始状态：f(0) = 1, f(1) = 1 while ( -- n) // 从第 2 阶开始计算 { int c = a + b; // 当前阶方法数等于前两阶之和 a = b; // 更新前一阶为当前阶 b = c; // 更新当前阶为下一阶 } return b; // 返回第 n 阶的方法数 } }; ","date":"2024-12-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/climbing-stairs/","title":"Climbing Stairs"},{"content":"198. 打家劫舍 分析 定义状态： f[i]：表示偷取第 i 个房屋的最高金额 g[i]：表示不偷取第 i 个房屋的最高金额 状态转移方程： 若偷第 i 个房屋：f[i] = g[i - 1] + nums[i - 1]，即前一个房屋没有被偷，当前房屋可以偷取，累加其金额。 若不偷第 i 个房屋：g[i] = max(f[i - 1], g[i - 1])，即第 i 个房屋不被偷，其最优结果取决于前一个房屋偷或不偷的最大值 初始化： f[0] = 0（没有房屋可偷） g[0] = 0 （没有房屋可偷） 结果： 最终的最大金额为：max(f[n], g[n]) 时间复杂度 遍历一次数组，时间复杂度为 O(n)\n空间复杂度 使用两个长度为 n + 1 的数组，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public: int rob(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int n = nums.size(); std::vector\u0026lt;int\u0026gt; f(n + 1), g(n + 1); // 定义偷和不偷的状态数组 for (int i = 1; i \u0026lt;= n; ++ i) { f[i] = g[i - 1] + nums[i - 1]; // 偷当前房屋 g[i] = std::max(f[i - 1], g[i - 1]); // 不偷当前房屋 } return std::max(f[n], g[n]); // 返回最大值 } }; ","date":"2024-12-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/house-robber/","title":"House Robber"},{"content":"118. 杨辉三角 分析 杨辉三角的特点是：\n第 i 行有 i 个元素 每行的首尾元素为 1 中间的每个元素为其上一行相邻两个元素的和 公式：f[i][j] = f[i-1][j-1] + f[i-1][j]\n初始化数据结构： 使用一个二维数组 f 存储结果 逐行构造杨辉三角： 第 i 行初始化一个大小为 i + 1 的数组 设置首尾元素为 1 遍历中间位置，根据公式计算值并填充 返回结果： 将每一行加入结果数组并返 时间复杂度 外层循环运行 n 次； 内层循环最多运行 n - 2 次 总体复杂度为：O(1 + 2 + 3 + ... + n) = O(n^2)\n空间复杂度 空间复杂度为 O(n^2)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; generate(int numRows) { std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; f; for (int i = 0; i \u0026lt; numRows; ++ i) // 遍历每一行 { std::vector\u0026lt;int\u0026gt; line(i + 1); // 初始化当前行，长度为 i+1 line[0] = line[i] = 1; // 每行的首尾元素为 1 for (int j = 1; j \u0026lt; i; ++ j) // 中间元素由上一行相邻元素之和计算得出 line[j] = f[i - 1][j - 1] + f[i - 1][j]; f.push_back(line); // 将当前行加入结果数组 } return f; } }; ","date":"2024-12-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/pascals-triangle/","title":"Pascals Triangle"},{"content":"279. 完全平方数 分析 定义状态： f[i] ：表示和为 i 的完全平方数的最少数量 状态转移方程： 对于 i ，尝试减去一个完全平方数 j^2 ，转移状态为：f[i] = min(f[i], f[i - j^2] + 1) 初始化： f[0] = 0 ：和为 0 的最少数量为 0 其他状态 f[i] 初始化为 4，因为根据四平方和定理，一个数至多由 4 个完全平方数组成 遍历顺序： 外层循环遍历 i（目标和），内层循环遍历 j（尝试的完全平方数） 结果： 返回 f[n] 即为和为 n 的完全平方数的最少数量 时间复杂度 外层循环遍历 i 从 1 到 n，内层循环枚举完全平方数的数量，最多为 \\sqrt{n} 总复杂度为 O(n*\\sqrt{n})\n空间复杂度 使用排序 O(logn) 的额外空间，其余操作在原地完成，空间复杂度为 O(1)\n使用了长度为 n + 1 的数组 f，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: int numSquares(int n) { std::vector\u0026lt;int\u0026gt; f(n + 1, 4); // 初始化 f 数组，最大值为 4 f[0] = 0; // 和为 0 时的最少数量为 0 for (int i = 1; i \u0026lt;= n; ++i) { for (int j = 1; j * j \u0026lt;= i; ++j) { f[i] = std::min(f[i], f[i - j * j] + 1); // 状态转移 } } return f[n]; // 返回结果 } }; ","date":"2024-12-10T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/perfect-squares/","title":"Perfect Squares"},{"content":"295. 数据流的中位数 分析 为了高效地获取中位数，使用 两个优先队列（堆） 来维护数据流的有序性：\ndown 堆（大顶堆）： 存储较小的一半元素，堆顶是最大值 up 堆（小顶堆）： 存储较大的一半元素，堆顶是最小值 通过以下规则维持堆的平衡和正确性：\n插入元素： 如果当前元素小于或等于 down 堆的堆顶，将其插入 down 否则，将其插入 up 调整堆的平衡： 如果 down 的元素个数比 up 多 2，将 down 堆顶移到 up 如果 up 的元素个数比 down 多 1，将 up 堆顶移到 down 计算中位数： 如果两个堆的元素个数相等，中位数为两个堆顶的平均值； 如果 down 堆的元素个数比 up 多 1，中位数为 down 堆顶的值 时间复杂度 插入操作：O(logn) （堆的插入与删除） 获取中位数：O(1) 总体复杂度：O(logn) （单次操作）\n空间复杂度 使用两个堆存储所有元素，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class MedianFinder { public: // 大顶堆，存储较小的一半元素 std::priority_queue\u0026lt;int\u0026gt; down; // 小顶堆，存储较大的一半元素 std::priority_queue\u0026lt;int, std::vector\u0026lt;int\u0026gt;, std::greater\u0026lt;int\u0026gt;\u0026gt; up; MedianFinder() {} void addNum(int num) { // 插入到适当的堆 if (down.empty() || num \u0026lt;= down.top()) down.push(num); else up.push(num); // 调整堆的平衡 if (down.size() == up.size() + 2) { int x = down.top(); down.pop(); up.push(x); } else if (down.size() + 1 == up.size()) { int x = up.top(); up.pop(); down.push(x); } } double findMedian() { if (down.size() == up.size()) return (down.top() + up.top()) / 2.0; return down.top(); } }; ","date":"2024-12-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-median-from-data-stream/","title":"Find Median From Data Stream"},{"content":"215. 数组中的第K个最大元素 分析 分区过程：\n使用双指针 i 和 j ： i 向右找到第一个小于等于基准值的元素； j 向左找到第一个大于等于基准值的元素； 交换 i 和 j 的位置，确保左侧元素大于基准值，右侧元素小于基准值 递归判断：\n若 k 小于等于划分点索引 j ，说明目标在左区间； 若 k 大于划分点索引 j ，说明目标在右区间； 继续递归查找，直到缩小到单元素区间 处理第 k 大：\nk 是基于第 1 个元素的索引，因此将 k - 1 转换为 0 索引形式进行处理 时间复杂度 平均情况下为 O(n) ：每次划分能排除约一半的元素 最坏情况下为 O(n^2) ：当数组退化为不平衡分区时 空间复杂度 O(log n)：递归栈的深度，取决于分区的层数\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: // 快速排序分治查找第 k 大元素 int quick_sort(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int l, int r, int k) { // 当区间缩小到单个元素时，返回该元素 if (l == r) return nums[k]; // 基准值选择第一个元素 int x = nums[l], i = l - 1, j = r + 1; // 快速排序分区过程：双指针划分 while (i \u0026lt; j) { do ++i; while (nums[i] \u0026gt; x); // 找到左侧第一个小于等于基准值的元素 do --j; while (nums[j] \u0026lt; x); // 找到右侧第一个大于等于基准值的元素 if (i \u0026lt; j) std::swap(nums[i], nums[j]); // 交换元素使其划分到正确的区间 } // 判断第 k 大元素所在区间 if (k \u0026lt;= j) return quick_sort(nums, l, j, k); // 在左侧区间 else return quick_sort(nums, j + 1, r, k); // 在右侧区间 } // 主函数：寻找第 k 大元素 int findKthLargest(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { return quick_sort(nums, 0, nums.size() - 1, k - 1); // 转换为 0 索引 } }; ","date":"2024-12-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/kth-largest-element-in-an-array/","title":"Kth Largest Element In An Array"},{"content":"84. 柱状图中的最大矩形 分析 确定左右边界：\n左边界：对于柱子 heights[i]，寻找它左边第一个比它小的柱子位置 left[i] 使用一个单调递增栈，从左到右遍历数组： 当前柱子比栈顶柱子低时，弹出栈顶柱子，找到当前柱子作为某柱右边界的场景 当前柱子的左边界就是栈顶元素（若栈为空，则左边界为 -1） 右边界：对于柱子 heights[i]，寻找它右边第一个比它小的柱子位置 right[i] 使用相同逻辑，从右向左遍历数组，确定每个柱子的右边界 利用单调栈快速找到这些边界 计算最大矩形面积：\n每个柱子的宽度 = right[i] - left[i] - 1 面积 = 柱高度 × 宽度，取最大值 时间复杂度 每个柱子在左右边界的遍历中最多进栈和出栈一次，总时间复杂度为 O(n)\n空间复杂度 需要存储左右边界和栈，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public: int largestRectangleArea(vector\u0026lt;int\u0026gt;\u0026amp; heights) { int n = heights.size(); std::vector\u0026lt;int\u0026gt; left(n), right(n); // 左边界和右边界 std::stack\u0026lt;int\u0026gt; stk; // 确定左边界 for (int i = 0; i \u0026lt; n; ++i) { while (!stk.empty() \u0026amp;\u0026amp; heights[i] \u0026lt;= heights[stk.top()]) stk.pop(); left[i] = stk.empty() ? -1 : stk.top(); stk.push(i); } // 清空栈，重新计算右边界 stk = std::stack\u0026lt;int\u0026gt;(); for (int i = n - 1; i \u0026gt;= 0; --i) { while (!stk.empty() \u0026amp;\u0026amp; heights[i] \u0026lt;= heights[stk.top()]) stk.pop(); right[i] = stk.empty() ? n : stk.top(); stk.push(i); } // 计算最大矩形面积 int res = 0; for (int i = 0; i \u0026lt; n; ++i) res = std::max(res, heights[i] * (right[i] - left[i] - 1)); return res; } }; ","date":"2024-12-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/largest-rectangle-in-histogram/","title":"Largest Rectangle In Histogram"},{"content":"347. 前K个高频元素 分析 统计频率： 使用哈希表 hash 记录数组中每个元素的出现次数，键为数组元素，值为对应频率 统计频率分布： 创建一个计数数组 count，其中 count[i] 表示频率为 i 的元素个数 确定频率阈值： 从高频到低频累加 count，直到累计的元素数达到 k ，此时的频率即为筛选阈值 i 筛选元素： 再次遍历哈希表 hash，将频率大于阈值 i 的元素加入结果数组 res 时间复杂度 统计频率：O(n) ，遍历数组 统计频率分布：O(m) ，m 为哈希表的大小 筛选元素：O(m) 总复杂度：O(n + m) ，其中 m \u0026lt;= n\n空间复杂度 哈希表存储频率： O(m) 频率分布数组：O(n) 总复杂度：O(n + m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: vector\u0026lt;int\u0026gt; topKFrequent(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { // 1. 统计每个元素的频率 std::unordered_map\u0026lt;int, int\u0026gt; hash; for (int num : nums) ++hash[num]; // 2. 统计每个频率的出现次数 int n = nums.size(); std::vector\u0026lt;int\u0026gt; count(n + 1); for (std::pair\u0026lt;int, int\u0026gt; item : hash) ++count[item.second]; // 3. 找到频率阈值 int i = n, sum = 0; while (sum \u0026lt; k) sum += count[i--]; // 4. 筛选出符合条件的元素 std::vector\u0026lt;int\u0026gt; res; for (std::pair\u0026lt;int, int\u0026gt; item : hash) if (item.second \u0026gt; i) res.push_back(item.first); return res; } }; ","date":"2024-12-09T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/top-k-frequent-elements/","title":"Top K Frequent Elements"},{"content":"739. 每日温度 分析 倒序遍历： 从最后一天向第一天遍历温度数组 因为从后向前，可以快速找到当前温度后面更高的温度 使用单调递减栈： 栈中保存数组的下标，保证栈内元素对应的温度单调递减 每次遇到更高的温度时，将栈顶元素弹出，更新结果数组 计算结果： 如果栈不为空，当前栈顶元素对应的温度是下一个更高温度，计算两者下标的差值 如果栈为空，表示后面没有更高温度，结果置为 0 将当前天的下标压入栈，供后续处理 时间复杂度 每个元素最多被压栈和弹栈一次，总时间复杂度 O(n)\n空间复杂度 使用了一个栈，最坏情况下栈中可能存储所有天数，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: vector\u0026lt;int\u0026gt; dailyTemperatures(vector\u0026lt;int\u0026gt;\u0026amp; temperatures) { // 初始化结果数组，长度与输入数组相同，初始值为 0 std::vector\u0026lt;int\u0026gt; res(temperatures.size()); // 单调栈，存储下标 std::stack\u0026lt;int\u0026gt; stk; // 从后向前遍历温度数组 for (int i = temperatures.size() - 1; i \u0026gt;= 0; --i) { // 弹出所有不满足更高温度条件的栈顶元素 while (!stk.empty() \u0026amp;\u0026amp; temperatures[i] \u0026gt;= temperatures[stk.top()]) stk.pop(); // 如果栈不为空，计算下标差值作为结果 if (!stk.empty()) res[i] = stk.top() - i; // 将当前下标压入栈 stk.push(i); } return res; } }; ","date":"2024-12-08T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/daily-temperatures/","title":"Daily Temperatures"},{"content":"394. 字符串解码 分析 使用两个栈： 一个存储重复次数 nums 一个存储当前解码过程中尚未完成的字符串 strs 遍历输入字符串 s： 如果是数字字符 isdigit()，将其加入当前数字 num，用于解析多位数字 如果是左括号 '['，将当前数字 num 和当前字符串 str 压栈，并重置 num 和 str 如果是右括号 ']'： 弹出栈顶的数字 n（重复次数） 弹出栈顶的字符串（上一级未完成的字符串） 将当前字符串重复 n 次后附加到弹出的字符串中，作为当前解码结果 如果是普通字符，直接加入当前字符串 str 最后返回构造好的字符串 时间复杂度 每个字符都会被处理一次，栈操作均为 O(1) ，总时间复杂度 O(n^2)\n空间复杂度 需要两个栈来存储数字和字符串，栈的最大深度取决于嵌套层数，空间复杂度为 O(m)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Solution { public: string decodeString(string s) { // 栈 nums 保存每段的重复次数，栈 strs 保存每段解码的部分字符串 std::stack\u0026lt;int\u0026gt; nums; std::stack\u0026lt;std::string\u0026gt; strs; int num = 0; // 当前数字（重复次数） std::string str; // 当前构造的字符串 for (int i = 0; i \u0026lt; s.size(); ++i) { if (std::isdigit(s[i])) { // 累积数字，支持多位数字解析 num = num * 10 + s[i] - \u0026#39;0\u0026#39;; } else if (s[i] == \u0026#39;[\u0026#39;) { // 遇到 \u0026#39;[\u0026#39;，将当前数字和字符串压栈 nums.push(num); strs.push(str); num = 0; // 重置 num str = \u0026#34;\u0026#34;; // 重置当前字符串 } else if (s[i] == \u0026#39;]\u0026#39;) { // 遇到 \u0026#39;]\u0026#39;，进行解码 int n = nums.top(); // 获取当前的重复次数 nums.pop(); std::string cur = str; // 当前字符串 str = strs.top(); // 获取上一层未完成的字符串 strs.pop(); // 将当前字符串重复 n 次并追加到上一层字符串后 while (n--) str += cur; } else { // 普通字符直接追加到当前字符串中 str += s[i]; } } return str; } }; ","date":"2024-12-08T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/decode-string/","title":"Decode String"},{"content":"155. 最小栈 分析 为了支持常数时间获取最小值，可以使用两个栈：\n主栈（stk）： 保存所有入栈的元素\n辅助栈（f）： 保存每次操作后的当前最小值\n每当一个值入栈时，如果它小于或等于当前最小值f.top()，将其压入辅助栈 当一个值出栈时，如果它等于辅助栈的栈顶，也从辅助栈中弹出 时间复杂度 push、pop、top 和 getMin 的时间复杂度均为 O(1) ，因为每个操作只涉及栈的入栈、出栈或访问栈顶\n空间复杂度 主栈 stk 存储所有元素，占用 O(n) 空间 辅助栈 f 最多存储所有元素，占用 O(n) 空间 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class MinStack { public: std::stack\u0026lt;int\u0026gt; stk; // 主栈，存储所有元素 std::stack\u0026lt;int\u0026gt; f; // 辅助栈，存储当前最小值 MinStack() { // 构造函数，初始化空栈 } void push(int val) { stk.push(val); // 将值压入主栈 // 若辅助栈为空或当前值小于等于栈顶最小值，将其压入辅助栈 if (f.empty() || val \u0026lt;= f.top()) f.push(val); } void pop() { // 若主栈栈顶元素等于辅助栈栈顶元素，同时弹出两个栈的栈顶元素 if (stk.top() == f.top()) f.pop(); stk.pop(); } int top() { return stk.top(); // 返回主栈栈顶元素 } int getMin() { return f.top(); // 返回辅助栈栈顶元素，即当前最小值 } }; ","date":"2024-12-08T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/min-stack/","title":"Min Stack"},{"content":"4. 寻找两个正序数组的中位数 分析 采用 二分查找 的方法，避免直接合并数组：\n总长度奇偶性判断： 如果两个数组合并后的长度为奇数，只需找到第 total / 2 + 1 小的数 如果为偶数，需找到第 total / 2 和第 total / 2 + 1 小的数，然后取平均值 递归查找第 k 小的数： 边界条件（递归终止条件）： 如果一个数组为空，直接返回另一个数组中的第 k 小的数 如果 k = 1，返回两个数组当前起点的较小值 二分递归： 比较两个数组中第 k / 2 小的元素 较小的那一部分不可能包含第 k 小的数，因此可以直接从数组中排除 递归更新起始位置和 k 的值 时间复杂度 每次递归将搜索范围缩小一半，因此时间复杂度为 O(log(min(m, n)))\n空间复杂度 空间复杂度为递归调用的栈空间，最大为 O(log(min(m, n)))\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public: double findMedianSortedArrays(vector\u0026lt;int\u0026gt;\u0026amp; nums1, vector\u0026lt;int\u0026gt;\u0026amp; nums2) { // 计算总长度 int total = nums1.size() + nums2.size(); // 如果总长度为偶数，需找到中间两个数并取平均值 if (total % 2 == 0) { double left = find(nums1, 0, nums2, 0, total / 2); double right = find(nums1, 0, nums2, 0, total / 2 + 1); return (left + right) / 2; } // 如果总长度为奇数，找到中间的数 return find(nums1, 0, nums2, 0, total / 2 + 1); } double find(std::vector\u0026lt;int\u0026gt;\u0026amp; nums1, int i, std::vector\u0026lt;int\u0026gt;\u0026amp; nums2, int j, int k) { // 让 nums1 始终是较短的数组 if (nums1.size() - i \u0026gt; nums2.size() - j) return find(nums2, j, nums1, i, k); // 边界条件：nums1 已经为空 if (nums1.size() - i == 0) return nums2[j + k - 1]; // 边界条件：k = 1，直接返回两数组当前起点的较小值 if (k == 1) return std::min(nums1[i], nums2[j]); // 二分查找 int mid1 = std::min((int)nums1.size() - 1, i + k / 2 - 1); int mid2 = j + k / 2 - 1; if (nums1[mid1] \u0026gt; nums2[mid2]) // 排除 nums2 前 (mid2 - j + 1) 个元素 return find(nums1, i, nums2, mid2 + 1, k - (mid2 - j + 1)); else // 排除 nums1 前 (mid1 - i + 1) 个元素 return find(nums1, mid1 + 1, nums2, j, k - (mid1 - i + 1)); } }; ","date":"2024-12-07T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/median-of-two-sorted-arrays/","title":"Median of Two Sorted Arrays"},{"content":"838. 推多米诺 分析 字符串首尾哨兵添加：\n为简化边界条件处理，在字符串首部添加 'L'，尾部添加 'R'，变成 dominoes = 'L' + dominoes + 'R' 这样首尾骨牌始终有确定的受力方向 预处理左右最近的受力源：\n定义两个数组 l 和 r： l[i] 表示索引 i 左侧最近的 'L' 的位置； r[i] 表示索引 i 右侧最近的 'R' 的位置 遍历计算： 从左向右遍历填充 l，记录每个位置最近的 'L'； 从右向左遍历填充 r，记录每个位置最近的 'R' 根据受力平衡更新状态：\n遍历每个位置，根据 l[i] 和 r[i] 的值判断受力方向： 如果 dominoes[l[i]] == 'L' \u0026amp;\u0026amp; dominoes[r[i]] == 'R'，受力平衡，骨牌保持竖立 '.'； 如果两侧的受力方向一致，骨牌倒向对应方向； 如果受力方向不同且距离不同，骨牌倒向距离近的一侧； 如果受力方向不同且距离相等，骨牌保持竖立 '.' 移除哨兵：\n返回字符串 dominoes.substr(1, n - 2)，移除首尾哨兵 时间复杂度 遍历三次字符串：分别计算 l 和 r 的值，以及最终更新 dominoes 的状态\n总时间复杂度 O(n)\n空间复杂度 空间复杂度：O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Solution { public: string pushDominoes(string dominoes) { // 添加哨兵 dominoes = \u0026#39;L\u0026#39; + dominoes + \u0026#39;R\u0026#39;; int n = dominoes.size(); std::vector\u0026lt;int\u0026gt; l(n), r(n); // 记录左侧最近的\u0026#39;L\u0026#39; int pos = 0; for (int i = 1; i \u0026lt; n; ++i) { if (dominoes[i] != \u0026#39;.\u0026#39;) pos = i; // 更新最近的\u0026#39;L\u0026#39;或\u0026#39;R\u0026#39; l[i] = pos; // 保存当前位置左侧的最近受力源 } // 记录右侧最近的\u0026#39;R\u0026#39; for (int i = n - 1; i \u0026gt;= 0; --i) { if (dominoes[i] != \u0026#39;.\u0026#39;) pos = i; // 更新最近的\u0026#39;L\u0026#39;或\u0026#39;R\u0026#39; r[i] = pos; // 保存当前位置右侧的最近受力源 } // 判断骨牌最终的状态 for (int i = 0; i \u0026lt; n; ++i) { if (dominoes[l[i]] == \u0026#39;L\u0026#39; \u0026amp;\u0026amp; dominoes[r[i]] == \u0026#39;R\u0026#39;) { dominoes[i] = \u0026#39;.\u0026#39;; // 平衡受力 } else if (dominoes[l[i]] == \u0026#39;L\u0026#39; \u0026amp;\u0026amp; dominoes[r[i]] == \u0026#39;L\u0026#39;) { dominoes[i] = \u0026#39;L\u0026#39;; // 受左侧\u0026#39;L\u0026#39;影响 } else if (dominoes[l[i]] == \u0026#39;R\u0026#39; \u0026amp;\u0026amp; dominoes[r[i]] == \u0026#39;R\u0026#39;) { dominoes[i] = \u0026#39;R\u0026#39;; // 受右侧\u0026#39;R\u0026#39;影响 } else { // 受不同方向影响，根据距离判断 if (i - l[i] \u0026lt; r[i] - i) dominoes[i] = \u0026#39;R\u0026#39;; else if (i - l[i] \u0026gt; r[i] - i) dominoes[i] = \u0026#39;L\u0026#39;; else dominoes[i] = \u0026#39;.\u0026#39;; } } // 移除首尾哨兵 return dominoes.substr(1, n - 2); } }; ","date":"2024-12-07T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/push-dominoes/","title":"Push Dominoes"},{"content":"34. 在排序数组中查找元素的第一个和最后一个位置 分析 寻找左边界：\n目标是找到 target 在数组中的最左侧位置 每次检查 nums[mid]是否大于等于 target 如果是，则缩小右边界 r = mid，以确保找到第一个出现的目标值 如果左边界不存在（即数组中没有 target），直接返回 [-1, -1] 寻找右边界：\n目标是找到 target 在数组中的最右侧位置 每次检查 nums[mid] 是否小于等于 target 如果是，则缩小左边界 l = mid，确保找到最后一个出现的目标值 只有在左边界存在时，才会继续找右边界 时间复杂度 每次二分查找的时间复杂度为 O(log n)，总共两次，因此时间复杂度为 O(log n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 vector\u0026lt;int\u0026gt; searchRange(vector\u0026lt;int\u0026gt;\u0026amp; nums, int target) { // 如果数组为空，直接返回 if (nums.empty()) return {-1, -1}; // 第一次二分查找左边界 int l = 0, r = nums.size() - 1; while (l \u0026lt; r) { int mid = (l + r) \u0026gt;\u0026gt; 1; // 中间位置 if (target \u0026lt;= nums[mid]) r = mid; // 缩小右边界 else l = mid + 1; // 缩小左边界 } // 检查左边界是否存在 if (nums[r] != target) return {-1, -1}; int L = r; // 记录左边界 // 第二次二分查找右边界 l = 0, r = nums.size() - 1; while (l \u0026lt; r) { int mid = (l + r + 1) \u0026gt;\u0026gt; 1; // 中间位置，偏向右 if (target \u0026gt;= nums[mid]) l = mid; // 缩小左边界 else r = mid - 1; // 缩小右边界 } // 返回左右边界 return {L, r}; } ","date":"2024-12-06T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-first-and-last-postion-of-element-in-sorted-array/","title":"Find First and Last Postion of Element In Sorted Array"},{"content":"153. 寻找旋转排序数组中的最小值 分析 未旋转情况：\n如果数组的首元素小于尾元素，说明数组未旋转，直接返回 nums[0] 二分查找旋转点：\n在旋转数组中，最小值位于右部分的起点。 使用二分查找，通过比较 nums[mid] 和 nums[0] 的值，可以判断最小值的所在位置： 如果 nums[mid] \u0026gt;= nums[0]，说明最小值在右侧，缩小左边界 如果 nums[mid] \u0026lt; nums[0]，说明最小值在左侧或当前 mid 是最小值，缩小右边界 最终，r + 1 指向最小值的位置 时间复杂度 二分查找的时间复杂度为 O(logn)\n空间复杂度 使用常量级额外空间，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: int findMin(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int n = nums.size(); // 如果数组未旋转，直接返回首元素 if (nums[0] \u0026lt;= nums[n - 1]) return nums[0]; int l = 0, r = n - 1; while (l \u0026lt; r) { int mid = (l + r + 1) \u0026gt;\u0026gt; 1; // 取中间值 if (nums[0] \u0026lt;= nums[mid]) l = mid; // 最小值在右侧 else r = mid - 1; // 最小值在左侧 } return nums[r + 1]; // 返回最小值 } }; ","date":"2024-12-06T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-minimum-in-rotated-sorted-array/","title":"Find Minimum in Rotated Sorted Array"},{"content":"51. N皇后 分析 棋盘表示： 使用一个长度为 n 的字符串数组 path 表示棋盘，每个字符串表示棋盘的一行，'.' 表示空格，'Q\u0026rsquo; 表示皇后 res 保存所有可能的棋盘方案 冲突检测： 使用三个布尔数组记录皇后占据的列和对角线： col[i] 表示第 i 列是否有皇后 dg[i] 表示正对角线是否有皇后（从左上到右下，索引为 u - i + n） 正对角线元素使用索引u - i来表示，因为正对角线上的元素都满足坐标x - y == 0 棋盘上所有元素坐标之差范围是-(n - 1) ~ n - 1，加偏移量 n 将其映射为1 ~ 2n - 1 udg[i] 表示反对角线是否有皇后（从右上到左下，索引为 u + i） 反对角线元素使用u + i来表示，因为反对角线上的元素都满足坐标x + y == n 棋盘上所有元素坐标之和范围为0 ~ 2(n - 1)，因此不需要加偏移量 回溯放置皇后： 从第 u 行开始，尝试在每一列放置皇后 如果当前列、正对角线、反对角线均未被占用，则将皇后放置在对应位置，并标记这些区域为占用 递归尝试放置下一行的皇后 回溯时，将皇后移除，并恢复状态 结果保存： 当递归到最后一行 u == n 时，说明当前方案有效，将棋盘方案保存到结果中 时间复杂度 每一行最多有 n 个选择，搜索深度为 n，回溯过程的总时间复杂度约为 O(n!)\n空间复杂度 需要存储棋盘状态 path 和标记数组（col、dg、udg），每个的大小为 O(n)\n总空间复杂度为 O(n²)，主要用于存储结果\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;std::string\u0026gt;\u0026gt; res; // 存储所有有效的棋盘方案 std::vector\u0026lt;std::string\u0026gt; path; // 当前棋盘状态 std::vector\u0026lt;bool\u0026gt; col, dg, udg; // 列、正对角线、反对角线的占用状态 vector\u0026lt;vector\u0026lt;string\u0026gt;\u0026gt; solveNQueens(int n) { // 初始化棋盘和标记数组 path = std::vector\u0026lt;std::string\u0026gt;(n, std::string(n, \u0026#39;.\u0026#39;)); col = std::vector\u0026lt;bool\u0026gt;(n); dg = std::vector\u0026lt;bool\u0026gt;(2 * n); udg = std::vector\u0026lt;bool\u0026gt;(2 * n); // 开始回溯 dfs(n, 0); return res; } void dfs(int n, int u) { // 如果所有行都放置完成，保存当前棋盘方案 if (u == n) { res.push_back(path); return; } // 遍历当前行的每一列，尝试放置皇后 for (int i = 0; i \u0026lt; n; ++i) { if (!col[i] \u0026amp;\u0026amp; !dg[u - i + n] \u0026amp;\u0026amp; !udg[u + i]) // 检查是否冲突 { // 放置皇后并标记状态 col[i] = dg[u - i + n] = udg[u + i] = true; path[u][i] = \u0026#39;Q\u0026#39;; // 递归放置下一行 dfs(n, u + 1); // 回溯：移除皇后并恢复状态 path[u][i] = \u0026#39;.\u0026#39;; col[i] = dg[u - i + n] = udg[u + i] = false; } } } }; ","date":"2024-12-06T00:00:00Z","image":"https://blog.hangzhang.cv/codetest.jpg","permalink":"https://blog.hangzhang.cv/p/n-queens/","title":"N Queens"},{"content":"35. 搜索插入位置 分析 定义搜索范围： 使用两个指针 l 和 r 分别表示数组的左边界和右边界，初始值为 0 和 nums.size() 注意右边界初始值为 nums.size()，因为我们需要处理目标值可能插入在数组末尾的情况 进行二分查找： 计算中间位置 mid = (l + r) \u0026gt;\u0026gt; 1 如果 target \u0026lt;= nums[mid]： target 可能在 mid 位置或其左侧，更新右边界 r = mid 如果 target \u0026gt; nums[mid]： target 一定在 mid 右侧，更新左边界 l = mid + 1 返回结果： 最终，l 和 r 会收敛到目标值的位置 如果 target 不在数组中，返回的是它应该插入的位置 时间复杂度 二分查找的时间复杂度为 O(log n)，其中 n 是数组长度\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 int searchInsert(vector\u0026lt;int\u0026gt;\u0026amp; nums, int target) { int l = 0, r = nums.size(); while (l \u0026lt; r) // 当左边界小于右边界时，继续搜索 { int mid = (l + r) \u0026gt;\u0026gt; 1; // 计算中点 if (target \u0026lt;= nums[mid]) r = mid; // 缩小右边界，可能找到目标或插入点 else l = mid + 1; // 缩小左边界 } return r; // 返回插入位置 } ","date":"2024-12-06T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/search-insert-position/","title":"Search Insert Position"},{"content":"39. 数组总和 分析 使用深度优先搜索（DFS）+ 回溯的方法 遍历候选数组 candidates 时，允许重复选择当前数字，目标值 target 减去选中的数字，直到达到目标（target == 0） 若 target 不为 0，但已遍历完数组，表示当前路径无解，返回上一层 回溯时移除已选择的数字 关键：每次递归时，既允许不选当前数字，也允许多次选取当前数字\n时间复杂度 最坏情况下，candidates 长度为 n，每次递归中需要尝试所有可能的组合，时间复杂度为 O(2^n)\n空间复杂度 递归深度为 target / min(candidates)，路径存储需要 O(target / min(candidates))\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储结果集 std::vector\u0026lt;int\u0026gt; path; // 当前路径 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; combinationSum(vector\u0026lt;int\u0026gt;\u0026amp; candidates, int target) { dfs(candidates, 0, target); // 从第一个数字开始递归 return res; } void dfs(std::vector\u0026lt;int\u0026gt;\u0026amp; candidates, int u, int target) { if (target == 0) { // 目标值为0，找到一种组合 res.push_back(path); return; } if (u == candidates.size()) // 遍历完所有数字 return; // 尝试选择当前数字若干次 for (int i = 0; i * candidates[u] \u0026lt;= target; ++i) { dfs(candidates, u + 1, target - i * candidates[u]); // 递归 path.push_back(candidates[u]); // 将当前数字加入路径 } for (int i = 0; i * candidates[u] \u0026lt;= target; ++i) { path.pop_back(); // 回溯，移除当前数字 } } }; ","date":"2024-12-05T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/combination-sum/","title":"Combination Sum"},{"content":"131. 分割回文串 分析 预处理判断回文子串： 通过动态规划预处理所有可能的子串，判断每个子串是否是回文 定义二维数组 f[i][j]，表示从 i 到 j 的子串是否为回文： 若 s[i] == s[j]，且内部的子串 s[i+1:j-1] 也是回文，则 f[i][j] = true 特殊情况下（i == j 或 j == i+1），直接判断字符是否相等 回溯搜索分割方案： 从字符串的第一个字符开始尝试分割 若当前子串是回文，则将其加入路径中，继续搜索剩余部分 如果遍历完整个字符串，将当前路径保存为一种结果 搜索结束后，将最后加入路径的子串弹出，回溯到上一步继续尝试 时间复杂度 预处理回文数组：需要枚举所有子串，时间复杂度为 O(n²) 回溯搜索：每个字符有两种选择（分割或不分割），复杂度近似为 O(2ⁿ) 总体时间复杂度：O(n² + 2ⁿ)\n空间复杂度 动态规划数组 f 占用 O(n²) 递归深度最多为字符串长度 n，路径数组和递归栈占用 O(n) 总体空间复杂度：O(n²)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;std::string\u0026gt;\u0026gt; res; // 存储最终结果 std::vector\u0026lt;std::string\u0026gt; path; // 当前路径 std::vector\u0026lt;std::vector\u0026lt;bool\u0026gt;\u0026gt; f; // 动态规划存储回文判断结果 vector\u0026lt;vector\u0026lt;string\u0026gt;\u0026gt; partition(string s) { int n = s.size(); // 初始化回文判断数组 f = std::vector\u0026lt;std::vector\u0026lt;bool\u0026gt;\u0026gt;(n, std::vector\u0026lt;bool\u0026gt;(n)); // 动态规划预处理回文子串 for (int j = 0; j \u0026lt; n; ++j) for (int i = 0; i \u0026lt;= j; ++i) { if (i == j) // 单个字符是回文 f[i][j] = true; else if (s[i] == s[j]) // 判断两端字符是否相等 if (i + 1 \u0026gt; j - 1 || f[i + 1][j - 1]) // 内部子串为空或回文 f[i][j] = true; } // 开始回溯搜索 dfs(s, 0); return res; } void dfs(std::string s, int u) { // 如果已经分割到字符串末尾，保存当前路径 if (u == s.size()) { res.push_back(path); return; } // 遍历从 u 开始的每个子串 for (int i = u; i \u0026lt; s.size(); ++i) if (f[u][i]) // 如果当前子串是回文 { path.push_back(s.substr(u, i - u + 1)); // 加入路径 dfs(s, i + 1); // 递归处理剩余部分 path.pop_back(); // 回溯 } } }; ","date":"2024-12-05T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/palindrome-partitioning/","title":"Palindrome Partitioning"},{"content":"79. 单词搜索 分析 从任意位置出发向下 dfs，只在搜到时，返回 true 若当前位置与 word 不等，返回 false 否则说明相等，检查是否搜到末尾，若搜到末尾返回 true 否则继续向下搜索 暂存当前字符 向四个方向向下 dfs 恢复现场 时间复杂度 每个网格单元都可能作为起点触发一次深度优先搜索。 搜索深度最多为 word.length。 最坏情况下，搜索路径为网格大小 m × n 的全部单元 时间复杂度为 O(m × n × 4^l)，其中 l 是单词的长度\n空间复杂度 递归栈的深度最多为单词的长度 l，空间复杂度为 O(l)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution { public: bool exist(vector\u0026lt;vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; board, string word) { // 遍历整个网格 for (int i = 0; i \u0026lt; board.size(); ++ i) for (int j = 0; j \u0026lt; board[0].size(); ++ j) if (dfs(board, word, 0, i, j)) return true; return false; // 如果遍历完所有位置都未找到，返回false } bool dfs(std::vector\u0026lt;std::vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; board, std::string\u0026amp; word, int u, int x, int y) { // 当前字符与目标单词的字符不匹配 if (board[x][y] != word[u]) return false; // 匹配到单词的最后一个字符，返回true if (u == word.size() - 1) return true; // 标记当前字符已访问 char temp = board[x][y]; board[x][y] = \u0026#39;*\u0026#39;; int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; for (int i = 0; i \u0026lt; 4; ++ i) { int a = x + dx[i], b = y + dy[i]; // 确保新的位置不越界且未访问 if (a \u0026lt; 0 || a \u0026gt;= board.size() || b \u0026lt; 0 || b \u0026gt;= board[0].size() || board[a][b] == \u0026#39;*\u0026#39;) continue; // 递归检查下一字符是否匹配 if (dfs(board, word, u + 1, a, b)) return true; } // 恢复当前字符，继续尝试其他路径 board[x][y] = temp; return false; } }; ","date":"2024-12-05T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/word-search/","title":"Word Search"},{"content":"46. 全排列 分析 思路 每次递归选择一个未使用的数字加入当前路径，直到路径长度等于数组长度，记录当前排列 初始化 用 res 保存最终的所有排列结果 用 path 保存当前排列路径 用布尔数组 is_used 标记某个数字是否已被使用，避免重复选择 回溯逻辑 dfs 如果当前路径长度 u 等于数组长度，保存当前路径到结果集 res 中 遍历数组，对未使用的数字： 标记为已使用 将数字加入路径 递归进入下一层 回溯时取消标记并移除路径中的数字 时间复杂度 时间复杂度：O(n!)，因为共有 n! 种排列\n空间复杂度 空间复杂度：O(n)，递归调用栈深度为 n，path 和 is_used 数组均为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储最终结果 std::vector\u0026lt;int\u0026gt; path; // 当前排列路径 std::vector\u0026lt;bool\u0026gt; is_used; // 标记是否使用过当前数字 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; permute(vector\u0026lt;int\u0026gt;\u0026amp; nums) { // 初始化 path 和 is_used 数组 path = std::vector\u0026lt;int\u0026gt;(nums.size()); is_used = std::vector\u0026lt;bool\u0026gt;(nums.size()); // 调用回溯函数 dfs(nums, 0); return res; } void dfs(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int u) { // 如果路径长度等于数组长度，保存结果 if (u == nums.size()) { res.push_back(path); return; } // 遍历所有数字，尝试加入当前路径 for (int i = 0; i \u0026lt; nums.size(); ++i) { if (!is_used[i]) { // 如果当前数字未使用 is_used[i] = true; // 标记当前数字已使用 path[u] = nums[i]; // 加入当前路径 dfs(nums, u + 1); // 递归生成下一个位置的数字 is_used[i] = false; // 回溯时取消标记 } } } }; ","date":"2024-12-04T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/permutations/","title":"Permutations"},{"content":"78. 子集 分析 回溯法 思路 每个元素都有两种状态：选入当前子集或不选入当前子集 对每个元素进行选择，并递归处理剩余元素 当递归到数组末尾时，将当前子集 path 加入结果集 res 步骤 用 res 存储所有子集结果 用 path 存储当前子集路径 用递归函数 dfs(nums, u) 表示从索引 u 开始构造子集 如果索引到达数组末尾，将当前路径加入结果集 否则： 跳过当前元素，继续递归 包含当前元素，继续递归，并在递归结束后回溯状态 位运算枚举 位运算思路\n每个子集可以看作长度为 n 的二进制数，每一位 1 表示对应的数组元素被选中 枚举从 0 到 2^n - 1 的所有整数，用二进制表示子集选择情况 对于每个整数： 遍历其二进制的每一位，若某一位为 1，将对应位置的数组元素加入当前子集 将生成的子集加入结果集 步骤\n初始化结果集 res 遍历从 0 到 2^n - 1 的整数，用位运算检查每一位是否为 1 若某位为 1，将对应的数组元素加入当前子集 path 枚举结束后返回结果集 res 时间复杂度 每个元素有两种状态（选或不选），总状态数为 2^n。遍历每个状态生成子集，总时间复杂度 O(n^2)\n空间复杂度 递归调用栈深度为 O(n)，路径数组 path 最多占用 O(n)，总空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储所有子集 std::vector\u0026lt;int\u0026gt; path; // 当前子集路径 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; subsets(vector\u0026lt;int\u0026gt;\u0026amp; nums) { dfs(nums, 0); // 从索引 0 开始回溯 return res; } void dfs(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int u) { // 递归终止条件：索引越界时，将当前路径加入结果集 if (u == nums.size()) { res.push_back(path); return; } // 不选择当前元素，继续递归 dfs(nums, u + 1); // 选择当前元素，加入路径并继续递归 path.push_back(nums[u]); dfs(nums, u + 1); // 回溯状态，移除当前元素 path.pop_back(); } }; class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; subsets(vector\u0026lt;int\u0026gt;\u0026amp; nums) { std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; // 存储所有子集 int n = nums.size(); // 枚举所有可能的子集，用二进制表示 for (int i = 0; i \u0026lt; (1 \u0026lt;\u0026lt; n); ++i) { std::vector\u0026lt;int\u0026gt; path; // 当前子集 for (int j = 0; j \u0026lt; n; ++j) { if ((i \u0026gt;\u0026gt; j) \u0026amp; 1) // 若第 j 位为 1，加入对应元素 path.push_back(nums[j]); } res.push_back(path); // 将当前子集加入结果集 } return res; } }; ","date":"2024-12-04T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/subsets/","title":"subsets"},{"content":"207. 课程表 分析 构建图结构与入度数组 使用邻接表 g 表示课程之间的依赖关系：对于 prerequisites[i] = [a, b]，将 b -\u0026gt; a 加入图中 使用入度数组 d 记录每个课程的入度（依赖它的先修课程数量） 初始化队列 将入度为 0 的课程（无依赖课程）加入队列 q，这些课程可以直接开始学习 拓扑排序 从队列中取出课程，计数器 res 加 1（表示完成了一门课程） 遍历该课程的邻接课程，将它们的入度减 1。如果某门课程的入度减为 0，将其加入队列 重复直到队列为空 判断是否可行 如果最终完成课程数 res 等于 numCourses，说明可以完成所有课程，返回 true 否则返回 false，表示存在环形依赖，课程无法全部完成 时间复杂度 构建图：遍历所有的先修课程关系，时间复杂度为 O(E)，其中 E 是先修课程的数量 拓扑排序：每门课程和它的邻接课程都最多被访问一次，时间复杂度为 O(V)，其中 V 是课程数 总时间复杂度 O(E + V)\n空间复杂度 图邻接表 g 占用 O(E) 空间 入度数组 d 占用 O(V) 空间。 队列 q 的最大空间占用为 O(V) 空间复杂度为 O(E + V)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public: bool canFinish(int numCourses, vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; prerequisites) { // 1. 构建图和入度数组 std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; g(numCourses); // 邻接表 std::vector\u0026lt;int\u0026gt; d(numCourses); // 入度数组 for (std::vector\u0026lt;int\u0026gt;\u0026amp; pre : prerequisites) { g[pre[1]].push_back(pre[0]); // 构建图：b -\u0026gt; a ++d[pre[0]]; // 课程 a 的入度加 1 } // 2. 初始化队列 std::queue\u0026lt;int\u0026gt; q; for (int i = 0; i \u0026lt; numCourses; ++i) { if (d[i] == 0) // 入度为 0 的课程入队 q.push(i); } // 3. 拓扑排序 int res = 0; // 记录完成课程数量 while (!q.empty()) { ++res; // 每完成一门课程，计数加 1 int i = q.front(); q.pop(); for (int j : g[i]) { // 遍历当前课程的后续课程 if (--d[j] == 0) // 后续课程入度减 1 q.push(j); // 如果入度变为 0，加入队列 } } // 4. 判断结果 return res == numCourses; // 如果完成课程数等于总课程数，返回 true } }; ","date":"2024-12-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/course-schedule/","title":"Course Schedule"},{"content":"208. 实现Trie树 分析 定义 Trie 树的节点结构 每个节点包含以下信息： is_end：标志当前节点是否是某个单词的结束节点 son[26]：一个数组，用于存储指向 26 个小写英文字母子节点的指针。如果某个字母子节点不存在，则为 nullptr 初始化 Trie 树 构造函数中初始化根节点 root 插入单词 insert() 从根节点出发，逐字符检查单词： 如果当前字符对应的子节点不存在，则创建新节点 移动到对应的子节点。 最后将当前节点标记为结束节点 is_end = true 搜索单词 search() 从根节点出发，逐字符遍历单词： 如果某字符对应的子节点不存在，返回 false 如果全部字符都匹配且最后节点是结束节点，返回 true 否则，返回 false 检查前缀 startsWith() 与 search 类似，但不需要检查是否是结束节点。只要能完整遍历到前缀，返回 true；否则返回 false 时间复杂度 insert()：O(n)，其中 n 是单词长度，每次需要逐字符插入 search()：O(n)，需要逐字符查找 startsWith()：O(n)，需要逐字符查找 空间复杂度 每个节点存储 26 个指针，整体空间复杂度取决于插入的单词集合的总字符数\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class Trie { public: // 定义 Trie 树节点类型 struct Node { Node() { is_end = false; for (int i = 0; i \u0026lt; 26; ++i) son[i] = nullptr; } bool is_end; // 当前节点是否是某个单词的结束 Node* son[26]; // 指向 26 个子节点的指针数组 }; Node* root; // Trie 树的根节点 Trie() { root = new Node(); // 初始化根节点 } // 插入单词 void insert(string word) { Node* p = root; for (char c : word) { int u = c - \u0026#39;a\u0026#39;; // 计算字符对应的子节点索引 if (!p-\u0026gt;son[u]) // 如果不存在该子节点，则创建新节点 p-\u0026gt;son[u] = new Node(); p = p-\u0026gt;son[u]; // 移动到子节点 } p-\u0026gt;is_end = true; // 标记单词结束 } // 搜索单词 bool search(string word) { Node* p = root; for (char c : word) { int u = c - \u0026#39;a\u0026#39;; // 计算字符对应的子节点索引 if (!p-\u0026gt;son[u]) // 如果子节点不存在，返回 false return false; p = p-\u0026gt;son[u]; // 移动到子节点 } return p-\u0026gt;is_end; // 如果最后是结束节点，返回 true } // 检查前缀 bool startsWith(string prefix) { Node* p = root; for (char c : prefix) { int u = c - \u0026#39;a\u0026#39;; // 计算字符对应的子节点索引 if (!p-\u0026gt;son[u]) // 如果子节点不存在，返回 false return false; p = p-\u0026gt;son[u]; // 移动到子节点 } return true; // 如果能遍历完整前缀，返回 true } }; ","date":"2024-12-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/implement-trie-prefix-tree/","title":"Implement Trie Prefix Tree"},{"content":"200. 岛屿数量 分析 岛屿的定义 '1' 表示陆地，'0' 表示水 一个岛屿是由相邻（水平或垂直方向）的 '1' 连续组成的一块区域 遍历网格 遍历网格中每个位置 (i, j)，若当前位置为 '1'，说明发现了一个新的岛屿： 执行深度优先搜索 dfs() 标记当前岛屿的所有陆地为 '*'，表示已访问 岛屿数量加 1 深度优先搜索dfs() 从当前陆地 (x, y) 出发，尝试向四个方向移动（上、下、左、右） 若移动后的位置仍为 '1' 且未越界，递归调用 dfs() 继续标记 时间复杂度 每个格子最多被访问一次，时间复杂度为 O(m * n)，其中 m 和 n 分别是网格的行数和列数\n空间复杂度 递归栈深度取决于岛屿的最大面积，最坏情况下为 O(m * n)，即岛屿覆盖整个网格\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;char\u0026gt;\u0026gt; g; // 全局保存网格 int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; // 四个方向 int numIslands(vector\u0026lt;vector\u0026lt;char\u0026gt;\u0026gt;\u0026amp; grid) { g = grid; // 初始化网格 int res = 0; // 岛屿数量 for (int i = 0; i \u0026lt; g.size(); ++i) for (int j = 0; j \u0026lt; g[0].size(); ++j) if (g[i][j] == \u0026#39;1\u0026#39;) // 找到未访问的陆地 { dfs(i, j); // 标记整个岛屿 ++res; // 岛屿数量加 1 } return res; } void dfs(int x, int y) { g[x][y] = \u0026#39;*\u0026#39;; // 标记当前位置为已访问 for (int i = 0; i \u0026lt; 4; ++i) // 尝试向四个方向移动 { int a = x + dx[i], b = y + dy[i]; if (a \u0026gt;= 0 \u0026amp;\u0026amp; a \u0026lt; g.size() \u0026amp;\u0026amp; b \u0026gt;= 0 \u0026amp;\u0026amp; b \u0026lt; g[0].size() \u0026amp;\u0026amp; g[a][b] == \u0026#39;1\u0026#39;) dfs(a, b); // 若新位置是未访问的陆地，递归处理 } } }; ","date":"2024-12-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/number-of-islands/","title":"Number of Islands"},{"content":"994. 腐烂的橘子 分析 初始状态 遍历整个网格 grid，找到所有腐烂橘子（值为 2），并将其坐标加入队列 q 中 初始化时间计数器 res 为 -1，表示腐烂传播的分钟数 广度优先搜索BFS扩散 使用 BFS 模拟腐烂橘子的扩散过程，每次遍历队列中的腐烂橘子，将其四周的相邻新鲜橘子（值为 1）腐烂，并将新腐烂的橘子加入队列 每完成一次队列的扩散操作，时间计数器 res 加 1 检查剩余新鲜橘子 遍历网格，若仍存在新鲜橘子（值为 1），返回 -1 否则返回 res，表示所有橘子腐烂所需的最小分钟数 时间复杂度 遍历网格：初始状态下遍历所有单元格 O(n * m) BFS 扩散：每个橘子最多入队一次，时间复杂度为 O(n * m) 总时间复杂度 O(n * m)\n空间复杂度 队列：队列中存储橘子的坐标，最多为 O(m * n) 总空间复杂度：O(m * n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { public: int orangesRotting(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; grid) { using PII = std::pair\u0026lt;int, int\u0026gt;; std::queue\u0026lt;PII\u0026gt; q; int n = grid.size(), m = grid[0].size(); // 将所有初始腐烂橘子的坐标加入队列 for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; m; ++j) if (grid[i][j] == 2) q.push({i, j}); int res = 0; if (!q.empty()) --res; // 队列非空，初始化分钟数为 -1 int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; // BFS 扩散腐烂 while (!q.empty()) { ++res; // 每遍历一层队列，时间加 1 int len = q.size(); while (len--) { PII tmp = q.front(); q.pop(); int x = tmp.first, y = tmp.second; // 遍历四个方向 for (int i = 0; i \u0026lt; 4; ++i) { int a = x + dx[i], b = y + dy[i]; if (a \u0026lt; 0 || a \u0026gt;= n || b \u0026lt; 0 || b \u0026gt;= m || grid[a][b] != 1) continue; q.push({a, b}); // 加入新腐烂的橘子 grid[a][b] = 2; // 标记为腐烂 } } } // 检查是否有剩余新鲜橘子 for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; m; ++j) if (grid[i][j] == 1) return -1; // 仍有新鲜橘子，返回 -1 return res; // 返回所需的分钟数 } }; ","date":"2024-12-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/rotting-oranges/","title":"Rotting Oranges"},{"content":"98. 验证二叉搜索树 分析 二叉搜索树的性质 对于任意节点，其左子树所有节点值均小于该节点值，右子树所有节点值均大于该节点值 递归验证子树 每次检查当前节点是否满足范围条件 [l, r] 如果当前节点值小于等于 l 或大于等于 r，直接返回 false 对左子树递归验证，更新上界为当前节点值 对右子树递归验证，更新下界为当前节点值 递归终止条件 当节点为 nullptr 时，返回 true，表示空子树有效 时间复杂度 每个节点只访问一次，时间复杂度为 O(n)，其中 n 为节点数\n空间复杂度 递归深度由树的高度决定，空间复杂度为 O(h)，其中 h 为树的高度\n最坏情况：链式结构，O(n) 最好情况：平衡二叉树，O(logn) C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: bool isValidBST(TreeNode* root) { // 初始范围设为 LONG_MIN 到 LONG_MAX return dfs(root, LONG_MIN, LONG_MAX); } bool dfs(TreeNode* root, long l, long r) { if (!root) return true; // 空节点为有效子树 // 检查当前节点值是否在合法范围内 if (l \u0026gt;= root-\u0026gt;val || r \u0026lt;= root-\u0026gt;val) return false; // 递归检查左子树和右子树 return dfs(root-\u0026gt;left, l, root-\u0026gt;val) \u0026amp;\u0026amp; dfs(root-\u0026gt;right, root-\u0026gt;val, r); } }; ","date":"2024-12-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/validate-binary-search-tree/","title":"Validate Binary Search Tree"},{"content":"108. 将有序数组转换为二叉搜索树 分析 确定递归终止条件\n如果当前范围内左边界 l 大于右边界 r，返回 nullptr，表示没有节点 递归构造子树\n找到当前范围的中点 mid，该点的值作为当前子树的根节点 递归调用构建左子树，范围为 [l, mid - 1] 递归调用构建右子树，范围为 [mid + 1, r] 返回构造的根节点\n根据中点值创建一个新的节点，连接左子树和右子树，返回该节点作为当前子树的根节点 时间复杂度 递归次数：每次递归划分数组为两半，总体复杂度为 O(logn) 层 每层处理复杂度：每层需要处理数组范围，整体为 O(n) 总时间复杂度 O(n)\n空间复杂度 递归栈空间：递归深度为 O(logn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: TreeNode* sortedArrayToBST(vector\u0026lt;int\u0026gt;\u0026amp; nums) { // 调用递归函数构建平衡二叉搜索树 return build(nums, 0, nums.size() - 1); } TreeNode* build(std::vector\u0026lt;int\u0026gt;\u0026amp; nums, int l, int r) { if (l \u0026gt; r) return nullptr; // 递归终止条件：无效范围 int mid = (l + r) \u0026gt;\u0026gt; 1; // 找到当前范围的中点 TreeNode* root = new TreeNode(nums[mid]); // 创建根节点 root-\u0026gt;left = build(nums, l, mid - 1); // 构建左子树 root-\u0026gt;right = build(nums, mid + 1, r); // 构建右子树 return root; // 返回根节点 } }; ","date":"2024-12-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/convert-sorted-array-to-binary-search-tree/","title":"Convert Sorted Array to Binary Search Tree"},{"content":"543. 二叉树的直径 分析 主函数 diameterOfBinaryTree\n初始化全局变量 res，用来存储最大直径 调用辅助函数 dfs，从根节点开始递归计算深度和路径长度 返回 res 辅助函数 dfs\n基本情况：如果当前节点为空，返回深度为 0 递归计算左右子树深度：分别调用 dfs(root-\u0026gt;left) 和 dfs(root-\u0026gt;right) 更新直径： 当前节点的路径长度为 left + right 更新 res 为 max(res, left + right) 返回节点深度：节点的深度是其左右子树深度的最大值加 1 时间复杂度 时间复杂度 O(n)，其中 n 是节点总数，每个节点访问一次\n空间复杂度 空间复杂度 O(h)，其中 h 是树的高度，递归栈的深度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public: int res = 0; // 保存树的最大直径 int diameterOfBinaryTree(TreeNode* root) { // 调用辅助函数计算直径 dfs(root); return res; } int dfs(TreeNode* root) { // 如果节点为空，返回深度为 0 if (!root) return 0; // 递归计算左右子树的深度 int left = dfs(root-\u0026gt;left); int right = dfs(root-\u0026gt;right); // 更新最大直径：当前节点的左子树深度 + 右子树深度 res = std::max(res, left + right); // 返回当前节点的深度 return std::max(left, right) + 1; } }; ","date":"2024-12-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/diameter-of-binary-tree/","title":"Diameter of Binary Tree"},{"content":"226. 翻转二叉树 分析 递归结束条件 如果当前节点 root 为 nullptr，直接返回 nullptr，表示已到达叶子节点的空子树 交换左右子树 使用 std::swap 交换当前节点的左子树和右子树 递归处理子树 对交换后的左子树调用 invertTree 对交换后的右子树调用 invertTree 返回结果 当所有节点都完成翻转后，返回根节点 root 时间复杂度 每个节点被访问一次，执行左右子树的交换操作，因此时间复杂度为 O(n)，其中 n 是节点总数\n空间复杂度 递归调用的栈深度取决于树的高度。在最坏情况下（单链表形式的树），空间复杂度为 O(n)；在最优情况下（完全平衡二叉树），空间复杂度为 O(logn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // DFS class Solution { public: TreeNode* invertTree(TreeNode* root) { if (!root) return nullptr; // 若节点为空，直接返回 std::swap(root-\u0026gt;left, root-\u0026gt;right); // 交换左右子树 invertTree(root-\u0026gt;left); // 递归处理左子树 invertTree(root-\u0026gt;right); // 递归处理右子树 return root; // 返回翻转后的树根节点 } }; // BFS class Solution { public: TreeNode* invertTree(TreeNode* root) { if (!root) return nullptr; std::queue\u0026lt;TreeNode*\u0026gt; q; q.push(root); // 将根节点入队 while (!q.empty()) { TreeNode* node = q.front(); q.pop(); // 交换左右子树 std::swap(node-\u0026gt;left, node-\u0026gt;right); // 将子节点入队 if (node-\u0026gt;left) q.push(node-\u0026gt;left); if (node-\u0026gt;right) q.push(node-\u0026gt;right); } return root; } }; ","date":"2024-12-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/invert-binary-tree/","title":"Invert Binary Tree"},{"content":"101. 对称二叉树 分析 主函数 isSymmetric\n首先检查根节点是否为空。如果根节点为空，则树是对称的，返回 true 调用辅助函数 dfs，传入根节点的左右子树进行判断 辅助函数 dfs\n判断当前两个节点： 如果两者都为空，返回 true 如果只有一个为空或节点值不同，返回 false 递归调用 dfs： 比较左子树的左孩子和右子树的右孩子是否对称 比较左子树的右孩子和右子树的左孩子是否对称 时间复杂度 时间复杂度 O(n)，n 为节点数，每个节点访问一次\n空间复杂度 空间复杂度 O(h)，h 为树的高度，递归栈的深度\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public: bool isSymmetric(TreeNode* root) { // 如果根节点为空，直接返回 true if (!root) return true; // 调用辅助函数判断左右子树是否对称 return dfs(root-\u0026gt;left, root-\u0026gt;right); } bool dfs(TreeNode* left, TreeNode* right) { // 如果两个子树都为空，说明对称 if (!left \u0026amp;\u0026amp; !right) return true; // 如果只有一个子树为空，或两子树值不等，不对称 if (!left || !right || left-\u0026gt;val != right-\u0026gt;val) return false; // 递归检查：左子树的左孩子和右子树的右孩子是否对称 // 以及左子树的右孩子和右子树的左孩子是否对称 return dfs(left-\u0026gt;left, right-\u0026gt;right) \u0026amp;\u0026amp; dfs(left-\u0026gt;right, right-\u0026gt;left); } }; ","date":"2024-12-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/is-symmetric/","title":"Is Symmetric"},{"content":"102. 二叉树的层序遍历 分析 初始化结果数组 res 和队列 q 若 root 为空，直接返回空的 res 将根节点加入队列，进入循环： 获取当前层节点数量 len 遍历当前层的所有节点： 弹出队头节点，加入当前层结果数组 level 将该节点的左右子节点（若存在）加入队列 将当前层的结果 level 加入到 res 中 时间复杂度 时间复杂度 O(n)，其中 n 是二叉树的节点总数。每个节点都会被访问一次\n空间复杂度 空间复杂度 O(m)，其中 m 是二叉树的最大宽度。队列在最宽的层存储所有节点\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; levelOrder(TreeNode* root) { // 存储最终结果 std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; if (!root) return res; // 特殊情况：空树 // 初始化队列 std::queue\u0026lt;TreeNode*\u0026gt; q; q.push(root); // 遍历每一层 while (!q.empty()) { int len = q.size(); // 当前层节点数 std::vector\u0026lt;int\u0026gt; level; // 存储当前层的节点值 // 遍历当前层的节点 while (len--) { TreeNode* node = q.front(); q.pop(); level.push_back(node-\u0026gt;val); // 将节点值加入当前层结果 if (node-\u0026gt;left) q.push(node-\u0026gt;left); // 加入左子节点 if (node-\u0026gt;right) q.push(node-\u0026gt;right); // 加入右子节点 } res.push_back(level); // 将当前层结果加入到最终结果 } return res; // 返回层序遍历结果 } }; ","date":"2024-12-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/level-order/","title":"Level Order"},{"content":"94. 二叉树的中序遍历 分析 中序遍历的顺序是：左子树 -\u0026gt; 根节点 -\u0026gt; 右子树\n初始化数据结构\n创建一个结果数组 res，用于存储中序遍历的结果 创建一个栈 stk，用于辅助遍历 模拟递归过程\n向左下方向遍历：当前节点非空时，将当前节点压入栈，并将其移动到左子节点\n回溯节点并访问右子树：如果当前节点为空（即到达左子树的最深处），从栈中弹出一个节点，访问该节点的值并存入结果数组，然后将当前节点移动到其右子节点\n重复以上过程，直到栈为空且当前节点为空\n时间复杂度 每个节点被访问两次（一次压入栈，一次弹出栈），因此时间复杂度为 O(n)，其中 n 是节点总数\n空间复杂度 栈的深度取决于树的高度。在最坏情况下（如链状树），栈的空间复杂度为 O(n)；在平衡树中，栈的空间复杂度为 O(logn)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 // 非递归 class Solution { public: vector\u0026lt;int\u0026gt; inorderTraversal(TreeNode* root) { std::vector\u0026lt;int\u0026gt; res; // 存储中序遍历结果 std::stack\u0026lt;TreeNode*\u0026gt; stk; // 辅助栈 while (root || !stk.empty()) // 当节点非空或栈非空时，继续遍历 { // 一直向左走，将路径上的节点压入栈 while (root) { stk.push(root); // 压入栈 root = root-\u0026gt;left; // 移动到左子节点 } // 回溯：弹出栈顶节点 root = stk.top(); stk.pop(); res.push_back(root-\u0026gt;val); // 访问节点值 root = root-\u0026gt;right; // 转向右子树 } return res; // 返回结果 } }; // 递归 class Solution { public: std::vector\u0026lt;int\u0026gt; res; void dfs(TreeNode* root) { if (!root) return; dfs(root-\u0026gt;left); res.push_back(root-\u0026gt;val); dfs(root-\u0026gt;right); } vector\u0026lt;int\u0026gt; inorderTraversal(TreeNode* root) { dfs(root); return res; } }; ","date":"2024-12-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/binary-tree-inorder-traversal/","title":"Binary Tree inorder Traversal"},{"content":"104. 二叉树的最大深度 分析 初始化\n创建一个变量 res，初始化为 0，用于记录树的最大深度 创建一个队列 q，用于辅助层序遍历 处理根节点\n如果根节点 root 不为空，将其加入队列 层序遍历\n每次从队列中取出当前层的所有节点（根据队列长度决定） 遍历当前层的节点，依次将每个节点的左右子节点加入队列 每处理完一层后，将深度变量 res 增加 1 返回结果\n当队列为空时，树的所有层已遍历完，返回变量 res，即为最大深度 时间复杂度 每个节点被访问一次，因此时间复杂度为 O(n)，其中 n 是节点总数\n空间复杂度 队列的最大空间取决于某一层的最大节点数。在完全二叉树的情况下，最大节点数可能是 n/2，因此空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 // BFS class Solution { public: int maxDepth(TreeNode* root) { int res = 0; // 初始化深度为 0 std::queue\u0026lt;TreeNode*\u0026gt; q; // 辅助队列，用于层序遍历 if (root) q.push(root); // 如果根节点非空，加入队列 while (!q.empty()) // 队列非空时，继续遍历 { int len = q.size(); // 当前层的节点数量 // 遍历当前层的所有节点 while (len--) { TreeNode* node = q.front(); // 获取队首节点 q.pop(); // 弹出队首节点 // 将当前节点的左右子节点加入队列 if (node-\u0026gt;left) q.push(node-\u0026gt;left); if (node-\u0026gt;right) q.push(node-\u0026gt;right); } ++res; // 每完成一层，深度加 1 } return res; // 返回最大深度 } }; // DFS class Solution { public: int maxDepth(TreeNode* root) { if (!root) return 0; return std::max(maxDepth(root-\u0026gt;left), maxDepth(root-\u0026gt;right)) + 1; } }; ","date":"2024-12-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maximum-depth-of-binary-tree/","title":"Maximum Depth of Binary Tree"},{"content":"148. 排序链表 分析 计算链表长度 遍历链表，统计节点数量 n，以确定归并的步长范围 自底向上归并排序 按步长 i 从小到大逐步归并，初始步长为 1，逐渐乘以 2，直到覆盖整个链表 每次归并时，将链表分成两部分，每部分包含 i 个节点，然后对这两部分进行归并 最后将归并的结果重新连接到链表中 归并两个子链表 使用两个指针分别指向两个子链表的头部，逐一比较节点的值，将较小值的节点加入到排序后的链表中，直到处理完两部分中的所有节点 更新链表 经过多轮归并后，最终的链表即为排序后的链表 时间复杂度 每轮归并的时间复杂度为 O(n)，需要进行 log(n) 轮归并，因此总复杂度为 O(nlogn)\n空间复杂度 采用自底向上归并，不需要额外空间，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 class Solution { public: ListNode* sortList(ListNode* head) { // 第 1 步：计算链表长度 int n = 0; for (ListNode* p = head; p; p = p-\u0026gt;next) ++n; // 第 2 步：归并排序 for (int i = 1; i \u0026lt; n; i *= 2) { ListNode* dummy = new ListNode(-1); // 虚拟头节点 ListNode* cur = dummy; for (int j = 1; j \u0026lt;= n; j += i * 2) { // 划分两段链表 ListNode *p = head, *q = p; for (int k = 0; k \u0026lt; i \u0026amp;\u0026amp; q; ++k) q = q-\u0026gt;next; // 第二段起点 ListNode* o = q; for (int k = 0; k \u0026lt; i \u0026amp;\u0026amp; o; ++k) o = o-\u0026gt;next; // 下一部分的起点 // 归并两段链表 int l = 0, r = 0; while (l \u0026lt; i \u0026amp;\u0026amp; r \u0026lt; i \u0026amp;\u0026amp; p \u0026amp;\u0026amp; q) { if (p-\u0026gt;val \u0026lt;= q-\u0026gt;val) { cur-\u0026gt;next = p; p = p-\u0026gt;next; ++l; } else { cur-\u0026gt;next = q; q = q-\u0026gt;next; ++r; } cur = cur-\u0026gt;next; } // 剩余部分处理 while (l \u0026lt; i \u0026amp;\u0026amp; p) { cur = cur-\u0026gt;next = p; p = p-\u0026gt;next; ++l; } while (r \u0026lt; i \u0026amp;\u0026amp; q) { cur = cur-\u0026gt;next = q; q = q-\u0026gt;next; ++r; } // 更新链表头 head = o; } cur-\u0026gt;next = nullptr; head = dummy-\u0026gt;next; // 更新链表头为新的排序结果 } return head; } }; ","date":"2024-12-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/sort-list/","title":"Sort List"},{"content":"2. 两数相加 分析 初始化 使用一个虚拟头节点 dummy，方便操作结果链表 用指针 cur 指向结果链表的当前尾部 定义一个变量 sum 用于存储当前位的加和（包括进位） 逐位相加 遍历链表 l1 和 l2，对每一位的值进行相加，同时加上进位 sum 取个位数字作为当前位的值，新建节点添加到结果链表中 更新 sum 为十位上的进位。 处理进位 当 l1 和 l2 遍历完后，如果还有进位 sum \u0026gt; 0，需要额外创建一个节点存储进位 时间复杂度 时间复杂度 O(max(n, m))，n 和 m 分别为链表 l1 和 l2 的长度，需要遍历两链表的所有节点\n空间复杂度 空间复杂度为 O(max(n, m))\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public: ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { int sum = 0; // 用于存储每个位的和以及进位 ListNode* dummy = new ListNode(-1); // 虚拟头节点 ListNode* cur = dummy; // 指向结果链表的当前尾部 // 遍历链表 l1 和 l2 while (l1 || l2 || sum) { // 如果 l1 或 l2 不为空，加上它们当前位的值 if (l1) { sum += l1-\u0026gt;val; l1 = l1-\u0026gt;next; // 移动到下一位 } if (l2) { sum += l2-\u0026gt;val; l2 = l2-\u0026gt;next; // 移动到下一位 } // 创建新节点存储当前位的值，并连接到结果链表 cur = cur-\u0026gt;next = new ListNode(sum % 10); // 更新进位 sum /= 10; } // 返回结果链表 return dummy-\u0026gt;next; } }; ","date":"2024-11-30T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/add-two-numbers/","title":"Add Two Numbers"},{"content":"138. 随机链表的复制 分析 在原链表中插入新节点 遍历原链表，对于每个节点 p，创建一个新节点 q 将新节点插入到原节点 p 和 p-\u0026gt;next 之间，形成交错链表结构：原节点 -\u0026gt; 新节点 -\u0026gt; 原节点 复制 random 指针 再次遍历交错链表，对于每个原节点 p，将 p-\u0026gt;next-\u0026gt;random 设置为 p-\u0026gt;random-\u0026gt;next，即新节点的 random 指向对应的新节点 拆分链表 遍历交错链表，将新节点从中分离出来，形成深拷贝链表，同时还原原链表的结构 时间复杂 遍历链表三次，分别处理插入节点、复制 random 指针、拆分链表，时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public: Node* copyRandomList(Node* head) { // 第 1 步：在原链表中插入新节点 for (Node* p = head; p; p = p-\u0026gt;next-\u0026gt;next) { Node* q = new Node(p-\u0026gt;val); // 创建新节点 q-\u0026gt;next = p-\u0026gt;next; // 插入新节点到原节点之后 p-\u0026gt;next = q; } // 第 2 步：复制 random 指针 for (Node* p = head; p; p = p-\u0026gt;next-\u0026gt;next) { if (p-\u0026gt;random) p-\u0026gt;next-\u0026gt;random = p-\u0026gt;random-\u0026gt;next; // 设置新节点的 random } // 第 3 步：拆分链表 Node* dummy = new Node(-1); // 虚拟头节点 Node* cur = dummy; for (Node* p = head; p; p = p-\u0026gt;next) { Node* q = p-\u0026gt;next; // 取出新节点 cur = cur-\u0026gt;next = q; // 将新节点连接到深拷贝链表 p-\u0026gt;next = q-\u0026gt;next; // 恢复原链表结构 } return dummy-\u0026gt;next; } }; ","date":"2024-11-30T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/copy-list-with-random-pointer/","title":"Copy List with Random Pointer"},{"content":"141. 环形链表 分析 初始化指针\n如果链表为空或只有一个节点，则无法形成环，直接返回 false 设置两个指针 slow 和 fast，初始时都指向链表的头节点 head 移动指针\n慢指针 slow 每次移动一步 快指针 fast 每次移动两步 每次移动后，检查 slow 和 fast 是否相等。如果相等，说明链表中存在环 终止条件\n如果 fast 为 nullptr，说明链表没有环，直接返回 false 时间复杂度 慢指针最多遍历链表一次，快指针最多遍历链表两次，因此时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public: bool hasCycle(ListNode *head) { // 链表为空或只有一个节点，无法形成环 if (!head || !head-\u0026gt;next) return false; ListNode *slow = head, *fast = head; while (fast) // 快指针未到达链表末尾 { slow = slow-\u0026gt;next; // 慢指针走一步 fast = fast-\u0026gt;next; // 快指针走一步 if (fast) fast = fast-\u0026gt;next; // 快指针再走一步 if (slow == fast) // 快慢指针相遇，存在环 return true; } return false; // 快指针到达链表末尾，无环 } }; ","date":"2024-11-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/linkedlistcycle/","title":"linkedListCycle"},{"content":"142. 环形链表II 分析 判断链表是否有环\n设置两个指针 slow 和 fast，初始都指向链表头节点 head 快指针每次移动两步，慢指针每次移动一步 如果快指针追上慢指针，说明链表中有环；否则，如果快指针或其下一节点为空，则链表无环 找到环的起始节点\n当快慢指针相遇时，将慢指针重置为链表头节点 head 快指针保持在相遇位置 两个指针每次都向前移动一步，当两者再次相遇时，相遇点即为环的起始节点 数学推导 假设：\n链表头到环入口节点的距离为 a 环入口到相遇点的距离为 b 相遇点到环入口的距离为 c 快指针比慢指针速度快一倍，因此：2(a + b) = a + b + n(b + c)（ n 为快指针在环中绕的圈数），化简得：a = c + (n - 1)(b + c) 这表明：\n从链表头节点到环入口的距离 a 等于从相遇点沿环走回入口的距离 c 因此，当两个指针一个从链表头开始，一个从相遇点开始，每次移动一步，最终会在环的起始节点相遇 时间复杂度 快慢指针遍历链表一次即可确定是否有环，以及找到环的起始节点，时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public: ListNode *detectCycle(ListNode *head) { // 链表为空或只有一个节点，不可能有环 if (!head || !head-\u0026gt;next) return nullptr; ListNode *slow = head, *fast = head; // 判断链表是否有环 while (fast) { slow = slow-\u0026gt;next; // 慢指针走一步 fast = fast-\u0026gt;next; // 快指针走一步 if (fast) fast = fast-\u0026gt;next; // 快指针再走一步 if (slow == fast) // 快慢指针相遇 { // 找到环的起始节点 slow = head; // 慢指针回到链表头 while (slow != fast) // 两指针相遇即为环入口 { slow = slow-\u0026gt;next; fast = fast-\u0026gt;next; } return fast; } } return nullptr; // 无环 } }; ","date":"2024-11-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/linkedlistcycleii/","title":"linkedListCycleII"},{"content":"234. 回文链表 分析 计算链表长度\n遍历链表一次，统计节点总数 len。用于确定需要存入栈的节点数量 存储前半部分元素到栈中\n再次遍历链表，将前半部分的节点值压入栈中。如果链表长度为奇数，则跳过中间节点（因为中间节点不影响回文判断） 比较后半部分的值\n从链表中间位置开始继续遍历链表，每遇到一个节点，就弹出栈顶的元素，与当前节点值比较。如果有任意不相等的情况，说明链表不是回文链表 判断结果\n遍历完后，若所有值都匹配，则链表是回文的，返回 true 时间复杂度 时间复杂度 O(n)\n空间复杂度 空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public: bool isPalindrome(ListNode* head) { // 求链表长度 int len = 0; for (ListNode* p = head; p; p = p-\u0026gt;next) ++len; // 用栈存储前半部分节点值 ListNode* p = head; std::stack\u0026lt;int\u0026gt; stk; for (int i = 0; i \u0026lt; len / 2; ++i) { stk.push(p-\u0026gt;val); p = p-\u0026gt;next; } // 如果链表长度为奇数，跳过中间节点 if (len % 2 == 1) p = p-\u0026gt;next; // 比较后半部分节点值与栈中元素 while (p) { if (stk.top() != p-\u0026gt;val) return false; stk.pop(); p = p-\u0026gt;next; } return true; } }; ","date":"2024-11-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/palindromelinkedlist/","title":"palindromeLinkedList"},{"content":"206. 反转链表 分析 初始化指针\n定义一个指针 prev，初始值为 nullptr，用于指向反转后链表的头节点 定义一个指针 cur，初始指向链表的头节点 head，用于遍历链表 遍历链表\n在遍历的每一步，保存当前节点的下一节点 next 调整当前节点 cur 的 next 指针，使其指向 prev，实现反转 将 prev 更新为当前节点 cur，将 cur 更新为 next 终止条件\n当 cur 遍历到链表末尾（即为 nullptr ）时，链表已经完全反转，此时 prev 指针指向反转后的链表头节点 时间复杂度 时间复杂度 O(n)，其中 n 为链表的长度。每个节点访问一次\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public: ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; // 初始化反转链表的头节点为 nullptr ListNode* cur = head; // 从链表头节点开始遍历 while (cur) // 遍历整个链表 { ListNode* next = cur-\u0026gt;next; // 保存当前节点的下一节点 cur-\u0026gt;next = prev; // 将当前节点的 next 指向反转链表的头 prev = cur; // 更新 prev 为当前节点 cur = next; // 继续遍历下一个节点 } return prev; // 返回反转后的链表头节点 } }; // 递归 class Solution { public: ListNode* reverseList(ListNode* head) { // 递归终止条件：空节点或只有一个节点时直接返回 if (!head || !head-\u0026gt;next) return head; // 递归反转后面的链表，tail 是反转后的新头节点 ListNode* tail = reverseList(head-\u0026gt;next); // 将当前节点放到反转链表的尾部 head-\u0026gt;next-\u0026gt;next = head; head-\u0026gt;next = nullptr; return tail; // 返回新的头节点 } }; ","date":"2024-11-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reverse-linked-list/","title":"Reverse Linked List"},{"content":"160. 相交链表 分析 初始化指针\n定义两个指针 p 和 q，分别指向链表 headA 和 headB 的头节点 遍历链表\n让两个指针同时遍历各自的链表，当某个指针到达链表尾部时，切换到另一个链表的头节点继续遍历 终止条件\n如果链表相交，p 和 q 会在相交节点相遇，此时返回相交节点 如果链表不相交，两个指针会同时变为 null，退出循环并返回 null 关键点\n当一个指针遍历完自己的链表后，切换到另一个链表，从而保证两个指针在第二次遍历时长度相同 这样，当两链表有相交节点时，两个指针在第二次遍历中必定会在相交节点处相遇 时间复杂度 指针最多遍历两个链表一次，两个链表的长度分别为 m 和 n，总时间复杂度 O(m + n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { ListNode *p = headA, *q = headB; // 初始化两个指针 p 和 q while (p != q) // 当 p 和 q 不相等时继续循环 { p = p ? p-\u0026gt;next : headB; // 如果 p 非空，移动到下一个节点，否则切换到 headB q = q ? q-\u0026gt;next : headA; // 如果 q 非空，移动到下一个节点，否则切换到 headA } return p; // 返回相交节点或 null } }; ","date":"2024-11-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/intersectionoftwolinkedlists/","title":"intersectionOfTwoLinkedLists"},{"content":"48. 旋转图像 分析 转置矩阵： 遍历上三角区域 (i, j) ，交换每对对称元素 matrix[i][j] 和 matrix[j][i] 水平翻转矩阵： 遍历每一行，交换每行左右两端对称的元素 时间复杂度 转置矩阵需要 O(n^2) 水平翻转需要 O(n^2) 总时间复杂度 O(n^2)\n空间复杂度 在原地修改矩阵，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public: void rotate(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; matrix) { int n = matrix.size(); // 1. 转置矩阵 for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; i; ++j) std::swap(matrix[i][j], matrix[j][i]); // 2. 水平翻转矩阵 for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; n / 2; ++j) std::swap(matrix[i][j], matrix[i][n - 1 - j]); } }; ","date":"2024-11-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/rotateimage/","title":"rotateImage"},{"content":"74. 搜索二维矩阵 分析 由于矩阵按行列有序，可以将整个二维矩阵视为一个长度为 n * m 的一维数组，并在这个一维数组上进行二分查找\n矩阵映射为一维数组：\n矩阵的第 i 行和第 j 列的元素 matrix[i][j]，在一维数组中对应的索引为 i * m + j 反之，一维数组的索引 k 对应矩阵中的元素为 matrix[k / m][k % m] 二分查找：\n初始时，定义查找区间的左右端点为 l = 0 和 r = m * n - 1 每次取中点 mid，将其映射到二维矩阵元素 matrix[mid / m][mid % m] ，与 target 比较： 若该值大于等于 target，收缩右边界 r = mid 若该值小于 target，收缩左边界 l = mid + 1 循环结束时，检查索引 r 对应的矩阵元素是否等于 target 时间复杂度 每次二分查找都会将查找范围缩小为原来的一半，总体复杂度为 O(log(m * n))\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: bool searchMatrix(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; matrix, int target) { int n = matrix.size(), m = matrix[0].size(); // 矩阵行数和列数 int l = 0, r = n * m - 1; // 二分区间 [l, r] while (l \u0026lt; r) { int mid = (l + r) \u0026gt;\u0026gt; 1; // 中点 if (target \u0026lt;= matrix[mid / m][mid % m]) r = mid; // 收缩右边界 else l = mid + 1; // 收缩左边界 } // 判断最终位置是否是目标值 return matrix[r / m][r % m] == target; } }; ","date":"2024-11-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/search2dmatrix/","title":"search2dMatrix"},{"content":"240. 搜索二维矩阵II 分析 利用矩阵的排序特性，可以从矩阵的右上角（或左下角）开始搜索：\n选择右上角 (0, m - 1) 为起点： 如果当前位置的值等于目标值，返回 true 如果当前位置的值小于目标值，则向下移动一行（增加行索引） 如果当前位置的值大于目标值，则向左移动一列（减少列索引） 退出条件： 如果行索引越界 x \u0026gt; n 或列索引越界 y \u0026lt; 0，说明矩阵中没有目标值，返回 false 时间复杂度 每次移动都会排除当前行或列的一部分，最多移动 m + n 次，因此时间复杂度为 O(m + n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: bool searchMatrix(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; matrix, int target) { int n = matrix.size(), m = matrix[0].size(); if (n == 0 || m == 0) return false; int x = 0, y = m - 1; // 从右上角开始 while (x \u0026lt; n \u0026amp;\u0026amp; y \u0026gt;= 0) { if (matrix[x][y] == target) return true; // 找到目标值 else if (matrix[x][y] \u0026lt; target) ++ x; // 目标值更大，向下移动 else -- y; // 目标值更小，向左移动 } return false; // 未找到目标值 } }; ","date":"2024-11-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/search2dmatrixii/","title":"search2dMatrixII"},{"content":"54. 螺旋矩阵 分析 初始化变量： 使用 count 记录已访问的元素个数，初始为 0 将已访问的元素标记为 INF，以避免重复访问 初始化起始位置为 (x, y) = (0, -1) 按顺时针方向遍历： 按照 右、下、左、上 的顺序依次遍历矩阵 检查下一步是否越界或访问到标记值，如果满足条件则改变方向 收集结果： 在每次遍历过程中，将当前值加入结果数组 res 返回结果： 当遍历的元素数量 count 等于矩阵元素总数时，返回结果 时间复杂度 需要遍历矩阵中的所有元素，总时间复杂度 O(n * m)\n空间复杂度 在矩阵内原地标记访问过的元素，无需额外空间，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public: vector\u0026lt;int\u0026gt; spiralOrder(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; matrix) { int n = matrix.size(); // 矩阵行数 int m = matrix[0].size(); // 矩阵列数 int count = 0; // 记录访问过的元素数量 int INF = 0x3f3f3f3f; // 标记值，用于标记已访问的元素 std::vector\u0026lt;int\u0026gt; res; // 存放结果的数组 int x = 0, y = -1; // 起始位置在矩阵外部 while (count \u0026lt; n * m) { // 向右遍历 while (y + 1 \u0026lt; m \u0026amp;\u0026amp; matrix[x][y + 1] != INF) { res.push_back(matrix[x][y + 1]); // 记录元素 matrix[x][y + 1] = INF; // 标记为已访问 ++ y; // 移动列 ++ count; // 更新已访问数量 } // 向下遍历 while (x + 1 \u0026lt; n \u0026amp;\u0026amp; matrix[x + 1][y] != INF) { res.push_back(matrix[x + 1][y]); matrix[x + 1][y] = INF; ++ x; ++ count; } // 向左遍历 while (y - 1 \u0026gt;= 0 \u0026amp;\u0026amp; matrix[x][y - 1] != INF) { res.push_back(matrix[x][y - 1]); matrix[x][y - 1] = INF; -- y; ++ count; } // 向上遍历 while (x - 1 \u0026gt;= 0 \u0026amp;\u0026amp; matrix[x - 1][y] != INF) { res.push_back(matrix[x - 1][y]); matrix[x - 1][y] = INF; -- x; ++ count; } } return res; } }; ","date":"2024-11-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/spiralmatrix/","title":"spiralMatrix"},{"content":"41. 缺失的第一个正数 分析 原地哈希：\n对于一个长度为 n 的数组，最小的缺失正整数只能在 1 到 n+1 之间 遍历数组，对于每个数字 nums[i]，如果满足条件 1 ≤ nums[i] ≤ n 且 nums[i] != nums[nums[i] - 1]，则将其交换到正确的位置 通过循环交换，可以在原地调整数组顺序 检查缺失：\n调整完成后，遍历数组，检查每个位置是否满足 nums[i] == i + 1 如果存在不满足的位置，则返回 i + 1 如果所有位置都满足，说明数组包含所有 1 到 n 的正整数，缺失的正整数为 n + 1 时间复杂度 每个数字最多只会被交换一次，因此总操作次数为 O(n)\n空间复杂度 原地调整数组，无额外空间开销，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: int firstMissingPositive(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int n = nums.size(); // 调整数组顺序 for (int i = 0; i \u0026lt; n; ++i) while (nums[i] \u0026gt;= 1 \u0026amp;\u0026amp; nums[i] \u0026lt;= n \u0026amp;\u0026amp; nums[i] != nums[nums[i] - 1]) std::swap(nums[i], nums[nums[i] - 1]); // 查找第一个缺失的正整数 for (int i = 0; i \u0026lt; n; ++i) if (nums[i] != i + 1) return i + 1; // 如果所有正整数都在正确位置，返回 n + 1 return n + 1; } }; ","date":"2024-11-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/firstmisspositive/","title":"firstMissPositive"},{"content":"53. 最大子数组和 算法 定义状态：\n用变量 last 表示以当前元素结尾的最大子数组和 用变量 res 表示全局最大子数组和 状态转移方程：\n对于每个元素 num，当前的 last 值可以由以下两种情况决定： 单独包含当前元素：如果此前的 last 为负数，则舍弃 包含当前元素并延续之前的子数组：如果此前的 last 为正数，则将当前元素加入子数组 状态转移方程：last = max(num, last + num) 更新全局最大值：\n在遍历每个元素后，更新全局最大值 res 为：res = max(res, last) 复杂度分析 时间复杂度 单次遍历：仅需遍历数组一次，时间复杂度为 O(n) 空间复杂度 只使用了 res 和 last 两个变量，空间复杂度为 O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public: int maxSubArray(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = INT_MIN; // 全局最大子数组和 int last = 0; // 当前子数组和 for (int num : nums) { last = std::max(num, last + num); // 动态更新以当前元素结尾的最大子数组和 res = std::max(res, last); // 更新全局最大值 } return res; } }; Python 代码 1 2 3 4 5 6 7 class Solution: def maxSubArray(self, nums: List[int]) -\u0026gt; int: res, last = nums[0], 0 for x in nums: last = max(x, last + x) res = max(res, last) return res Go 代码 1 2 3 4 5 6 7 8 func maxSubArray(nums []int) int { res, last := nums[0], 0 for _, x := range nums { last = max(x, last + x) res = max(res, last) } return res } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 /** * @param {number[]} nums * @return {number} */ var maxSubArray = function (nums) { let res = nums[0], last = 0; for (let x of nums) { last = Math.max(x, last + x); res = Math.max(res, last); } return res; }; ","date":"2024-11-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maxsubarray/","title":"maxSubArray"},{"content":"238. 除自身以外数组的乘积 算法 前缀积数组 pre： pre[i] 表示 nums[0] 到 nums[i - 1] 的乘积 用于保存当前元素之前所有元素的乘积 后缀积变量 sub： 在第二次遍历时，通过一个变量动态记录从右到左的乘积（即 nums[i + 1] 到 nums[n - 1]的乘积） 每次更新 pre[i] 为 pre[i] * sub（计算结果数组），并更新 sub 复杂度分析 前缀积遍历一次，后缀积遍历一次，时间复杂度为 O(n) 使用了一个前缀积数组和一个后缀积变量，空间复杂度为 O(n) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: vector\u0026lt;int\u0026gt; productExceptSelf(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int n = nums.size(); // 初始化前缀积数组 std::vector\u0026lt;int\u0026gt; pre(n, 1); for (int i = 1; i \u0026lt; n; ++ i) pre[i] = pre[i - 1] * nums[i - 1]; // 动态计算后缀积并更新结果 int sub = 1; for (int i = n - 1; i \u0026gt;= 0; -- i) { pre[i] *= sub; sub *= nums[i]; } return pre; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution: def productExceptSelf(self, nums: List[int]) -\u0026gt; List[int]: n = len(nums) pre = [1] * n for i in range(1, n): pre[i] = pre[i - 1] * nums[i - 1] suffix = 1 for i in range(n - 1, -1, -1): pre[i] *= suffix suffix *= nums[i] return pre Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func productExceptSelf(nums []int) []int { n := len(nums) pre := make([]int, n) for i, _ := range pre { pre[i] = 1 } for i := 1; i \u0026lt; n; i += 1 { pre[i] = pre[i - 1] * nums[i - 1] } suffix := 1 for i := n - 1; i \u0026gt;= 0; i -= 1 { pre[i] *= suffix suffix *= nums[i] } return pre } ","date":"2024-11-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/product-of-array-except-self/","title":"Product of Array Except Self"},{"content":"189. 轮转数组 分析 确定实际轮转次数： 如果 k 大于数组长度 n，轮转后的结果与 k % n 次轮转相同 因此需要先对 k 取模：k %= n 三次反转法： 整体反转：将整个数组反转，这样后 k 个元素会移动到前面 前部分反转：将前 k 个元素反转，恢复它们的顺序 后部分反转：将剩余的 n-k 个元素反转，恢复它们的顺序 时间复杂度 反转操作：每次反转需要遍历部分数组，整体复杂度为 O(n)\n空间复杂度 原地操作：仅使用常数级别的额外空间，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: void rotate(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { int n = nums.size(); k %= n; // 计算有效的轮转次数 // 反转整个数组 for (int i = 0; i \u0026lt; n / 2; ++i) std::swap(nums[i], nums[n - 1 - i]); // 反转前 k 个元素 for (int i = 0; i \u0026lt; k / 2; ++i) std::swap(nums[i], nums[k - 1 - i]); // 反转后 n-k 个元素 for (int i = k; i \u0026lt; (n + k) / 2; ++i) std::swap(nums[i], nums[n - 1 - i + k]); } }; ","date":"2024-11-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/rotatearray/","title":"rotateArray"},{"content":"73. 矩阵置零 分析 标记行和列：\n用第一行和第一列作为标记位置 如果矩阵中的元素 matrix[i][j] == 0，则将 matrix[i][0] 和 matrix[0][j] 置为 0 ，分别标记第 i 行和第 j 列需要被置 0 特殊处理首行和首列：\n因为第一行和第一列被用作标记，置 0 的操作可能覆盖原始信息 使用两个额外变量 r 和 c 分别标记首行和首列是否需要置 0 置零操作：\n根据第一列的标记，从第二行开始，将需要置 0 的行的所有元素置为 0 根据第一行的标记，从第二列开始，将需要置 0 的列的所有元素置为 0 处理首行和首列：\n如果 r == 0，将第一行全部置 0 如果 c == 0，将第一列全部置 0 时间复杂度 时间复杂度：O(m * n)\n空间复杂度 使用矩阵本身作为标记，无额外空间开销，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { public: void setZeroes(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; matrix) { int n = matrix.size(), m = matrix[0].size(); int r = 1, c = 1; // 标记需要置零的行和列 for (int i = 0; i \u0026lt; n; ++i) for (int j = 0; j \u0026lt; m; ++j) if (matrix[i][j] == 0) { if (i == 0) r = 0; if (j == 0) c = 0; matrix[i][0] = 0; matrix[0][j] = 0; } // 根据标记置零 for (int i = 1; i \u0026lt; n; ++i) if (matrix[i][0] == 0) for (int j = 1; j \u0026lt; m; ++j) matrix[i][j] = 0; for (int j = 1; j \u0026lt; m; ++j) if (matrix[0][j] == 0) for (int i = 1; i \u0026lt; n; ++i) matrix[i][j] = 0; // 处理首行和首列 if (r == 0) for (int j = 0; j \u0026lt; m; ++j) matrix[0][j] = 0; if (c == 0) for (int i = 0; i \u0026lt; n; ++i) matrix[i][0] = 0; } }; ","date":"2024-11-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/setmatrixzeroes/","title":"setMatrixZeroes"},{"content":"438. 找到字符串中所有字母异位词 算法 初始化目标频率表：\n使用哈希表 hash 存储字符串 p 中每个字符的频率 滑动窗口遍历字符串 s：\n窗口初始范围为 [j, i]，窗口长度不超过 p.size() 遍历字符串 s，对窗口内的字符更新哈希表： 窗口右扩（加入字符）： 将当前字符 s[i] 加入窗口，并在 hash 中减少其频率 如果该字符的频率变为 0，说明该字符的频率匹配，增加匹配计数 count 窗口左缩（移除字符）： 如果窗口长度超过 p.size()，将左端字符 s[j] 从窗口移除： 如果 s[j] 在 hash 中频率变为非零，匹配计数 count 减少 更新左指针 j 判断异位词：\n如果窗口内所有字符频率均匹配（count == total），将当前窗口左端索引 j 加入结果集 res 复杂度分析 时间复杂度 初始化哈希表：O(p)，其中 p 是字符串 p 的长度 滑动窗口遍历：O(s)，其中 s 是字符串 s 的长度 总时间复杂度：O(s + p) 空间复杂度 使用了一个哈希表存储字符频率，空间复杂度为 O(k)，其中 k 是字符集大小 C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public: vector\u0026lt;int\u0026gt; findAnagrams(string s, string p) { std::unordered_map\u0026lt;char, int\u0026gt; hash; // 存储目标字符频率 for (char c : p) ++hash[c]; std::vector\u0026lt;int\u0026gt; res; int total = hash.size(), count = 0; for (int i = 0, j = 0; i \u0026lt; s.size(); ++ i) { // 右扩：加入字符 s[i] -- hash[s[i]]; if (hash[s[i]] == 0) ++ count; // 左缩：移除字符 s[j] if (i - j + 1 \u0026gt; p.size()) { if (hash[s[j]] == 0) -- count; ++ hash[s[j]]; ++ j; } // 检查是否找到异位词 if (count == total) res.push_back(j); } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution: def findAnagrams(self, s: str, p: str) -\u0026gt; List[int]: freq = {} for ch in p: freq[ch] = freq.get(ch, 0) + 1 total, count = len(freq), 0 res = [] j = 0 for i in range(len(s)): freq[s[i]] = freq.get(s[i], 0) - 1 if freq[s[i]] == 0: count += 1 if i - j + 1 \u0026gt; len(p): if freq[s[j]] == 0: count -= 1 freq[s[j]] = freq.get(s[j], 0) + 1 j += 1 if total == count: res.append(j) return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func findAnagrams(s string, p string) []int { freq := make(map[byte]int) for i := 0; i \u0026lt; len(p); i ++ { freq[p[i]] ++ } total, count := len(freq), 0 res := []int{} for i, j := 0, 0; i \u0026lt; len(s); i ++ { freq[s[i]] -- if freq[s[i]] == 0 { count ++ } if i - j + 1 \u0026gt; len(p) { if freq[s[j]] == 0 { count -- } freq[s[j]] ++ j ++ } if count == total { res = append(res, j) } } return res } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /** * @param {string} s * @param {string} p * @return {number[]} */ var findAnagrams = function (s, p) { let freq = new Map(); for (let i = 0; i \u0026lt; p.length; i++) { freq.set(p[i], (freq.get(p[i]) || 0) + 1); } let total = freq.size, count = 0; let res = []; for (let i = 0, j = 0; i \u0026lt; s.length; i++) { freq.set(s[i], (freq.get(s[i]) || 0) - 1); if (freq.get(s[i]) == 0) { count++; } if (i - j + 1 \u0026gt; p.length) { if (freq.get(s[j]) == 0) { count--; } freq.set(s[j], (freq.get(s[j]) || 0) + 1); j++; } if (total == count) { res.push(j); } } return res; }; ","date":"2024-11-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/findanagrams/","title":"findAnagrams"},{"content":"3. 无重复字符的最长子串 算法 维护一个滑动窗口： 滑动窗口用两个指针表示，j 为窗口左端，i 为窗口右端 窗口内的子串为 [j, i]，保证该窗口内没有重复字符 使用哈希表记录字符出现的次数： 哈希表 hash 的 key 为字符，value 为该字符在窗口中的出现次数 移动窗口右端： 遍历字符串，右端指针 i 每次右移一格，将字符加入窗口并更新哈希表 处理重复字符： 如果窗口中出现重复字符（即当前字符的出现次数大于 1），移动左端指针 j，并在哈希表中移除字符，直到窗口内没有重复字符。 更新结果： 每次移动窗口时，计算当前窗口的长度 (i - j + 1)，并更新结果 res 复杂度分析 时间复杂度 每个字符至多被访问两次（右指针扩展时访问一次，左指针收缩时访问一次），时间复杂度为 O(n)\n空间复杂度 使用哈希表存储字符出现次数，空间复杂度为 O(k)，其中 k 为字符集大小\nC++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: int lengthOfLongestSubstring(string s) { std::unordered_map\u0026lt;char, int\u0026gt; hash; // 记录字符出现次数 int res = 0; // 最长子串长度 for (int i = 0, j = 0; i \u0026lt; s.size(); ++ i) { ++hash[s[i]]; // 将字符加入窗口 // 如果出现重复字符，移动左指针 j while (hash[s[i]] \u0026gt; 1) { -- hash[s[j]]; ++ j; } // 更新最长子串长度 res = std::max(res, i - j + 1); } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution: def lengthOfLongestSubstring(self, s: str) -\u0026gt; int: res = 0 freq = {} j = 0 for i, ch in enumerate(s): if ch not in freq: freq[ch] = 0 freq[ch] += 1 while freq[ch] \u0026gt; 1: freq[s[j]] -= 1 j += 1 res = max(res, i - j + 1) return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 func lengthOfLongestSubstring(s string) int { freq := make(map[byte]int) res, j := 0, 0 for i := 0; i \u0026lt; len(s); i ++ { freq[s[i]] ++ for freq[s[i]] \u0026gt; 1 { freq[s[j]] -- j ++ } res = max(res, i - j + 1) } return res } Javascript 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * @param {string} s * @return {number} */ var lengthOfLongestSubstring = function (s) { let res = 0; let freq = new Map(); for (let i = 0, j = 0; i \u0026lt; s.length; i++) { freq.set(s[i], (freq.get(s[i]) || 0) + 1); while (freq.get(s[i]) \u0026gt; 1) { freq.set(s[j], freq.get(s[j]) - 1); j++; } res = Math.max(res, i - j + 1); } return res; }; ","date":"2024-11-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-substring-without-repeating-characters/","title":"Longest Substring Without Repeating Characters"},{"content":"239. 滑动窗口最大值 算法 维护一个单调递减队列：\n使用一个双端队列 q 存储数组的索引 队列中的元素对应的值保持单调递减顺序，队首始终是当前窗口的最大值索引 窗口滑动的处理：\n窗口失效处理：检查队首索引是否超出当前窗口范围 (i - k + 1)，若超出则弹出队首 保持单调性：将新元素加入队列时，弹出队列中所有小于当前元素值的索引，以确保队列的单调递减性 记录最大值：当窗口的大小达到 k 时 (i \u0026gt;= k - 1)，窗口的最大值即为队首索引对应的值 复杂度分析 时间复杂度 遍历数组：所有元素仅被插入和弹出队列一次，时间复杂度为 O(n) 队列操作：每次插入和弹出操作的平均时间复杂度为 O(1) 总时间复杂度：O(n) 空间复杂度 使用了一个双端队列存储索引，空间复杂度为 O(k) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public: vector\u0026lt;int\u0026gt; maxSlidingWindow(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { std::deque\u0026lt;int\u0026gt; q; // 双端队列存储索引 std::vector\u0026lt;int\u0026gt; res; // 存储结果 for (int i = 0; i \u0026lt; nums.size(); ++i) { // 移除队首超出窗口范围的元素 if (q.size() \u0026amp;\u0026amp; q.front() \u0026lt; i - k + 1) q.pop_front(); // 保持队列单调递减 while (q.size() \u0026amp;\u0026amp; nums[q.back()] \u0026lt;= nums[i]) q.pop_back(); // 添加当前元素索引到队列 q.push_back(i); // 当窗口形成时，记录窗口最大值 if (i \u0026gt;= k - 1) res.push_back(nums[q.front()]); } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from collections import deque class Solution: def maxSlidingWindow(self, nums: List[int], k: int) -\u0026gt; List[int]: dq = deque() res = [] for i in range(len(nums)): while dq and nums[dq[-1]] \u0026lt;= nums[i]: dq.pop() if dq and dq[0] \u0026lt; i - k + 1: dq.popleft() dq.append(i) if i \u0026gt;= k - 1: res.append(nums[dq[0]]) return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func maxSlidingWindow(nums []int, k int) []int { dq := []int{} res := []int{} for i := 0; i \u0026lt; len(nums); i ++ { for len(dq) \u0026gt; 0 \u0026amp;\u0026amp; nums[dq[len(dq) - 1]] \u0026lt;= nums[i] { dq = dq[:len(dq) - 1] } if len(dq) \u0026gt; 0 \u0026amp;\u0026amp; dq[0] \u0026lt; i - k + 1 { dq = dq[1:] } dq = append(dq, i) if i \u0026gt;= k - 1 { res = append(res, nums[dq[0]]); } } return res; } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /** * @param {number[]} nums * @param {number} k * @return {number[]} */ var maxSlidingWindow = function (nums, k) { let dq = [], res = []; for (let i = 0; i \u0026lt; nums.length; i++) { while (dq.length \u0026amp;\u0026amp; nums[dq[dq.length - 1]] \u0026lt;= nums[i]) { dq.pop(); } if (dq.length \u0026amp;\u0026amp; dq[0] \u0026lt; i - k + 1) { dq.shift(); } dq.push(i); if (i \u0026gt;= k - 1) { res.push(nums[dq[0]]); } } return res; }; ","date":"2024-11-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maxslidingwindow/","title":"maxSlidingWindow"},{"content":"76. 最小覆盖子串 算法 记录目标字符频率： 使用哈希表 hashT 记录字符串 t 中每个字符的频率。 维护当前窗口的字符频率： 使用另一个哈希表 hashS 记录当前窗口内字符的频率。 滑动窗口： 扩展窗口：从左到右遍历字符串 s，将当前字符加入窗口，并更新 hashS 满足条件：当窗口中的字符满足 t 中的所有字符（包括频率要求），记录当前窗口长度 收缩窗口：尝试从窗口左端收缩，以找到更小的满足条件的子串 更新结果： 如果窗口当前覆盖所有所需字符且长度更短，则更新结果字符串 复杂度分析 时间复杂度 遍历字符串 s：每个字符至多被访问两次（一次扩展窗口，一次收缩窗口），时间复杂度为 O(n) 更新哈希表：更新和查询哈希表的操作时间复杂度为 O(1) 总时间复杂度：O(n) 空间复杂度 使用了两个哈希表存储字符频率，空间复杂度为 O(n) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: string minWindow(string s, string t) { std::unordered_map\u0026lt;char, int\u0026gt; hashS, hashT; for (char c : t) ++ hashT[c]; // 记录目标字符串 t 的字符频率 int cnt = 0; // 当前窗口内满足条件的字符个数 std::string res; // 记录结果子串 for (int i = 0, j = 0; i \u0026lt; s.size(); ++i) { ++ hashS[s[i]]; // 扩展窗口 if (hashS[s[i]] \u0026lt;= hashT[s[i]]) ++ cnt; // 更新满足条件的字符数 // 收缩窗口：移除多余的字符 while (hashS[s[j]] \u0026gt; hashT[s[j]]) { -- hashS[s[j]]; ++ j; } // 检查当前窗口是否满足条件 if (cnt == t.size()) if (res.empty() || res.size() \u0026gt; i - j + 1) res = s.substr(j, i - j + 1); // 更新结果 } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution: def minWindow(self, s: str, t: str) -\u0026gt; str: freqS, freqT = defaultdict(int), defaultdict(int) for c in t: freqT[c] += 1 j, cnt = 0, 0 res = \u0026#34;\u0026#34; for i in range(len(s)): freqS[s[i]] += 1 if freqS[s[i]] \u0026lt;= freqT[s[i]]: cnt += 1 while j \u0026lt; len(s) and freqS[s[j]] \u0026gt; freqT[s[j]]: freqS[s[j]] -= 1 j += 1 if cnt == len(t): if not res or len(res) \u0026gt; i - j + 1: res = s[j : i + 1] return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func minWindow(s string, t string) string { freqS, freqT := make(map[byte]int), make(map[byte]int) for i := 0; i \u0026lt; len(t); i ++ { freqT[t[i]] ++ ; } cnt := 0 res := \u0026#34;\u0026#34; for i, j := 0, 0; i \u0026lt; len(s); i ++ { freqS[s[i]] ++ if freqS[s[i]] \u0026lt;= freqT[s[i]] { cnt ++ } for j \u0026lt; len(s) \u0026amp;\u0026amp; freqS[s[j]] \u0026gt; freqT[s[j]] { freqS[s[j]] -- j ++ } if (cnt == len(t)) { if (res == \u0026#34;\u0026#34; || len(res) \u0026gt; i - j + 1) { res = s[j : i + 1] } } } return res; } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /** * @param {string} s * @param {string} t * @return {string} */ var minWindow = function (s, t) { let freqS = new Map(), freqT = new Map(); for (let c of t) { freqT.set(c, (freqT.get(c) || 0) + 1); } let cnt = 0; let res = \u0026#34;\u0026#34;; for (let i = 0, j = 0; i \u0026lt; s.length; i++) { freqS.set(s[i], (freqS.get(s[i]) || 0) + 1); if (freqS.get(s[i]) \u0026lt;= freqT.get(s[i])) { cnt++; } while (freqS.get(s[j]) \u0026gt; (freqT.get(s[j]) || 0)) { freqS.set(s[j], freqS.get(s[j]) - 1); j++; } if (cnt == t.length) { if (res.length == 0 || res.length \u0026gt; i - j + 1) { res = s.slice(j, i + 1); } } } return res; }; ","date":"2024-11-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/minimum-window/","title":"Minimum Window"},{"content":"560. 和为k的子数组 算法 定义前缀和：\n用 sum 表示从数组起点到当前位置的元素总和 任意连续子数组的和可以通过前缀和的差来计算：sum[i] - sum[j] = k =\u0026gt; sum[i] - k = sum[j] 使用哈希表存储前缀和出现的次数：\n哈希表 hash 的键表示前缀和的值，值表示该前缀和出现的次数 初始状态下，将前缀和 0 的计数置为 1（代表从起点到当前位置的子数组和可能为 k ） 遍历数组，动态更新结果：\n遍历数组中的每个元素，将当前值累加到 sum 检查 hash 中是否存在键 sum - k： 若存在，说明从某个之前的位置到当前的位置的子数组和为 k ，将其对应的次数累加到结果 res 中 更新 hash，增加当前前缀和 sum 的计数 返回结果：\n遍历结束后，结果 res 即为和为 k 的子数组个数 复杂度分析 时间复杂度 遍历数组：O(n)，其中 n 是数组的长度 哈希表操作：平均时间复杂度为 O(1) 总时间复杂度：O(n) 空间复杂度 使用了哈希表存储前缀和及其次数，空间复杂度为 O(n)，其中 n 是数组的长度 C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public: int subarraySum(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { std::unordered_map\u0026lt;int, int\u0026gt; hash; // 存储前缀和及其出现次数 int res = 0, sum = 0; hash[0] = 1; // 初始化前缀和 0 的计数为 1 for (int num : nums) { sum += num; // 更新前缀和 res += hash[sum - k]; // 检查是否存在前缀和满足条件 ++ hash[sum]; // 更新当前前缀和的计数 } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution: def subarraySum(self, nums: List[int], k: int) -\u0026gt; int: res, prefix_sum = 0, 0 freq = {} freq[0] = 1 for x in nums: prefix_sum += x if prefix_sum - k not in freq: freq[prefix_sum - k] = 0 res += freq[prefix_sum - k] if prefix_sum not in freq: freq[prefix_sum] = 0 freq[prefix_sum] += 1 return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 func subarraySum(nums []int, k int) int { res, prefix_sum := 0, 0 freq := make(map[int]int) freq[0] = 1 for _, x := range nums { prefix_sum += x res += freq[prefix_sum - k] freq[prefix_sum] += 1 } return res } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * @param {number[]} nums * @param {number} k * @return {number} */ var subarraySum = function (nums, k) { let res = 0, prefix_sum = 0; let freq = new Map(); freq.set(0, 1); for (let x of nums) { prefix_sum += x; if (freq.has(prefix_sum - k)) { res += freq.get(prefix_sum - k); } freq.set(prefix_sum, (freq.get(prefix_sum) || 0) + 1); } return res; }; ","date":"2024-11-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/subarraysum/","title":"subarraySum"},{"content":"49. 字母异位词分组 算法 核心思想： 字母异位词在字母排序后，其排序结果是相同的。例如： \u0026quot;eat\u0026quot; 和 \u0026quot;tea\u0026quot; 排序后都为 \u0026quot;aet\u0026quot; 因此，可以将排序后的字符串作为键，将所有字母异位词分组存储在一个哈希表中 步骤： 遍历字符串数组，对每个字符串进行排序，得到其标准形式 将排序后的字符串作为键，将原始字符串加入到对应的哈希表键值中 遍历哈希表，提取所有的值（即字母异位词组） 复杂度分析 时间复杂度 排序： 每个字符串排序的时间复杂度为 O(klogk) ，其中 k 是字符串的平均长度 总的排序复杂度为 O(n * klogk) ，其中 n 是字符串数组的大小 哈希表操作： 插入和查找操作的平均复杂度为 O(1) 总时间复杂度为 O(n * klogk) 空间复杂度 哈希表存储：需要存储排序后的字符串和对应的原始字符串列表，空间复杂度为 O(n * k) 额外字符串副本：排序时需要创建字符串副本，额外空间复杂度为 O(k) 总空间复杂度为 O(n * k) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: std::vector\u0026lt;std::vector\u0026lt;std::string\u0026gt;\u0026gt; groupAnagrams(std::vector\u0026lt;std::string\u0026gt;\u0026amp; strs) { std::vector\u0026lt;std::vector\u0026lt;std::string\u0026gt;\u0026gt; res; // 存储最终结果 std::unordered_map\u0026lt;std::string, std::vector\u0026lt;std::string\u0026gt;\u0026gt; hash; // 哈希表用于分组 // 遍历字符串数组 for (string str : strs) { std::string word = str; // 创建副本以便排序 std::sort(word.begin(), word.end()); // 排序字符串 hash[word].push_back(str); // 将原始字符串加入对应组 } // 遍历哈希表，将值部分（分组结果）加入结果 for (auto e : hash) res.push_back(e.second); return res; // 返回分组后的结果 } }; Python 代码 1 2 3 4 5 6 7 8 9 class Solution: def groupAnagrams(self, strs: List[str]) -\u0026gt; List[List[str]]: hash_map = {} for s in strs: key = \u0026#34;\u0026#34;.join(sorted(s)) if key not in hash_map: hash_map[key] = [] hash_map[key].append(s) return list(hash_map.values()) Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func groupAnagrams(strs []string) [][]string { res := [][]string{} hash_map := make(map[string][]string) for _, str := range strs { word := []byte(str) sort.Slice(word, func(i, j int) bool { return word[i] \u0026lt; word[j] }) key := string(word) hash_map[key] = append(hash_map[key], str) } for _, group := range hash_map { res = append(res, group) } return res } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * @param {string[]} strs * @return {string[][]} */ var groupAnagrams = function (strs) { let hash = new Map(); for (let str of strs) { let key = str.split(\u0026#34;\u0026#34;).sort().join(\u0026#34;\u0026#34;); if (!hash.has(key)) { hash.set(key, []); } hash.get(key).push(str); } return Array.from(hash.values()); }; ","date":"2024-11-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/groupanagrams/","title":"groupAnagrams"},{"content":"128. 最长连续序列 算法 使用哈希集合 (unordered_set)：\n首先将数组中的所有元素存入一个哈希集合 s 中，便于快速判断某个数字是否存在 遍历数组，寻找序列起点：\n对于每个数字 start，如果 start - 1 不存在于集合中，说明它是某个连续序列的起点 从这个起点开始，逐步检查 start + 1, start + 2, … 是否存在于集合中，计算连续序列的长度 优化：删除已访问元素：\n在遍历过程中，一旦某个数字被处理，可以从集合中删除，避免后续重复处理 更新结果：\n记录所有连续序列的最大长度 复杂度分析 时间复杂度 构建哈希集合：O(n) ，其中 n 是数组长度 遍历数组： 每个元素最多被访问两次（一次作为序列起点，一次作为序列中元素） 总复杂度为 O(n) 总时间复杂度为 O(n) 空间复杂度 使用了一个哈希集合存储数组中的所有元素，空间复杂度为 O(n) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public: int longestConsecutive(std::vector\u0026lt;int\u0026gt;\u0026amp; nums) { std::unordered_set\u0026lt;int\u0026gt; s; // 哈希集合存储所有数字 for (int num : nums) s.insert(num); int res = 0; // 记录最长连续序列的长度 for (int start : nums) { // 如果 start 是序列的起点（前一个数字不存在） if (s.count(start) \u0026amp;\u0026amp; !s.count(start - 1)) { int end = start; // 初始化序列的起点 s.erase(end); // 移除起点，避免重复处理 // 找到当前序列的结尾 while (s.count(end + 1)) { end += 1; s.erase(end); // 同样移除，优化后续查询 } // 更新最长长度 res = std::max(res, end - start + 1); } } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution: def longestConsecutive(self, nums: List[int]) -\u0026gt; int: hash_set = set(nums) res = 0 for start in nums: if start in hash_set and start - 1 not in hash_set: end = start while end in hash_set: hash_set.remove(end) end += 1 res = max(res, end - start) return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func longestConsecutive(nums []int) int { hash_set := make(map[int]bool) for _, x := range nums { hash_set[x] = true } res := 0 for _, start := range nums { if hash_set[start] \u0026amp;\u0026amp; !hash_set[start - 1] { end := start for hash_set[end] { delete(hash_set, end) end ++ } if res \u0026lt; end - start { res = end - start } } } return res } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * @param {number[]} nums * @return {number} */ var longestConsecutive = function (nums) { let hash_set = new Set(nums); let res = 0; for (let start of nums) { if (hash_set.has(start) \u0026amp;\u0026amp; !hash_set.has(start - 1)) { let end = start; while (hash_set.has(end)) { hash_set.delete(end); end++; } res = Math.max(res, end - start); } } return res; }; ","date":"2024-11-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longestconsecutivesequence/","title":"longestConsecutiveSequence"},{"content":"11. 盛最多水的容器 算法 容器的水量由两条垂线的较小高度和它们之间的水平距离决定，公式为：min(height[i], height[j]) * (j - i)\n初始时，将左右指针分别置于数组的两端 计算当前两条垂线能容纳的水量，并更新最大水量 移动高度较小的一侧的指针： 因为容器的水量由两条垂线的较小高度决定，移动较小高度的一侧可能增加更高的高度，从而得到更大的水量 当左右指针相遇时，遍历结束，最大水量即为结果 复杂度分析 时间复杂度 指针遍历：每次移动一个指针，最多遍历数组一次，时间复杂度为 O(n) 空间复杂度 空间复杂度为 O(1) C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public: int maxArea(vector\u0026lt;int\u0026gt;\u0026amp; height) { int res = 0; // 存储最大水量 int i = 0, j = height.size() - 1; // 初始化左右指针 while (i \u0026lt; j) { // 计算当前容器的水量 res = std::max(res, std::min(height[i], height[j]) * (j - i)); // 移动高度较小的一侧 if (height[i] \u0026lt; height[j]) ++ i; else -- j; } return res; // 返回最大水量 } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 class Solution: def maxArea(self, height: List[int]) -\u0026gt; int: i, j = 0, len(height) - 1 res = 0 while i \u0026lt; j: res = max(res, min(height[i], height[j]) * (j - i)); if height[i] \u0026lt; height[j]: i += 1 else: j -= 1 return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func maxArea(height []int) int { i, j := 0, len(height) - 1 res := 0 for i \u0026lt; j { h := 0 if height[i] \u0026lt; height[j] { h = height[i] } else { h = height[j] } area := h * (j - i) if area \u0026gt; res { res = area } if height[i] \u0026lt; height[j] { i ++ } else { j -- } } return res } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * @param {number[]} height * @return {number} */ var maxArea = function (height) { let i = 0, j = height.length - 1; let res = 0; while (i \u0026lt; j) { res = Math.max(res, Math.min(height[i], height[j]) * (j - i)); if (height[i] \u0026lt; height[j]) { i++; } else { j--; } } return res; }; ","date":"2024-11-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maxarea/","title":"maxArea"},{"content":"283. 移动零 算法 i 用于记录非零元素的位置 遍历数组时，将非零元素按顺序填入数组前部，并记录当前插入的位置 遍历完成后，数组前部已填满非零元素，后续位置全部填充 0 复杂度分析 时间复杂度 遍历数组一次，时间复杂度为 O(n) 填充零的操作也是 O(n) ，但两者不重叠，总体为O(n) 空间复杂度 使用了两个指针变量，空间复杂度为 O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: void moveZeroes(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int i = 0; // 指针 i，用于记录下一个非零元素的位置 // 1. 遍历数组，将所有非零元素依次放到前部 for (int num : nums) { if (num != 0) { nums[i++] = num; // 将非零元素放入 i 位置，并移动 i } } // 2. 将剩余位置填充为 0 while (i \u0026lt; nums.size()) { nums[i++] = 0; } } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 class Solution: def moveZeroes(self, nums: List[int]) -\u0026gt; None: i = 0 for x in nums: if x != 0: nums[i] = x i += 1 while i \u0026lt; len(nums): nums[i] = 0 i += 1 Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func moveZeroes(nums []int) { i := 0 for _, x := range nums { if x != 0 { nums[i] = x i ++ } } for i \u0026lt; len(nums) { nums[i] = 0 i ++ } } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * @param {number[]} nums * @return {void} Do not return anything, modify nums in-place instead. */ var moveZeroes = function (nums) { let i = 0; for (let x of nums) { if (x != 0) { nums[i++] = x; } } while (i \u0026lt; nums.length) { nums[i++] = 0; } }; ","date":"2024-11-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/movezeroes/","title":"moveZeroes"},{"content":"42. 接雨水 算法 条件： 雨水的高度由当前柱子左右两边的最高柱子决定 能接的雨水量为两侧最高柱子中较小值减去当前柱子的高度 预处理左右最高高度： 左侧最高高度数组 (left_max)： 遍历数组，从左到右记录当前位置左侧的最高柱子 右侧最高高度数组 (right_max)： 遍历数组，从右到左记录当前位置右侧的最高柱子 计算雨水量： 遍历数组，每个位置接的雨水为 min(left_max[i], right_max[i]) - height[i] 累加所有位置的雨水量，得到最终结果 复杂度分析 时间复杂度 构建 left_max 和 right_max：O(n) 计算雨水量：O(n) 总时间复杂度：O(n) 空间复杂度 需要额外的两个数组存储 left_max 和 right_max，空间复杂度为 O(n) C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public: int trap(vector\u0026lt;int\u0026gt;\u0026amp; height) { int n = height.size(); if (n == 0) return 0; // 边界条件，空数组返回 0 // 1. 构建左侧最高高度数组 std::vector\u0026lt;int\u0026gt; left_max(n); left_max[0] = height[0]; for (int i = 1; i \u0026lt; n; ++i) left_max[i] = std::max(left_max[i - 1], height[i]); // 2. 构建右侧最高高度数组 std::vector\u0026lt;int\u0026gt; right_max(n); right_max[n - 1] = height[n - 1]; for (int i = n - 2; i \u0026gt;= 0; --i) right_max[i] = std::max(right_max[i + 1], height[i]); // 3. 计算总雨水量 int res = 0; for (int i = 0; i \u0026lt; n; ++i) res += std::min(left_max[i], right_max[i]) - height[i]; return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution: def trap(self, height: List[int]) -\u0026gt; int: n = len(height) left_max_height = [0] * n left_max_height[0] = height[0] for i in range(1, n): left_max_height[i] = max(left_max_height[i - 1], height[i]) right_max_height = [0] * n right_max_height[n - 1] = height[n - 1] for i in range(n - 2, -1, -1): right_max_height[i] = max(right_max_height[i + 1], height[i]) rainfall = 0 for i in range(n): rainfall += min(left_max_height[i], right_max_height[i]) - height[i] return rainfall Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 func trap(height []int) int { n := len(height) left_max_height := make([]int, n) left_max_height[0] = height[0] for i := 1; i \u0026lt; n; i ++ { if left_max_height[i - 1] \u0026lt; height[i] { left_max_height[i] = height[i] } else { left_max_height[i] = left_max_height[i - 1] } } right_max_height := make([]int, n) right_max_height[n - 1] = height[n - 1] for i := n - 2; i \u0026gt;= 0; i -- { if right_max_height[i + 1] \u0026lt; height[i] { right_max_height[i] = height[i] } else { right_max_height[i] = right_max_height[i + 1] } } rainfall := 0 for i := 0; i \u0026lt; n; i ++ { if left_max_height[i] \u0026lt; right_max_height[i] { rainfall += left_max_height[i] - height[i] } else { rainfall += right_max_height[i] - height[i] } } return rainfall } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /** * @param {number[]} height * @return {number} */ var trap = function (height) { let n = height.length; let left_max_height = Array(n).fill(0); left_max_height[0] = height[0]; for (let i = 1; i \u0026lt; n; ++i) { left_max_height[i] = Math.max(left_max_height[i - 1], height[i]); } let right_max_height = Array(n).fill(0); right_max_height[n - 1] = height[n - 1]; for (let i = n - 2; i \u0026gt;= 0; --i) { right_max_height[i] = Math.max(right_max_height[i + 1], height[i]); } let rainfall = 0; for (let i = 0; i \u0026lt; n; ++i) { rainfall += Math.min(left_max_height[i], right_max_height[i]) - height[i]; } return rainfall; }; ","date":"2024-11-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/trap/","title":"trap"},{"content":"31. LRUCache 分析 LRU（Least Recently Used）是一种缓存替换策略，淘汰最久未被使用的数据，以保证缓存内存的有效利用率\n核心数据结构：\n双向链表：维护缓存访问顺序，最新使用的数据放在链表头，最久未使用的数据放在链表尾。双向链表的插入和删除操作可以在 O(1) 时间内完成，适用于缓存频繁更新的场景 哈希表：通过关键字快速定位链表中的节点 get(key)：\n若 key 存在，将对应节点移动到链表头，并返回其值 若 key 不存在，返回 -1 put(key, value)：\n若 key 已存在，更新其值并移动到链表头 若 key 不存在： 如果缓存已满，删除链表尾的节点（最久未使用） 将新节点插入到链表头 时间复杂度 get(key)：O(1)\n哈希表查找节点是 O(1) ，双向链表操作也是 O(1) put(key, value)：O(1)\n哈希表插入和删除是 O(1) ，双向链表操作也是 O(1) 空间复杂度 哈希表和双向链表的空间复杂度均为 O(n) ，其中 n 是缓存容量 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class LRUCache { public: // 定义双向链表节点 struct Node { Node(int key, int value) : m_key(key), m_value(value) , prev(nullptr), next(nullptr) {} int m_key, m_value; Node *prev, *next; }; int n; // 缓存容量 Node *head, *tail; // 双链表的虚拟头节点 std::unordered_map\u0026lt;int, Node*\u0026gt; hash; // 哈希表，快速查找节点 // 初始化 LRU 缓存 LRUCache(int capacity) { n = capacity; head = new Node(-1, -1); // 创建虚拟头节点 tail = new Node(-1, -1); // 创建虚拟尾节点 head-\u0026gt;next = tail; // 初始化双向链表 tail-\u0026gt;prev = head; } // 从链表中删除指定节点 void remove(Node* cur) { cur-\u0026gt;next-\u0026gt;prev = cur-\u0026gt;prev; cur-\u0026gt;prev-\u0026gt;next = cur-\u0026gt;next; } // 在链表头部插入指定节点 void insert(Node* cur) { cur-\u0026gt;next = head-\u0026gt;next; cur-\u0026gt;prev = head; head-\u0026gt;next-\u0026gt;prev = cur; head-\u0026gt;next = cur; } // 获取节点的值 int get(int key) { if (!hash.count(key)) // 节点不存在 return -1; Node* cur = hash[key]; // 定位节点 remove(cur); // 更新节点在链表中的位置 insert(cur); return cur-\u0026gt;m_value; } // 插入或更新节点 void put(int key, int value) { if (hash.count(key)) // 节点已存在 { Node* cur = hash[key]; cur-\u0026gt;m_value = value; // 更新节点值 remove(cur); // 更新节点位置 insert(cur); } else // 节点不存在 { if (hash.size() == n) // 缓存已满 { Node* cur = tail-\u0026gt;prev; // 取出尾节点 remove(cur); // 从链表中移除 hash.erase(cur-\u0026gt;m_key); // 从哈希表中删除 } Node* cur = new Node(key, value); // 创建新节点 insert(cur); // 插入链表头 hash[key] = cur; // 添加到哈希表 } } }; ","date":"2024-11-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/lrucache/","title":"LRUCache"},{"content":"56. mergeIntervals 分析 排序： 首先将所有区间按照起始值从小到大排序。排序后，若两个区间有重叠，它们一定是相邻的 遍历与合并： 用变量 l 和 r 分别表示当前合并区间的起始和结束 遍历排序后的区间： 如果当前遍历区间的起始值大于当前合并区间的结束值 r，说明当前区间与前面的合并区间没有重叠，应将前面的合并区间加入结果，并更新 l 和 r 如果有重叠，则将 r 更新为当前区间结束值的较大值 遍历结束后，将最后一个合并区间加入结果 时间复杂度 排序：O(nlogn) ，n 是区间的数量 遍历：O(n)，每个区间只会被处理一次 总时间复杂度为 O(nlogn)\n空间复杂度 排序所需的额外空间复杂度为 O(logn) （排序算法的递归栈空间） 结果存储的空间复杂度为 O(n) 总空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; merge(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; intervals) { vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; res; // 用于存储最终结果 if (intervals.empty()) return res; // 如果输入为空，直接返回空数组 // 1. 按区间的起始值排序 sort(intervals.begin(), intervals.end()); // 2. 初始化合并区间的左右边界 int l = intervals[0][0], r = intervals[0][1]; // 3. 遍历剩余区间 for (int i = 1; i \u0026lt; intervals.size(); ++ i) { // 当前区间的起始值 \u0026gt; 当前合并区间的结束值，说明没有重叠 if (r \u0026lt; intervals[i][0]) { res.push_back({l, r}); // 将当前合并区间加入结果 l = intervals[i][0]; // 更新新的合并区间的起始值 r = intervals[i][1]; // 更新新的合并区间的结束值 } else { // 如果有重叠，则更新当前合并区间的结束值 r = max(r, intervals[i][1]); } } // 将最后一个合并区间加入结果 res.push_back({l, r}); return res; } }; ","date":"2024-11-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/mergeintervals/","title":"mergeIntervals"},{"content":"152. maxProduct 分析 使用f[i - 1]记录所有以nums[i - 1]元素结尾的连续子数组中的最大乘积\n使用g[i - 1]记录所有以nums[i - 1]元素结尾的连续子数组中的最小乘积\n求f[i]\n当nums[i]为正数时，f[i] = std::max(nums[i], f[i - 1] * nums[i])，因为正数与最大值相乘，值最大，所以与f[i - 1]相乘 当nums[i]为负数时，f[i] = std::max(nums[i], g[i - 1] * nums[i])，因为负数与最小值相乘，值最大，所以与g[i - 1]相乘 求g[i]\n当nums[i]为正数时，g[i] = std::min(nums[i], g[i - 1] * nums[i])，因为正数与最小值相乘，值最小，所以与g[i - 1]相乘 当nums[i]为负数时，g[i] = std::min(nums[i], f[i - 1] * nums[i])，因为负数与最大值相乘，值最小，所以与f[i - 1]相乘 当nums[i]为0时，f[i]和g[i]都变为0，即连续子数组断开，从下一个元素重新开始找最大乘积\n优化：因为每次更新f[i]和g[i]只需要前一个数，所以不需要开两个数组记录所有值\n时间复杂度：O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public: int maxProduct(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int res = nums[0]; int f_last = nums[0], g_last = nums[0]; for (int i = 1; i \u0026lt; nums.size(); ++ i) { int f_cur = f_last * nums[i], g_cur = g_last * nums[i]; f_last = std::max(nums[i], std::max(f_cur, g_cur)); g_last = std::min(nums[i], std::min(f_cur, g_cur)); res = std::max(res, f_last); } return res; } }; ","date":"2024-11-23T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/maxproduct/","title":"maxProduct"},{"content":"190. reverseBits 分析 核心步骤：取二进制数的第i位n \u0026gt;\u0026gt; i \u0026amp; 1\nres每次左移一位，然后将n的最低位加到res上\nC++代码 1 2 3 4 5 6 7 8 9 10 class Solution { public: uint32_t reverseBits(uint32_t n) { uint32_t res = 0; for (int i = 0; i \u0026lt; 32; ++ i) res = (res \u0026lt;\u0026lt; 1) + (n \u0026gt;\u0026gt; i \u0026amp; 1); return res; } }; ","date":"2024-11-23T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reversebits/","title":"reverseBits"},{"content":"15. 三数之和 算法 排序数组： 首先将数组从小到大排序，方便后续操作 固定一个数，双指针搜索： 遍历排序后的数组，用 i 指向当前固定的数 在 i 的右侧，使用双指针 j 和 k： j 指向 i + 1 k 指向数组末尾 检查三数之和： 若三数之和小于 0，说明需要更大的值，移动 j 向右 若三数之和大于 0，说明需要更小的值，移动 k 向左 若三数之和等于 0，记录结果，同时移动 j 和 k 跳过重复值 去重： 对于固定数 nums[i]，若 nums[i] = nums[i-1]，跳过当前遍历，避免重复三元组 对于 nums[j] 和 nums[k]，在找到一个解后，继续移动跳过相同值 复杂度分析 时间复杂度 排序复杂度：O(nlogn) 三重循环复杂度：外层循环 O(n)，内层双指针 O(n)，总复杂度为 O(n^2) 总时间复杂度 O(n^2) 空间复杂度 使用排序 O(logn) 的额外空间，其余操作在原地完成，空间复杂度为 O(1) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; threeSum(vector\u0026lt;int\u0026gt;\u0026amp; nums) { std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; res; std::sort(nums.begin(), nums.end()); // 对数组进行排序 for (int i = 0; i \u0026lt; nums.size(); ++i) { // 跳过重复的固定数 if (i \u0026gt; 0 \u0026amp;\u0026amp; nums[i] == nums[i - 1]) continue; // 双指针寻找其他两数 for (int j = i + 1, k = nums.size() - 1; j \u0026lt; k; ++ j) { // 跳过重复的第二个数 if (j \u0026gt; i + 1 \u0026amp;\u0026amp; nums[j] == nums[j - 1]) continue; // 移动右指针，寻找满足条件的三元组 while (j \u0026lt; k - 1 \u0026amp;\u0026amp; nums[i] + nums[j] + nums[k - 1] \u0026gt;= 0) -- k; // 判断当前三数之和是否为零 if (nums[i] + nums[j] + nums[k] == 0) res.push_back({nums[i], nums[j], nums[k]}); } } return res; } }; Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution: def threeSum(self, nums: List[int]) -\u0026gt; List[List[int]]: res = [] nums.sort() for i in range(len(nums)): if i \u0026gt; 0 and nums[i] == nums[i - 1]: continue j, k = i + 1, len(nums) - 1 while j \u0026lt; k: if j \u0026gt; i + 1 and nums[j] == nums[j - 1]: j += 1 continue while j \u0026lt; k - 1 and nums[i] + nums[j] + nums[k - 1] \u0026gt;= 0: k -= 1 if nums[i] + nums[j] + nums[k] == 0: res.append([nums[i], nums[j], nums[k]]) j += 1 return res Go 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func threeSum(nums []int) [][]int { res := [][]int{} sort.Ints(nums) for i := 0; i \u0026lt; len(nums); i ++ { if i \u0026gt; 0 \u0026amp;\u0026amp; nums[i] == nums[i - 1] { continue } for j, k := i + 1, len(nums) - 1; j \u0026lt; k; j ++ { if j \u0026gt; i + 1 \u0026amp;\u0026amp; nums[j] == nums[j - 1] { continue } for j \u0026lt; k - 1 \u0026amp;\u0026amp; nums[i] + nums[j] + nums[k - 1] \u0026gt;= 0 { k -- } if nums[i] + nums[j] + nums[k] == 0 { res = append(res, []int{nums[i], nums[j], nums[k]}) } } } return res } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /** * @param {number[]} nums * @return {number[][]} */ var threeSum = function (nums) { let res = []; nums.sort((a, b) =\u0026gt; a - b); for (let i = 0; i \u0026lt; nums.length; i++) { if (i \u0026gt; 0 \u0026amp;\u0026amp; nums[i] == nums[i - 1]) { continue; } for (let j = i + 1, k = nums.length - 1; j \u0026lt; k; j++) { if (j \u0026gt; i + 1 \u0026amp;\u0026amp; nums[j] == nums[j - 1]) { continue; } while (j \u0026lt; k - 1 \u0026amp;\u0026amp; nums[i] + nums[j] + nums[k - 1] \u0026gt;= 0) { k--; } if (nums[i] + nums[j] + nums[k] == 0) { res.push([nums[i], nums[j], nums[k]]); } } } return res; }; ","date":"2024-11-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/three-sum/","title":"Three Sum"},{"content":"1. 两数之和 算法 利用哈希表存储数组元素及其下标： 使用一个哈希表，键为数组元素值，值为对应的下标 哈希表可以快速查询某个元素是否已经出现过 遍历数组，寻找目标值对应的数： 在遍历数组时，对于当前元素 nums[i]，计算另一个需要的数 x = target - nums[i]； 在哈希表中检查是否存在 x： 如果 x 存在，说明找到了两个数，返回它们的下标 如果 x 不存在，将当前数 nums[i] 及其索引存入哈希表，继续遍历 复杂度分析 时间复杂度 哈希表查询和插入：每次操作 O(1) 数组遍历：遍历一次数组，时间复杂度为 O(n) 总时间复杂度 O(n) 空间复杂度 需要一个哈希表存储数组中的元素及其索引，空间复杂度为 O(n) C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: vector\u0026lt;int\u0026gt; twoSum(vector\u0026lt;int\u0026gt;\u0026amp; nums, int target) { std::unordered_map\u0026lt;int, int\u0026gt; hash; // 哈希表存储元素值及其索引 for (int i = 0; i \u0026lt; nums.size(); ++ i) { int x = target - nums[i]; // 计算需要的数 // 检查是否在哈希表中 if (hash.count(x)) return {hash[x], i}; // 找到目标值，返回下标 // 将当前数存入哈希表 hash[nums[i]] = i; } return {}; // 如果无解，返回空数组（题目保证一定有解） } }; Python 代码 1 2 3 4 5 6 7 8 9 class Solution: def twoSum(self, nums: List[int], target: int) -\u0026gt; List[int]: hash_map = {} for i, num in enumerate(nums): x = target - num if x in hash_map: return [hash_map[x], i] hash_map[num] = i return [] Go 代码 1 2 3 4 5 6 7 8 9 10 11 func twoSum(nums []int, target int) []int { hash_map := make(map[int]int) for i, num := range nums { x := target - num if idx, ok := hash_map[x]; ok { return []int{idx, i} } hash_map[num] = i } return []int{} } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** * @param {number[]} nums * @param {number} target * @return {number[]} */ var twoSum = function (nums, target) { let hash_map = new Map(); for (let i = 0; i \u0026lt; nums.length; i++) { let x = target - nums[i]; if (hash_map.has(x)) { return [hash_map.get(x), i]; } hash_map.set(nums[i], i); } return []; }; ","date":"2024-11-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/twosum/","title":"twoSum"},{"content":"33. Search in Rotated Sorted Array 分析 找旋转点： 通过二分查找，判断 nums[mid] 和 nums[0] 的关系： 如果 nums[mid] \u0026gt;= nums[0]，说明 mid 在前半部分，继续搜索右侧 否则，说明 mid 在后半部分，继续搜索左侧 最终 r 指向旋转点前的最后一个位置 确定搜索范围： 如果 target \u0026gt;= nums[0]，说明目标值在前半部分，设置 l = 0 否则，目标值在后半部分，设置 l = r + 1，r = nums.size() - 1 二分查找目标值： 普通的二分查找，最终检查 nums[r] 是否等于目标值 时间复杂度 找旋转点和目标值各需要一次二分查找，每次的时间复杂度为 O(log n) 总体时间复杂度为 O(log n)\n空间复杂度 使用常量级别额外空间，空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution { public: int search(vector\u0026lt;int\u0026gt;\u0026amp; nums, int target) { if (nums.empty()) // 特殊情况：数组为空 return -1; // 第一次二分查找：找到旋转点 int l = 0, r = nums.size() - 1; while (l \u0026lt; r) { int mid = (l + r + 1) \u0026gt;\u0026gt; 1; // 偏向右侧 if (nums[0] \u0026lt;= nums[mid]) l = mid; // 旋转点在右侧 else r = mid - 1; // 旋转点在左侧 } // 确定搜索范围 if (target \u0026gt;= nums[0]) // 目标值在前半部分 l = 0; else // 目标值在后半部分 { l = r + 1; r = nums.size() - 1; } // 第二次二分查找：找到目标值 while (l \u0026lt; r) { int mid = (l + r) \u0026gt;\u0026gt; 1; if (target \u0026lt;= nums[mid]) r = mid; // 收缩右边界 else l = mid + 1; // 收缩左边界 } // 检查是否找到目标值 if (target == nums[r]) return r; return -1; } }; ","date":"2024-04-03T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/search-in-rotated-sorted-array/","title":"Search in Rotated Sorted Array"},{"content":"32. Longest Valid Parentheses 分析 遍历字符串，当遇到 '(' 时，将索引入栈 遇到 ')' 时，分两种情况： 如果栈不为空，说明当前 ')' 可以与之前的 '(' 配对： 弹出栈顶索引 如果栈仍不为空，则当前有效括号长度为 i - stk.top() 如果栈为空，则当前有效括号长度为 i - end 如果栈为空，说明当前 ')' 无法配对，将其索引更新为 end 每次更新最长有效括号长度 res 时间复杂度： 遍历字符串一次，每个索引至多入栈、出栈一次，时间复杂度为 O(n)\n空间复杂度 使用栈存储括号索引，最坏情况下栈中存储所有的左括号，空间复杂度为 O(n)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public: int longestValidParentheses(string s) { std::stack\u0026lt;int\u0026gt; stk; // 用于存储未匹配的括号索引 int end = -1; // 记录最后一个无法匹配的右括号索引 int res = 0; // 记录最长有效括号长度 for (int i = 0; i \u0026lt; s.size(); ++i) { if (s[i] == \u0026#39;(\u0026#39;) stk.push(i); // 左括号入栈 else { if (!stk.empty()) // 有未匹配的左括号 { stk.pop(); // 配对成功，弹出栈顶 if (!stk.empty()) res = std::max(res, i - stk.top()); // 栈不为空，用当前索引减去栈顶索引 else res = std::max(res, i - end); // 栈为空，用当前索引减去 `end` } else end = i; // 当前右括号无法配对，更新 `end` } } return res; // 返回最长有效括号长度 } }; ","date":"2024-04-02T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-valid-parentheses/","title":"Longest Valid Parentheses"},{"content":"31. Next Permutation 分析 通过观察可以发现：\n字典序的下一个排列是在现有排列的基础上，从右往左找到某个位置调整以得到更大的排列，同时调整后尽可能小\n当数组已经是降序排列时，不存在更大的排列，此时直接将其翻转为最小排列\n步骤如下：\n从右往左找到第一个递减的数： 从后往前找到第一个位置 k-1，满足 nums[k-1] \u0026lt; nums[k]。此时，nums[k-1]是需要调整的“转折点” 如果不存在这样的 k-1，说明整个数组是降序排列，直接反转数组即可 从左往右找到最后一个比 nums[k-1] 大的数： 从 k 开始向右遍历，找到最后一个比 nums[k-1] 大的数，记为 nums[t] 交换 nums[k-1] 和 nums[t] 反转 nums[k] 到数组末尾的元素： 确保调整后的部分是最小的排列，符合字典序规则 时间复杂度 寻找递减位置：O(n) 找到交换位置：O(n) 反转数组：O(n) 总时间复杂度为 O(n)\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: void nextPermutation(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int k = nums.size() - 1; // 初始化指针从右向左寻找 while (k \u0026gt; 0 \u0026amp;\u0026amp; nums[k - 1] \u0026gt;= nums[k]) // 找到第一个递减的数 --k; if (k \u0026lt;= 0) // 如果整个数组是降序排列，直接反转 { std::reverse(nums.begin(), nums.end()); } else { int t = k; // 从左向右找到比 nums[k-1] 大的最后一个数 while (t \u0026lt; nums.size() \u0026amp;\u0026amp; nums[k - 1] \u0026lt; nums[t]) ++ t; // 交换 nums[k-1] 和 nums[t-1] std::swap(nums[k - 1], nums[t - 1]); // 反转 nums[k] 到末尾的部分 std::reverse(nums.begin() + k, nums.end()); } } }; ","date":"2024-04-01T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/next-permutation/","title":"Next Permutation"},{"content":"29. Divide Two Integers 分析 用位运算模拟除法过程\n例如，对于$\\frac{x}{y}=k$， 将k转换成2进制的形式，有$k = 2^{0} + 2^{1} + \u0026hellip; + 2^{31}$\n我们可以将每一位的值存下来，然后从高位向低位遍历，只要 x 大于当前位的值，x 就减去当前位的值\n又因为$x=yk=y2^0+y2^1+\u0026hellip;+y2^{31}$，所以每次 x 减去第i位的值时，最终要求得的商就加上 $2^{i}$\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: int divide(int dividend, int divisor) { typedef long long LL; vector\u0026lt;LL\u0026gt; exp; bool is_minus = false; if (dividend \u0026lt; 0 \u0026amp;\u0026amp; divisor \u0026gt; 0 || dividend \u0026gt; 0 \u0026amp;\u0026amp; divisor \u0026lt; 0) is_minus = true; LL a = abs((LL)dividend), b = abs((LL)divisor); for (LL i = b; i \u0026lt;= a; i = i + i) exp.push_back(i); LL res = 0; for (int i = exp.size() - 1; i \u0026gt;= 0; i -- ) { if (a \u0026gt;= exp[i]) { a -= exp[i]; res += (LL)1 \u0026lt;\u0026lt; i; } } if (is_minus) res = -res; if (res \u0026gt; INT_MAX) res = INT_MAX; if (res \u0026lt; INT_MIN) res = INT_MIN; return res; } }; ","date":"2024-03-30T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/divide-two-integers/","title":"Divide Two Integers"},{"content":"28. Find the Index of the First Occurrence in a String 分析 在源传和目标串首部添加一个字符，使得下标匹配从1开始，方便处理。 构建目标串的 next 数组。具体来说，对于目标串的任意位置 i，我们要找到一个数 j，使得 needle[1:j] 和 needle[i-j+1:i] 是相等的，并且 j 的值尽可能大。每个 next[i] 对应一个 j 。 遍历源串，在匹配过程中，若 haystack[i] 与 needle[j+1] 不相等，则将 j 回溯到 next[j]。若相等，则同时向后移动 i 和 j。若 j 移动到 needle 的末尾，则表示在 haystack 中找到了 needle，返回 i - m 即可。 时间复杂度为 O(n+m)，其中 n 和 m 分别是 haystack 和 needle 的长度。 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: int strStr(string haystack, string needle) { if (needle.empty()) return 0; int n = haystack.size(), m = needle.size(); haystack = \u0026#39; \u0026#39; + haystack, needle = \u0026#39; \u0026#39; + needle; vector\u0026lt;int\u0026gt; next(m + 1); for (int i = 2, j = 0; i \u0026lt;= m; i ++ ) { while (j \u0026amp;\u0026amp; needle[i] != needle[j + 1]) j = next[j]; if (needle[i] == needle[j + 1]) j ++ ; next[i] = j; } for (int i = 1, j = 0; i \u0026lt;= n; i ++ ) { while (j \u0026amp;\u0026amp; haystack[i] != needle[j + 1]) j = next[j]; if (haystack[i] == needle[j + 1]) j ++ ; if (j == m) return i - m; } return -1; } }; ","date":"2024-03-29T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/find-the-index-of-the-first-occurrence-in-a-string/","title":"Find the Index of the First Occurrence in a String"},{"content":"27. 移除元素 算法 双指针：\n一个读指针（通过范围 for 遍历 nums） 一个写指针 i，用于记录当前应当写入的位置。 每次遇到不等于 val 的元素，就写入当前位置 i 并将 i ++ 复杂度分析 时间复杂度：O(n)，每个元素最多被访问一次 空间复杂度：O(1)，只使用了常数额外空间 C++ 代码 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public: int removeElement(vector\u0026lt;int\u0026gt;\u0026amp; nums, int val) { int i = 0; // 写指针 for (int x : nums) if (x != val) nums[i ++ ] = x; return i; } }; Python 代码 1 2 3 4 5 6 7 8 class Solution: def removeElement(self, nums: List[int], val: int) -\u0026gt; int: i = 0 for x in nums: if x != val: nums[i] = x i += 1 return i Go 代码 1 2 3 4 5 6 7 8 9 10 func removeElement(nums []int, val int) int { i := 0 for _, x := range nums { if x != val { nums[i] = x i ++ } } return i } JavaScript 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * @param {number[]} nums * @param {number} val * @return {number} */ var removeElement = function (nums, val) { let i = 0; for (let x of nums) { if (x !== val) { nums[i++] = x; } } return i; }; ","date":"2024-03-28T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/remove-element/","title":"Remove Element"},{"content":"26. Remove Duplicates from Sorted Array 分析 用索引 k 记录非重复元素的位置。 判断当前元素 i 是否为第一个元素 或 是否与前一个元素不等。 如果满足上述条件，将当前元素复制到数组中索引为 k 的位置，然后递增 k。 最后返回索引 k，它表示移除重复元素后的数组长度。 C++代码 1 2 3 4 5 6 7 8 9 10 class Solution { public: int removeDuplicates(vector\u0026lt;int\u0026gt;\u0026amp; nums) { int k = 0; for (int i = 0; i \u0026lt; nums.size(); i ++ ) if (!i || nums[i] != nums[i - 1]) nums[k ++ ] = nums[i]; return k; } }; ","date":"2024-03-27T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/remove-duplicates-from-sorted-array/","title":"Remove Duplicates from Sorted Array"},{"content":"25. Reverse Nodes in k-Group 分析 引入虚拟头节点\n创建一个虚拟头节点 dummy，使其 next 指向链表的头节点，以便统一处理头节点的翻转 遍历链表检查是否需要翻转\n使用指针 cur 标记当前分组的前驱节点 检查 cur 后是否至少还有 k 个节点。如果节点数不足 k，直接退出循环 翻转当前分组的 k 个节点\n记录当前分组的起点 a 和翻转后的起点 b 按照链表翻转的规则，通过调整指针完成当前分组内的节点翻转，循环 k-1 次 连接翻转后的链表\n让当前分组的前驱节点 cur-\u0026gt;next 指向翻转后的新头节点 a 让当前分组的尾节点连接到下一分组的起始节点 b 更新 cur 为当前分组的尾节点 时间复杂度 每个节点最多被访问两次（检查和翻转），时间复杂度 O(n)\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public: ListNode* reverseKGroup(ListNode* head, int k) { // 创建虚拟头节点 ListNode* dummy = new ListNode(-1); dummy-\u0026gt;next = head; ListNode* cur = dummy; while (true) { // 检查是否有足够的节点进行翻转 ListNode* check = cur; for (int i = 0; i \u0026lt; k \u0026amp;\u0026amp; check; ++i) check = check-\u0026gt;next; if (!check) break; // 翻转当前分组的 k 个节点 ListNode *a = cur-\u0026gt;next, *b = cur-\u0026gt;next-\u0026gt;next; for (int i = 0; i \u0026lt; k - 1; ++i) { ListNode* c = b-\u0026gt;next; // 暂存下一节点 b-\u0026gt;next = a; // 翻转当前节点 a = b; // 同时后移 b = c; } // 连接翻转后的链表 ListNode* c = cur-\u0026gt;next; cur-\u0026gt;next = a; // 当前分组的前驱节点指向翻转后的新头节点 c-\u0026gt;next = b; // 当前分组的尾节点连接到下一分组的起始节点 cur = c; // 更新 cur 为当前分组的尾节点 } // 返回结果链表 return dummy-\u0026gt;next; } }; ","date":"2024-03-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/reverse-nodes-in-k-group/","title":"Reverse Nodes in k-Group"},{"content":"24. Swap Nodes in Pairs 分析 创建虚拟头节点\n为了方便处理头节点的交换，创建一个虚拟头节点 dummy，使其 next 指向链表的头节点 遍历链表并交换节点\n使用指针 cur 指向当前正在处理的一对节点的前驱节点。 检查当前节点 cur-\u0026gt;next 和 cur-\u0026gt;next-\u0026gt;next 是否存在，只有在有足够节点时才进行交换 定义两个指针 p 和 q 分别指向待交换的两个节点： 交换后，cur-\u0026gt;next 指向第二个节点 q 第一个节点 p-\u0026gt;next 指向第三个节点 第二个节点 q-\u0026gt;next 指向第一个节点 将指针 cur 移动到已交换节点对的末尾，继续处理下一对 时间复杂度 时间复杂度 O(n)，遍历链表一次，其中 n 是链表的长度\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: ListNode* swapPairs(ListNode* head) { // 创建虚拟头节点 ListNode* dummy = new ListNode(-1); dummy-\u0026gt;next = head; // 遍历链表并交换节点 for (ListNode* cur = dummy; cur-\u0026gt;next \u0026amp;\u0026amp; cur-\u0026gt;next-\u0026gt;next; ) { ListNode *p = cur-\u0026gt;next, *q = p-\u0026gt;next; // 定义待交换的两个节点 cur-\u0026gt;next = q; // 前驱节点指向第二个节点 p-\u0026gt;next = q-\u0026gt;next; // 第一个节点指向第三个节点 q-\u0026gt;next = p; // 第二个节点指向第一个节点 cur = p; // 移动到下一组的前驱节点 } // 返回结果链表 return dummy-\u0026gt;next; } }; ","date":"2024-03-26T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/swap-nodes-in-pairs/","title":"Swap Nodes in Pairs"},{"content":"23. 合并K个升序链表 分析 使用优先队列\n创建一个优先队列 heap，用于存储链表节点 队列中的元素按节点值从小到大排序（小根堆） 使用一个自定义比较器 cmp 来实现小根堆的逻辑 初始化优先队列\n将所有链表的头节点（如果非空）加入优先队列 归并链表\n每次从优先队列中取出值最小的节点 temp，将其加入新链表 如果 temp 节点还有下一个节点，则将其 next 节点加入优先队列 重复以上步骤，直到队列为空 时间复杂度 优先队列的插入和删除操作的时间复杂度为 O(logk)，其中 k 是链表的数量 每个节点都会被插入和删除一次，因此总时间复杂度为 O(nlogk)，其中 n 是链表中所有节点的总数 空间复杂度 优先队列最多同时存储 k 个节点，因此空间复杂度为 O(k)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution { public: ListNode* mergeKLists(vector\u0026lt;ListNode*\u0026gt;\u0026amp; lists) { // 自定义比较器，用于优先队列排序 struct cmp { bool operator()(ListNode* a, ListNode* b) { return a-\u0026gt;val \u0026gt; b-\u0026gt;val; // 小根堆 } }; // 定义优先队列（小根堆） std::priority_queue\u0026lt;ListNode*, std::vector\u0026lt;ListNode*\u0026gt;, cmp\u0026gt; heap; // 初始化优先队列，将每个链表的头节点加入堆中 for (ListNode* li : lists) if (li) heap.push(li); // 定义虚拟头节点，便于操作新链表 ListNode* dummy = new ListNode(-1); ListNode* cur = dummy; // 从优先队列中逐步取出最小值节点，并更新链表 while (!heap.empty()) { ListNode* temp = heap.top(); // 获取当前最小值节点 heap.pop(); // 将其从堆中移除 cur = cur-\u0026gt;next = temp; // 将其加入新链表 if (temp-\u0026gt;next) // 如果有后续节点，将后续节点加入堆中 { temp = temp-\u0026gt;next; heap.push(temp); } } // 返回合并后的链表 return dummy-\u0026gt;next; } }; ","date":"2024-03-25T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/merge-k-sorted-lists/","title":"Merge k Sorted Lists"},{"content":"22. Generate Parentheses 分析 合法的括号组合需要满足：\n左括号 ( 的数量不能超过 n 右括号 ) 的数量不能超过左括号的数量 递归函数在每一步根据当前左括号和右括号的数量，选择是否添加 ( 或 )\n如果左括号数量小于 n，可以添加 ( 如果右括号数量小于左括号，可以添加 ) 当左右括号数量均达到 n 时，将当前路径 path 加入结果集 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public: std::vector\u0026lt;std::string\u0026gt; res; // 存储所有合法的括号组合 std::string path; // 当前生成的括号组合 vector\u0026lt;string\u0026gt; generateParenthesis(int n) { dfs(n, 0, 0); // 从 0 左括号和 0 右括号开始递归 return res; } void dfs(int n, int l_p, int r_p) { // 如果左右括号都用完，生成一个合法组合 if (l_p == n \u0026amp;\u0026amp; r_p == n) { res.push_back(path); return; } // 尝试添加左括号 if (l_p \u0026lt; n) { path.push_back(\u0026#39;(\u0026#39;); // 添加左括号 dfs(n, l_p + 1, r_p); // 递归进入下一层 path.pop_back(); // 回溯，移除左括号 } // 尝试添加右括号 if (r_p \u0026lt; n \u0026amp;\u0026amp; r_p \u0026lt; l_p) { path.push_back(\u0026#39;)\u0026#39;); // 添加右括号 dfs(n, l_p, r_p + 1); // 递归进入下一层 path.pop_back(); // 回溯，移除右括号 } } }; ","date":"2024-03-24T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/generate-parentheses/","title":"Generate Parentheses"},{"content":"21. Merge Two Sorted Lists 分析 创建虚拟头节点\n创建一个虚拟头节点 dummy，用于方便拼接新链表，同时维护一个指针 cur 指向结果链表的当前末尾 比较两个链表节点值\n遍历 list1 和 list2，每次比较当前节点值，将较小值的节点接到 cur 的后面，并将对应链表的指针后移 拼接剩余节点\n如果某个链表还有剩余节点（未遍历完），直接将其接到结果链表末尾 返回结果链表\n返回虚拟头节点的下一个节点 dummy-\u0026gt;next，即为合并后的升序链表 时间复杂度 遍历两个链表，最多比较 n + m 次，其中 n 和 m 分别为两个链表的长度，时间复杂度 O(m + n)\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { // 创建虚拟头节点，简化操作 ListNode *dummy = new ListNode(); ListNode* cur = dummy; // 归并两个链表 while (list1 \u0026amp;\u0026amp; list2) { if (list1-\u0026gt;val \u0026lt;= list2-\u0026gt;val) // list1节点值较小 { cur = cur-\u0026gt;next = list1; // 接入结果链表 list1 = list1-\u0026gt;next; // list1指针后移 } else // list2节点值较小 { cur = cur-\u0026gt;next = list2; // 接入结果链表 list2 = list2-\u0026gt;next; // list2指针后移 } } // 拼接剩余的链表 if (list1) cur-\u0026gt;next = list1; if (list2) cur-\u0026gt;next = list2; // 返回结果链表 return dummy-\u0026gt;next; } }; ","date":"2024-03-22T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/merge-two-sorted-lists/","title":"Merge Two Sorted Lists"},{"content":"19. Remove Nth Node From End of List 分析 创建虚拟头节点\n为了方便处理边界情况（如删除头节点），创建一个虚拟头节点 dummy，其 next 指向链表头部 计算链表长度\n遍历链表，用变量 len 记录链表的总长度 定位目标节点的前驱节点\n倒数第 n 个节点的前驱节点是正数第 len - n 个节点 从虚拟头节点开始，移动 len - n 步到目标位置 删除目标节点\n通过修改前驱节点的 next 指针，跳过目标节点 时间复杂度 需要遍历链表两次，计算链表长度和定位目标节点位置，时间复杂度 O(L)， L 是链表长度\n空间复杂度 空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { // 创建虚拟头节点 ListNode* dummy = new ListNode(-1); dummy-\u0026gt;next = head; // 计算链表长度 int len = 0; for (ListNode* p = dummy; p; p = p-\u0026gt;next) ++len; // 定位到要删除节点的前驱节点 ListNode* p = dummy; for (int i = 0; i \u0026lt; len - n - 1; ++i) p = p-\u0026gt;next; // 删除目标节点 p-\u0026gt;next = p-\u0026gt;next-\u0026gt;next; // 返回结果链表 return dummy-\u0026gt;next; } }; ","date":"2024-03-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/remove-nth-node-from-end-of-list/","title":"Remove Nth Node From End of List"},{"content":"20. Valid Parentheses 分析 利用 栈 来检查括号匹配：\n核心规则： 左括号 (, [, { 是合法起始符号，直接入栈 遇到右括号 ), ], } 时，检查栈顶的左括号是否能匹配： 如果匹配，则栈顶元素出栈 如果不匹配，字符串无效，直接返回 false 遍历结束后，如果栈为空，则字符串有效；否则无效。 匹配判断： 两个字符匹配的条件是：abs(栈顶字符 - 当前字符) \u0026lt;= 2 ASCII 值中： ( 和 ) 相差 1 [ 和 ] 相差 2 { 和 } 相差 2 C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: bool isValid(string s) { // 特殊情况处理：空字符串 if (s.empty()) return true; // 使用栈存储左括号 std::stack\u0026lt;char\u0026gt; stk; // 遍历字符串 for (char c : s) { // 如果是左括号，入栈 if (c == \u0026#39;(\u0026#39; || c == \u0026#39;[\u0026#39; || c == \u0026#39;{\u0026#39;) stk.push(c); else { // 如果栈不为空并且栈顶括号能匹配当前右括号 if (stk.size() \u0026amp;\u0026amp; abs(stk.top() - c) \u0026lt;= 2) stk.pop(); // 匹配成功，栈顶出栈 else return false; // 不匹配，直接返回 false } } // 遍历结束后检查栈是否为空 return stk.empty(); } }; ","date":"2024-03-21T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/valid-parentheses/","title":"Valid Parentheses"},{"content":"18. 4Sum 分析 与三数之和一样，只是这里要枚举两个位置 i 和 j。\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: typedef long long LL; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; fourSum(vector\u0026lt;int\u0026gt;\u0026amp; nums, int target) { vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; res; sort(nums.begin(), nums.end()); for (int i = 0; i \u0026lt; nums.size(); i ++ ) { if (i \u0026amp;\u0026amp; nums[i] == nums[i - 1]) continue; for (int j = i + 1; j \u0026lt; nums.size(); j ++ ) { if (j \u0026gt; i + 1 \u0026amp;\u0026amp; nums[j] == nums[j - 1]) continue; for (int k = j + 1, l = nums.size() - 1; k \u0026lt; l; k ++ ) { if (k \u0026gt; j + 1 \u0026amp;\u0026amp; nums[k] == nums[k - 1]) continue; while (l - 1 \u0026gt; k \u0026amp;\u0026amp; (LL)nums[i] + nums[j] + nums[k] + nums[l - 1] \u0026gt;= target) l -- ; if ((LL)nums[i] + nums[j] + nums[k] + nums[l] == (LL)target) { res.push_back({nums[i], nums[j], nums[k], nums[l]}); } } } } return res; } }; ","date":"2024-03-20T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/4sum/","title":"4Sum"},{"content":"17. Letter Combinations of a Phone Number 分析 使用深度优先搜索（DFS）+ 回溯的方法。\n遍历数字字符串，按每个数字对应的字母集依次尝试： 对当前数字的每个字母，将其加入组合路径中 递归进入下一个数字，直到遍历完所有数字，将当前组合加入结果集 回溯到上一步，尝试其他可能性 时间复杂度 数字字符串的长度为 n，每个数字对应最多 4 个字母。构造所有组合的时间复杂度为 O(4^n)\n空间复杂度 递归深度为 n，路径存储需要 O(n)。 结果集存储需要 O(4^n * n) C++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public: std::vector\u0026lt;std::string\u0026gt; res; // 存储结果集 std::string path; // 当前路径 std::vector\u0026lt;std::string\u0026gt; strs = { // 数字对应的字母集 \u0026#34;\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;, \u0026#34;ghi\u0026#34;, \u0026#34;jkl\u0026#34;, \u0026#34;mno\u0026#34;, \u0026#34;pqrs\u0026#34;, \u0026#34;tuv\u0026#34;, \u0026#34;wxyz\u0026#34; }; vector\u0026lt;string\u0026gt; letterCombinations(string digits) { if (digits.empty()) // 特殊情况处理：空字符串 return res; dfs(digits, 0); // 从第一个数字开始递归 return res; } void dfs(std::string\u0026amp; digits, int u) { if (u == digits.size()) { res.push_back(path); // 将当前路径加入结果集 return; } for (char c : strs[digits[u] - \u0026#39;0\u0026#39;]) // 遍历当前数字对应的字母集 { path.push_back(c); // 将当前字母加入路径 dfs(digits, u + 1); // 递归到下一个数字 path.pop_back(); // 回溯，移除最后一个字母 } } }; ","date":"2024-03-19T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/letter-combinations-of-a-phone-number/","title":"Letter Combinations of a Phone Number"},{"content":"16. 3Sum Closest 分析 暴力做法就是三重循环，每种答案枚举一遍，求一个最接近的值。\n用双指针优化。先枚举位置i，对每一个位置j，找到一个最小的位置k使得 nums[i] + nums[j] + nums[k] \u0026gt;= target，这样可以得到大于等于target的最小值。并且，当k满足前述条件时，必然有 nums[i] + nums[j] + nums[k - 1] \u0026lt; target，也就可以得到小于target的最大值。每次更新下这两个值即可。\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public: int threeSumClosest(vector\u0026lt;int\u0026gt;\u0026amp; nums, int target) { sort(nums.begin(), nums.end()); pair\u0026lt;int, int\u0026gt; res(INT_MAX, INT_MAX); for (int i = 0; i \u0026lt; nums.size(); i ++ ) for (int j = i + 1, k = nums.size() - 1; j \u0026lt; k; j ++ ) { while (k - 1 \u0026gt; j \u0026amp;\u0026amp; nums[i] + nums[j] + nums[k - 1] \u0026gt;= target) k -- ; int s = nums[i] + nums[j] + nums[k]; res = min(res, make_pair(abs(s - target), s)); if (k - 1 \u0026gt; j) { s = nums[i] + nums[j] + nums[k - 1]; res = min(res, make_pair(target - s, s)); } } return res.second; } }; ","date":"2024-03-18T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/3sum-closest/","title":"3Sum Closest"},{"content":"14. Longest Common Prefix 分析 逐字符比较： 以第一个字符串 strs[0] 为基准，逐个字符增加前缀 对比每个字符串是否都以当前前缀开头 剪枝优化： 如果某个字符串不包含当前前缀，则当前前缀的上一个状态就是最长公共前缀 时间复杂度 最坏情况：所有字符串相同，需要逐字符对比，O(m * n) ，m 是第一个字符串的长度，n 是字符串数组的长度\n空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public: string longestCommonPrefix(vector\u0026lt;string\u0026gt;\u0026amp; strs) { std::string cur; // 当前公共前缀 for (char c : strs[0]) // 遍历第一个字符串的每个字符 { cur += c; // 将当前字符加入前缀 for (int i = 1; i \u0026lt; strs.size(); ++i) // 遍历剩余字符串 { // 如果当前字符串不以 cur 为前缀 if (strs[i].substr(0, cur.size()) != cur) return cur.substr(0, cur.size() - 1); // 返回上一个前缀 } } return cur; // 所有字符串都包含 cur，返回完整前缀 } }; 优化 减少 substr 使用：substr 有额外的时间和空间开销，可直接比较字符，避免生成子串\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public: string longestCommonPrefix(vector\u0026lt;string\u0026gt;\u0026amp; strs) { if (strs.empty()) return \u0026#34;\u0026#34;; for (int i = 0; i \u0026lt; strs[0].size(); ++i) { char c = strs[0][i]; for (int j = 1; j \u0026lt; strs.size(); ++j) { // 如果超出当前字符串长度或字符不匹配 if (i \u0026gt;= strs[j].size() || strs[j][i] != c) return strs[0].substr(0, i); } } return strs[0]; } }; ","date":"2024-03-16T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/longest-common-prefix/","title":"Longest Common Prefix"},{"content":"13. Roman to Integer 分析 1 2 3 4 5 6 7 8 9 I II III IV V VI VII VIII IX 10 20 30 40 50 60 70 80 90 X XX XXX XL L LX LXX LXXX XL 100 200 300 400 500 600 700 800 900 C CC CCC CD D DC DCC DCCC CM 1000 2000 3000 M MM MMM 罗马数字采用加法规则，但某些情况下需要减法规则，例如：\n加法规则：VI = 5 + 1 = 6，LXX = 50 + 10 + 10 = 70 减法规则：IV = 5 - 1 = 4，IX = 10 - 1 = 9 映射数值：\n使用哈希表将罗马字符与对应数值建立映射关系，便于快速查找 遍历字符串：\n加法逻辑： 如果当前字符的数值大于等于后一个字符的数值，直接将当前字符的数值加到结果中 减法逻辑： 如果当前字符的数值小于后一个字符的数值，则根据减法规则，将当前字符的数值从结果中减去。 边界处理：\n遍历时检查 i + 1 是否越界，确保安全访问下一个字符 返回结果：\n遍历完成后，累加结果即为罗马数字对应的整数 时间复杂度： 遍历字符串一次，查找哈希表的时间复杂度为 O(1)，因此整体为 O(n)，其中 n 是字符串的长度\n空间复杂度 空间复杂度为 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public: int romanToInt(string s) { // 哈希表：罗马字符到数值的映射 std::unordered_map\u0026lt;char, int\u0026gt; hash; hash[\u0026#39;I\u0026#39;] = 1; hash[\u0026#39;V\u0026#39;] = 5; hash[\u0026#39;X\u0026#39;] = 10; hash[\u0026#39;L\u0026#39;] = 50; hash[\u0026#39;C\u0026#39;] = 100; hash[\u0026#39;D\u0026#39;] = 500; hash[\u0026#39;M\u0026#39;] = 1000; int res = 0; // 存储最终结果 for (int i = 0; i \u0026lt; s.size(); ++i) { // 判断是否满足减法规则 if (i + 1 \u0026lt; s.size() \u0026amp;\u0026amp; hash[s[i]] \u0026lt; hash[s[i + 1]]) res -= hash[s[i]]; // 减去当前字符的数值 else res += hash[s[i]]; // 加上当前字符的数值 } return res; // 返回结果 } }; ","date":"2024-03-15T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/roman-to-integer/","title":"Roman to Integer"},{"content":"12. Integer to Roman 分析 1 2 3 4 5 6 7 8 9 I II III IV V VI VII VIII IX 10 20 30 40 50 60 70 80 90 X XX XXX XL L LX LXX LXXX XL 100 200 300 400 500 600 700 800 900 C CC CCC CD D DC DCC DCCC CM 1000 2000 3000 M MM MMM 1234 转换成罗马数字是 MCCXXXIV，也就是从千位到个位依次拼接，用代码将过程模拟出来即可\n分解数值： 定义一个从大到小排列的数值数组 values，以及对应的罗马数字字符串数组 romans 数值包括基本符号（如 1000, 500）及特殊减法组合（如 900, 400） 逐步转换： 遍历数值数组 values，对于每个值，检查当前数字 num 是否大于等于该值 若是，则从 num 中减去该值，同时将对应的罗马数字添加到结果字符串中 重复上述步骤，直到 num 为 0 输出结果： 返回拼接的罗马数字字符串 时间复杂度 常数时间复杂度 O(1)\n空间复杂度 常数空间复杂度 O(1)\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public: string intToRoman(int num) { // 定义数值和对应的罗马数字 std::vector\u0026lt;int\u0026gt; values = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; std::vector\u0026lt;std::string\u0026gt; romans = { \u0026#34;M\u0026#34;, \u0026#34;CM\u0026#34;, \u0026#34;D\u0026#34;, \u0026#34;CD\u0026#34;, \u0026#34;C\u0026#34;, \u0026#34;XC\u0026#34;, \u0026#34;L\u0026#34;, \u0026#34;XL\u0026#34;, \u0026#34;X\u0026#34;, \u0026#34;IX\u0026#34;, \u0026#34;V\u0026#34;, \u0026#34;IV\u0026#34;, \u0026#34;I\u0026#34; }; std::string res; // 遍历数值数组 for (int i = 0; i \u0026lt; values.size(); ++i) { // 减去当前值，拼接对应罗马数字 while (num \u0026gt;= values[i]) { num -= values[i]; res += romans[i]; } } return res; } }; ","date":"2024-03-14T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/integer-to-roman/","title":"Integer to Roman"},{"content":"11. Container With Most Water 分析 用两个指针i和j分别指向开头和结尾，如果指针i指向的水柱高度较低，则指针i向后移，反之指针j向前移。每移动一次后，求当前指针i和j指向水柱之间的面积，更新一下最大值。\n为什么更新出来的值一定是最优解。 i和j最开始在两侧，每次把一个指针向中间靠拢，则一定有一侧指针会先到最优解的位置。假定指针i先到最优解位置，则指针j一定在其最优解位置的右边，并且指针j在到达最优解位置前每次指向的水柱高度严格小于最优解位置的高度。\n可以使用反证法证明。如果指针j当前指向的水柱高度大于等于其最优解位置的高度，则能装的水量一定大于最优解，就和最优解位置矛盾。因此，通过上述过程，一定可以遍历出最优解。\nC++代码 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public: int maxArea(vector\u0026lt;int\u0026gt;\u0026amp; height) { int res = 0; for (int i = 0, j = height.size() - 1; i \u0026lt; j; ) { res = max(res, min(height[i], height[j]) * (j - i)); if (height[i] \u0026lt; height[j]) i ++ ; else j -- ; } return res; } }; ","date":"2024-03-13T00:00:00Z","image":"https://blog.hangzhang.cv/leetcode.png","permalink":"https://blog.hangzhang.cv/p/container-with-most-water/","title":"Container With Most Water"},{"content":"Similar Vocabulary high \u0026amp; tall high for things that are wider than their vertical height Sherlock Holmes fell from the top of a high building. I put the books on a high shelf. tall for things that are narrower than their vertical height Burj Khalifa is the tallest building in the world. My brother is six foot tall. big \u0026amp; large big for real and abstract things big city big house big decision big ideas large for real things with a more formal tone a large population a large number of projects a large amount of money Comparatives and Superlatives adjectives Comparatives Comparative adjectives tell us how something is different (the original smaller home of the BBC) or how something has changed (It\u0026rsquo;s busier than ever).\nHow do we form comparative adjectives?\nIn most cases, we add –er to the adjective to form a comparative (large - larger, fast - faster). If the adjective finishes with a consonant-vowel-consonant pattern (like ‘big’), the final consonant is doubled when –er is added (big - bigger, fat - fatter). If the adjective ends with a consonant + y, we change the y to an i (busy - busier, funny - funnier). use ‘than’ to make a direct comparison with something else (I am taller than my brother.) Superlatives Superlative adjectives highlight something that is bigger, better or more than everything else (the largest live newsroom in Europe, the latest technology).\n** How do we form superlative adjectives?**\nwe add –est after the adjective. We also use ‘the’ before the adjective (fast - the fastest, large - the largest). If the adjective finishes with a consonant-vowel-consonant pattern (like ‘big’), the final consonant is doubled (big - the biggest, fat - the fattest). If the adjective ends with a consonant + y, we change the y to an i (busy - the busiest, funny - the funniest). irregular forms adjective comparative superlative good etter (than) (the) best well better (than) (the) best bad worse (than) (the) worst ill worse (than) (the) worst far further (than) (the) furthest Much and more The comparatives and superlatives are different in these sentences. We use ‘more’ before the adjective to make a comparative form and ‘the most’ to make a superlative. because these adjectives have 2 syllables or more and they don’t end in ‘y’:\nThe new Broadcasting House was the most expensive project in BBC history. Having one central location makes the BBC more efficient. The new ‘John Peel Wing’ is named after one of the BBC’s most popular presenters. The new studios are more comfortable than the old ones. When we make comparisons, we sometimes also use ‘much’ or ‘a lot’ before the adjective for emphasis:\nThe new building is much nicer than the old one. I love this new furniture. It is a lot more comfortable than the old stuff. Our studios are much more modern now. My office is a lot bigger than before. It’s great! We do not use ‘much’ or ‘a lot’ with superlatives because they do not need extra emphasis! Comparing nouns When sentences compare nouns, not adjectives. We use ‘more than’ with a noun to mean ‘a greater number than’ and ‘the most’ with a noun to mean ‘the greatest number’.\nThe BBC reaches a worldwide audience of more than 150 million every week. New Broadcasting House has more departments than the old building. BBC One has the most viewers in the UK. We can use much and many for emphasis. And much more before an uncountable noun (like chocolate or money) and many more before a countable noun (like friends or countries).\nI feel sick. I ate much more chocolate than you. I have many more friends in my hometown than I do here. other forms of comparatives and superlatives one of the most \u0026hellip; Broadcasting House is one of the most iconic buildings in London. The BBC news team is one of the fastest in the world. The new ‘John Peel Wing’ is named after one of the BBC’s most popular presenters. \u0026hellip; as adjective as.. use the structure as + adjective + as to describe two things that are the same in some way.\nI am 3 years older than my brother but he is as tall as me. We are both 1m80 tall. Despite many changes over the years, the BBC is still as important as ever in the UK and around the world. All (of) the best it contains all the latest technology. == it is the most advanced studio in the world, not one of the most advanced. Broadcasting House contains all of the BBC\u0026rsquo;s most popular services. == All the best programmes are made in its studios. Even better \u0026ldquo;there are even more exciting things to see in the TV studios.\u0026rdquo; In this example, we use even for emphasis. For instance, we might say:\nThe original Broadcasting House was one of the BBC\u0026rsquo;s most important centres, but New Broadcasting House is even more important than before. The Beatles were one of Britain\u0026rsquo;s most famous bands when they played at Broadcasting House in 1963. However, they got even more popular later that year. Not as adjective as Paul McCartney is not as old as my grandfather. (means the grandfather is more older than Paul McCartney) Old Broadcasting House was not as modern as New Broadcasting House. (means New Broadcasting House is newer than the old building) ","date":"2023-09-07T00:00:00Z","image":"https://blog.hangzhang.cv/english-language.jpeg","permalink":"https://blog.hangzhang.cv/p/comparative-and-superlative/","title":"Comparative and Superlative"}]