From 78700485ecc3645b0fb8e36615a3f3b3233c3232 Mon Sep 17 00:00:00 2001 From: realDuang <250407778@qq.com> Date: Sat, 13 Jun 2026 18:22:52 +0800 Subject: [PATCH 1/3] feat(solutions): add LC 2196/763 and refine several solutions - Add 2196 create binary tree from descriptions - Add 763 partition labels - Refine 42 trap (monotonic stack), 22 generate parentheses, 79 word search, 49 group anagrams, 239 sliding window maximum --- ...72\344\272\214\345\217\211\346\240\221.ts" | 125 ++++++++++++++++++ ...2.\346\216\245\351\233\250\346\260\264.ts" | 83 +++++++++--- ...54\345\217\267\347\224\237\346\210\220.ts" | 36 ++++- ...25\350\257\215\346\220\234\347\264\242.ts" | 68 +++++++--- ...15\350\257\215\345\210\206\347\273\204.ts" | 33 ++--- ...43\346\234\200\345\244\247\345\200\274.ts" | 43 +++--- ...27\346\257\215\345\214\272\351\227\264.ts" | 92 +++++++++++++ 7 files changed, 400 insertions(+), 80 deletions(-) create mode 100644 "src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" create mode 100644 "src/string/763.\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.ts" diff --git "a/src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" "b/src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" new file mode 100644 index 000000000..4727ea639 --- /dev/null +++ "b/src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" @@ -0,0 +1,125 @@ +/* + * @lc app=leetcode.cn id=2196 lang=typescript + * + * [2196] 根据描述创建二叉树 + * + * https://leetcode.cn/problems/create-binary-tree-from-descriptions/description/ + * + * algorithms + * Medium (73.68%) + * Likes: 68 + * Dislikes: 0 + * Total Accepted: 16.1K + * Total Submissions: 21.2K + * Testcase Example: '[[20,15,1],[20,17,0],[50,20,1],[50,80,0],[80,19,1]]' + * + * 给你一个二维整数数组 descriptions ,其中 descriptions[i] = [parenti, childi, isLefti] 表示 + * parenti 是 childi 在 二叉树 中的 父节点,二叉树中各节点的值 互不相同 。此外: + * + * + * 如果 isLefti == 1 ,那么 childi 就是 parenti 的左子节点。 + * 如果 isLefti == 0 ,那么 childi 就是 parenti 的右子节点。 + * + * + * 请你根据 descriptions 的描述来构造二叉树并返回其 根节点 。 + * + * 测试用例会保证可以构造出 有效 的二叉树。 + * + * + * + * 示例 1: + * + * + * + * + * 输入:descriptions = [[20,15,1],[20,17,0],[50,20,1],[50,80,0],[80,19,1]] + * 输出:[50,20,80,15,17,19] + * 解释:根节点是值为 50 的节点,因为它没有父节点。 + * 结果二叉树如上图所示。 + * + * + * 示例 2: + * + * + * + * + * 输入:descriptions = [[1,2,1],[2,3,0],[3,4,1]] + * 输出:[1,2,null,null,3,4] + * 解释:根节点是值为 1 的节点,因为它没有父节点。 + * 结果二叉树如上图所示。 + * + * + * + * 提示: + * + * + * 1 <= descriptions.length <= 10^4 + * descriptions[i].length == 3 + * 1 <= parenti, childi <= 10^5 + * 0 <= isLefti <= 1 + * descriptions 所描述的二叉树是一棵有效二叉树 + * + * + */ + +// @lc code=start +/** + * Definition for a binary tree node. + * class TreeNode { + * val: number + * left: TreeNode | null + * right: TreeNode | null + * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + * } + */ + +function createBinaryTree(descriptions: number[][]): TreeNode | null { + const nodeMap: Map = new Map(); + const parents = new Set(); + const childs = new Set(); + + for (let i = 0; i < descriptions.length; i++) { + const [parent, child, isLeft] = descriptions[i]; + + parents.add(parent); + childs.add(child); + + if (!nodeMap.has(parent)) { + nodeMap.set(parent, new TreeNode(parent)); + } + if (!nodeMap.has(child)) { + nodeMap.set(child, new TreeNode(child)); + } + const parentNode = nodeMap.get(parent); + const childNode = nodeMap.get(child); + if (isLeft) { + parentNode.left = childNode; + } else { + parentNode.right = childNode; + } + } + + for (const p of parents) { + if (!childs.has(p)) { + return nodeMap.get(p)!; + } + } + return null; +} + +function normalizeTreeOutput(root: TreeNode | null): Array { + return JSON.parse(Tree.serialize(root)).map((value: number | string | null) => + value === 'null' || value === null ? null : value + ); +} +// @lc code=end + +(() => { + LCT.func(createBinaryTree).auto({ + output: normalizeTreeOutput + }); +})(); diff --git "a/src/array/42.\346\216\245\351\233\250\346\260\264.ts" "b/src/array/42.\346\216\245\351\233\250\346\260\264.ts" index 7ef2ecfec..435c7b516 100644 --- "a/src/array/42.\346\216\245\351\233\250\346\260\264.ts" +++ "b/src/array/42.\346\216\245\351\233\250\346\260\264.ts" @@ -48,30 +48,23 @@ // @lc code=start function trap(height: number[]): number { - const len = height.length; - const lMax = Array(len).fill(0); - const rMax = Array(len).fill(0); + let res = 0; + const stack: number[] = []; - // base case - lMax[0] = height[0]; - rMax[len - 1] = height[len - 1]; + for (let i = 0; i < height.length; i++) { + while (stack.length && height[stack[stack.length - 1]] < height[i]) { + const cur = stack.pop(); + if (!stack.length) break; + const l = stack[stack.length - 1]; + const r = i; + const h = Math.min(height[l], height[r]) - height[cur]; + res += (r - l - 1) * h; + } - // Get left max height and right max height for each one - for (let i = 1; i < len; i++) { - lMax[i] = Math.max(height[i], lMax[i - 1]); - } - for (let i = len - 2; i >= 0; i--) { - rMax[i] = Math.max(height[i], rMax[i + 1]); - } - - // Calculate - let sum = 0; - // exclude both sides - for (let i = 1; i < len - 1; i++) { - sum += Math.min(lMax[i], rMax[i]) - height[i]; + stack.push(i); } - return sum; + return res; } // @lc code=end @@ -79,3 +72,53 @@ function trap(height: number[]): number { const height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]; console.log(trap(height)); })(); + +// function trap(height: number[]): number { +// let res = 0; +// let l = 0; +// let r = height.length - 1; + +// let lMax = 0; +// let rMax = 0; + +// while (l < r) { +// lMax = Math.max(lMax, height[l]); +// rMax = Math.max(rMax, height[r]); + +// if (lMax < rMax) { +// res += lMax - height[l]; +// l++; +// } else { +// res += rMax - height[r]; +// r--; +// } +// } +// return res; +// } + +// function trap(height: number[]): number { +// const len = height.length; +// const lMax = Array(len).fill(0); +// const rMax = Array(len).fill(0); + +// // base case +// lMax[0] = height[0]; +// rMax[len - 1] = height[len - 1]; + +// // Get left max height and right max height for each one +// for (let i = 1; i < len; i++) { +// lMax[i] = Math.max(height[i], lMax[i - 1]); +// } +// for (let i = len - 2; i >= 0; i--) { +// rMax[i] = Math.max(height[i], rMax[i + 1]); +// } + +// // Calculate +// let sum = 0; +// // exclude both sides +// for (let i = 1; i < len - 1; i++) { +// sum += Math.min(lMax[i], rMax[i]) - height[i]; +// } + +// return sum; +// } diff --git "a/src/backtracking/22.\346\213\254\345\217\267\347\224\237\346\210\220.ts" "b/src/backtracking/22.\346\213\254\345\217\267\347\224\237\346\210\220.ts" index 1e1c7d8bc..582dfcbe9 100644 --- "a/src/backtracking/22.\346\213\254\345\217\267\347\224\237\346\210\220.ts" +++ "b/src/backtracking/22.\346\213\254\345\217\267\347\224\237\346\210\220.ts" @@ -47,15 +47,21 @@ function generateParenthesis(n: number): string[] { backtrack('', 0, 0); return res; - function backtrack(str: string, left: number, right: number) { - if (left === n && right === n) { + // str存已放入的括号串,l, r 表示已经放入左括号和右括号的数量 + function backtrack(str: string, l: number, r: number) { + if (l === n && r === n) { res.push(str); + return; } - if (left < n) { - backtrack(str + '(', left + 1, right); + + // 选择放左括号,条件是还剩左括号可以放 + if (l < n) { + backtrack(str + '(', l + 1, r); } - if (right < left) { - backtrack(str + ')', left, right + 1); + // 选择放右括号, 条件是还剩右括号可以放且子串里已经有多余的左括号了(即右括号比左括号少) + // r Array(n).fill(0)); @@ -74,36 +73,27 @@ function exist(board: string[][], word: string): boolean { for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { const flag = backtrack(i, j, 0); - // 剪枝:当找到了一次匹配时,不继续进行遍历了 if (flag) return true; } } return false; function backtrack(i: number, j: number, index: number): boolean { - // 剪枝:数组越界或者被访问过,直接 return if (i < 0 || i >= m || j < 0 || j >= n || visited[i][j] !== 0) return false; - // 剪枝:当前节点与目标不匹配,直接 return + if (board[i][j] !== word[index]) return false; - // 已经比对到 word 最后一个字符,且当前节点与目标匹配,则说明找到了答案,直接返回true。 - if (index === wordLen - 1) { - return true; - } + if (index === word.length - 1) return true; // 做选择 visited[i][j] = 1; - // 递归 - // 剪枝:当发现其中一条路径已经走通,则不进行下面的递归了。 - const result = - backtrack(i + 1, j, index + 1) || - backtrack(i - 1, j, index + 1) || + const res = backtrack(i, j + 1, index + 1) || - backtrack(i, j - 1, index + 1); - // 撤销选择 + backtrack(i, j - 1, index + 1) || + backtrack(i + 1, j, index + 1) || + backtrack(i - 1, j, index + 1); visited[i][j] = 0; - - return result; + return res; } } // @lc code=end @@ -117,3 +107,47 @@ function exist(board: string[][], word: string): boolean { word = 'ABCB'; console.log(exist(board, word)); })(); + +// function exist(board: string[][], word: string): boolean { +// const m = board.length; +// const n = board[0].length; +// const wordLen = word.length; +// const visited: number[][] = Array(m) +// .fill(0) +// .map(x => Array(n).fill(0)); + +// for (let i = 0; i < m; i++) { +// for (let j = 0; j < n; j++) { +// const flag = backtrack(i, j, 0); +// // 剪枝:当找到了一次匹配时,不继续进行遍历了 +// if (flag) return true; +// } +// } +// return false; + +// function backtrack(i: number, j: number, index: number): boolean { +// // 剪枝:数组越界或者被访问过,直接 return +// if (i < 0 || i >= m || j < 0 || j >= n || visited[i][j] !== 0) return false; +// // 剪枝:当前节点与目标不匹配,直接 return +// if (board[i][j] !== word[index]) return false; + +// // 已经比对到 word 最后一个字符,且当前节点与目标匹配,则说明找到了答案,直接返回true。 +// if (index === wordLen - 1) { +// return true; +// } + +// // 做选择 +// visited[i][j] = 1; +// // 递归 +// // 剪枝:当发现其中一条路径已经走通,则不进行下面的递归了。 +// const result = +// backtrack(i + 1, j, index + 1) || +// backtrack(i - 1, j, index + 1) || +// backtrack(i, j + 1, index + 1) || +// backtrack(i, j - 1, index + 1); +// // 撤销选择 +// visited[i][j] = 0; + +// return result; +// } +// } diff --git "a/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" "b/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" index 82707f2a1..6e123f7e2 100644 --- "a/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" +++ "b/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" @@ -52,38 +52,27 @@ // @lc code=start function groupAnagrams(strs: string[]): string[][] { - const res: string[][] = []; - const hashMap: string[][] = []; + const map: Map = new Map(); for (const str of strs) { const temp = Array(26).fill(0); - for (let i = 0; i < str.length; i++) { - const index = str.charCodeAt(i) - 97; + for (const ch of str) { + const index = ch.charCodeAt(0) - 97; temp[index] += 1; } - - let noMatch = true; - for (let i = 0; i < hashMap.length; i++) { - const isSame = hashMap[i].every((val, index) => val === temp[index]); - if (isSame) { - res[i].push(str); - noMatch = false; - break; - } - } - - if (noMatch) { - hashMap.push(temp); - res.push([str]); + const key = temp.join(','); + const group = map.get(key); + if (group) { + group.push(str); + } else { + map.set(key, [str]); } } - return res; + return Array.from(map.values()); } // @lc code=end (() => { - const strs = ['eat', 'tea', 'tan', 'ate', 'nat', 'bat']; - console.log(groupAnagrams(strs)); - console.log(groupAnagrams([''])); + LCT.func(groupAnagrams).auto({ unordered: true }); })(); diff --git "a/src/sliding-window/239.\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.ts" "b/src/sliding-window/239.\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.ts" index a0b1716ea..dd2531abc 100644 --- "a/src/sliding-window/239.\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.ts" +++ "b/src/sliding-window/239.\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.ts" @@ -98,25 +98,38 @@ function maxSlidingWindow(nums: number[], k: number): number[] { ]); })(); -// let l = 0; -// let r = 0; -// while (r < nums.length) { -// while (queue.length > 0 && nums[r] > queue[queue.length - 1]) { -// queue.pop(); +// 另一种实现:单调队列存值(先填满窗口,再逐个滑动) +// function maxSlidingWindow(nums: number[], k: number): number[] { +// if (nums.length == 0 || k == 0) return []; +// const dequeue = []; +// const res: number[] = []; +// // 初始化窗口 +// for (let i = 0; i < k; i++) { +// // 保持单调队列的单调递减 +// while (dequeue.length !== 0 && dequeue[dequeue.length - 1] < nums[i]) { +// dequeue.pop(); // } -// queue.push(nums[r]); -// r++; +// // 加入窗口右边界的值 +// dequeue.push(nums[i]); +// } +// // 由于滑动窗口中元素单调递减,队头一定是当前滑动窗口的最大值 +// res.push(dequeue[0]); -// // 窗口满足要求 -// if (r - l >= k) { -// res.push(queue[0]); +// for (let i = k; i < nums.length; i++) { +// // i - k 为窗口的左边界 +// const left = i - k; +// // 如果这个元素在单调队列中,则直接删除 +// if (dequeue[0] === nums[left]) { +// dequeue.shift(); +// } -// // 收缩左侧 -// if (nums[l] === queue[0]) { -// queue.shift(); -// } -// l++; +// // 保持单调队列的单调递减 +// while (dequeue.length !== 0 && dequeue[dequeue.length - 1] < nums[i]) { +// dequeue.pop(); // } +// dequeue.push(nums[i]); + +// res.push(dequeue[0]); // } // return res; // } diff --git "a/src/string/763.\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.ts" "b/src/string/763.\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.ts" new file mode 100644 index 000000000..3ac329b8b --- /dev/null +++ "b/src/string/763.\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.ts" @@ -0,0 +1,92 @@ +/* + * @lc app=leetcode.cn id=763 lang=typescript + * + * [763] 划分字母区间 + * + * https://leetcode.cn/problems/partition-labels/description/ + * + * algorithms + * Medium (79.03%) + * Likes: 1424 + * Dislikes: 0 + * Total Accepted: 521.9K + * Total Submissions: 660.5K + * Testcase Example: '"ababcbacadefegdehijhklij"' + * + * 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", + * "cc"],但类似 ["aba", "bcc"] 或 ["ab", "ab", "cc"] 的划分是非法的。 + * + * 注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。 + * + * 返回一个表示每个字符串片段的长度的列表。 + * + * + * 示例 1: + * + * + * 输入:s = "ababcbacadefegdehijhklij" + * 输出:[9,7,8] + * 解释: + * 划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。 + * 每个字母最多出现在一个片段中。 + * 像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。 + * + * 示例 2: + * + * + * 输入:s = "eccbbbbdec" + * 输出:[10] + * + * + * + * + * 提示: + * + * + * 1 <= s.length <= 500 + * s 仅由小写英文字母组成 + * + * + */ + +// @lc code=start +function partitionLabels(s: string): number[] { + const charMap: Map = new Map(); + const res: number[] = []; + + for (let i = 0; i < s.length; i++) { + const ch = s[i]; + if (!charMap.get(ch)) { + charMap.set(ch, []); + } + charMap.get(ch)!.push(i); + } + + const charSet = Array.from(charMap.values()); + + let begin = 0; + let end = 0; + for (const set of charSet) { + const left = set[0]; + const right = set[set.length - 1]; + + // 与当前区间有交集则区间取并集 + if (left <= end) { + end = Math.max(end, right); + } else { + // 与当前集合没有交集,则旧区间计算长度,并新开区间 + res.push(end - begin + 1); + begin = left; + end = right; + } + } + + res.push(end - begin + 1); + + return res; +} +// @lc code=end + +(() => { + LCT.func(partitionLabels).auto(); +})(); From 2e289b6d8284f3a416f46dba242b7e0fa1db91e1 Mon Sep 17 00:00:00 2001 From: realDuang <250407778@qq.com> Date: Sat, 13 Jun 2026 18:22:57 +0800 Subject: [PATCH 2/3] fix(docs): correct topic nav link to avoid 404 Topic nav pointed to /docs/topic/0.introduction but the file is introduction.md, causing a 404. --- docs/.vitepress/config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 3658b99ac..79899da18 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -20,7 +20,7 @@ export default defineConfig({ nav: [ { text: '首页', link: '/' }, { text: '📖 题解', link: '/docs/list/array/1.two-sum' }, - { text: '📖 专题', link: '/docs/topic/0.introduction' } + { text: '📖 专题', link: '/docs/topic/introduction' } ], sidebar, From 9e776367acf5937d5b9bbb145c93ff1cea885cfb Mon Sep 17 00:00:00 2001 From: realDuang <250407778@qq.com> Date: Sat, 13 Jun 2026 18:32:08 +0800 Subject: [PATCH 3/3] fix(solutions): use judge option instead of unsupported LCTOptions fields 2196 and 49 referenced 'output'/'unordered' options that don't exist in LCTOptions, breaking typecheck and tests. Use the supported 'judge' option for custom comparison (tree serialization / unordered group matching). --- ...273\272\344\272\214\345\217\211\346\240\221.ts" | 14 +++++++------- ...275\215\350\257\215\345\210\206\347\273\204.ts" | 8 +++++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git "a/src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" "b/src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" index 4727ea639..62c194650 100644 --- "a/src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" +++ "b/src/Unknown/2196.\346\240\271\346\215\256\346\217\217\350\277\260\345\210\233\345\273\272\344\272\214\345\217\211\346\240\221.ts" @@ -110,16 +110,16 @@ function createBinaryTree(descriptions: number[][]): TreeNode | null { } return null; } - -function normalizeTreeOutput(root: TreeNode | null): Array { - return JSON.parse(Tree.serialize(root)).map((value: number | string | null) => - value === 'null' || value === null ? null : value - ); -} // @lc code=end (() => { + const normalize = (root: TreeNode | null): Array => + JSON.parse(Tree.serialize(root)).map((value: number | string | null) => + value === 'null' || value === null ? null : value + ); + LCT.func(createBinaryTree).auto({ - output: normalizeTreeOutput + judge: (actual: TreeNode | null, expected: Array) => + JSON.stringify(normalize(actual)) === JSON.stringify(expected) }); })(); diff --git "a/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" "b/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" index 6e123f7e2..e5683d7c8 100644 --- "a/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" +++ "b/src/hash-table/49.\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.ts" @@ -74,5 +74,11 @@ function groupAnagrams(strs: string[]): string[][] { // @lc code=end (() => { - LCT.func(groupAnagrams).auto({ unordered: true }); + const normalize = (groups: string[][]): string[][] => + groups.map(group => [...group].sort()).sort((a, b) => (a.join(',') < b.join(',') ? -1 : 1)); + + LCT.func(groupAnagrams).auto({ + judge: (actual: string[][], expected: string[][]) => + JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)) + }); })();