From 2ee2823f0b36c89db53177441498bdde3f79ad27 Mon Sep 17 00:00:00 2001 From: realDuang <250407778@qq.com> Date: Sat, 27 Jun 2026 23:54:59 +0800 Subject: [PATCH] feat: add new leetcode solutions and utility helpers --- ...60\346\234\200\345\244\247\345\222\214.ts" | 80 +++++++++ ...64\347\232\204\351\207\215\351\207\217.ts" | 88 +++++++++ ...347\232\204\351\207\215\351\207\217-ii.ts" | 105 +++++++++++ ...27\351\251\254\346\225\260\345\255\227.ts" | 168 ++++++++++++++++++ ...25\350\257\215\346\213\206\345\210\206.ts" | 55 ++++++ ...11\346\225\260\344\271\213\345\222\214.ts" | 79 ++++++++ ...72\345\212\233\351\227\256\351\242\230.ts" | 88 +++++++++ ...04\347\232\204\344\271\230\347\247\257.ts" | 20 +++ ...43\346\234\200\345\244\247\345\200\274.ts" | 90 ++++++++++ ...73\350\275\254\351\223\276\350\241\250.ts" | 70 ++++++++ ...60\346\234\272\345\231\250\344\272\272.ts" | 62 +++++++ ...36\345\255\220\345\272\217\345\210\227.ts" | 16 ++ ...2.\346\216\245\351\233\250\346\260\264.ts" | 48 +++++ ...6.\345\205\250\346\216\222\345\210\227.ts" | 86 +++++++++ ...53\346\211\213\347\273\255\350\264\271.ts" | 78 ++++++++ ...06\347\233\226\345\255\220\344\270\262.ts" | 57 ++++++ ...60\346\234\272\345\231\250\344\272\272.ts" | 143 +++++++++++++++ ...50\345\271\263\346\226\271\346\225\260.ts" | 81 +++++---- src/test.ts | 0 src/utils/global-utils.d.ts | 2 + src/utils/lct.ts | 17 +- 21 files changed, 1399 insertions(+), 34 deletions(-) create mode 100644 "src/1043.\345\210\206\351\232\224\346\225\260\347\273\204\344\273\245\345\276\227\345\210\260\346\234\200\345\244\247\345\222\214.ts" create mode 100644 "src/1046.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217.ts" create mode 100644 "src/1049.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217-ii.ts" create mode 100644 "src/12.\346\225\264\346\225\260\350\275\254\347\275\227\351\251\254\346\225\260\345\255\227.ts" create mode 100644 "src/139.\345\215\225\350\257\215\346\213\206\345\210\206.ts" create mode 100644 "src/16.\346\234\200\346\216\245\350\277\221\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.ts" create mode 100644 "src/2140.\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.ts" create mode 100644 "src/238.\351\231\244\344\272\206\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.ts" create mode 100644 "src/239.\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.ts" create mode 100644 "src/25.K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.ts" create mode 100644 "src/2874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" create mode 100644 "src/300.\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.ts" create mode 100644 "src/42.\346\216\245\351\233\250\346\260\264.ts" create mode 100644 "src/46.\345\205\250\346\216\222\345\210\227.ts" create mode 100644 "src/714.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.ts" create mode 100644 "src/76.\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.ts" create mode 100644 "src/874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" create mode 100644 src/test.ts diff --git "a/src/1043.\345\210\206\351\232\224\346\225\260\347\273\204\344\273\245\345\276\227\345\210\260\346\234\200\345\244\247\345\222\214.ts" "b/src/1043.\345\210\206\351\232\224\346\225\260\347\273\204\344\273\245\345\276\227\345\210\260\346\234\200\345\244\247\345\222\214.ts" new file mode 100644 index 00000000..b534d359 --- /dev/null +++ "b/src/1043.\345\210\206\351\232\224\346\225\260\347\273\204\344\273\245\345\276\227\345\210\260\346\234\200\345\244\247\345\222\214.ts" @@ -0,0 +1,80 @@ +/* + * @lc app=leetcode.cn id=1043 lang=typescript + * + * [1043] 分隔数组以得到最大和 + * + * https://leetcode.cn/problems/partition-array-for-maximum-sum/description/ + * + * algorithms + * Medium (75.56%) + * Likes: 327 + * Dislikes: 0 + * Total Accepted: 40.1K + * Total Submissions: 53.1K + * Testcase Example: '[1,15,7,9,2,5,10]\n3' + * + * 给你一个整数数组 arr,请你将该数组分隔为长度 最多 为 k 的一些(连续)子数组。分隔完成后,每个子数组的中的所有值都会变为该子数组中的最大值。 + * + * 返回将数组分隔变换后能够得到的元素最大和。本题所用到的测试用例会确保答案是一个 32 位整数。 + * + * + * + * 示例 1: + * + * + * 输入:arr = [1,15,7,9,2,5,10], k = 3 + * 输出:84 + * 解释:数组变为 [15,15,15,9,10,10,10] + * + * 示例 2: + * + * + * 输入:arr = [1,4,1,5,7,3,6,1,9,9,3], k = 4 + * 输出:83 + * + * + * 示例 3: + * + * + * 输入:arr = [1], k = 1 + * 输出:1 + * + * + * + * + * 提示: + * + * + * 1 <= arr.length <= 500 + * 0 <= arr[i] <= 10^9 + * 1 <= k <= arr.length + * + * + */ + +// @lc code=start +function maxSumAfterPartitioning(arr: number[], k: number): number { + const dp: number[] = Array(arr.length).fill(0); + dp[0] = arr[0]; + + let cur = arr[0]; + for (let i = 1; i < arr.length; i++) { + if (i < k) { + cur = Math.max(cur, arr[i]); + dp[i] = cur * (i + 1); + } else { + cur = arr[i]; + for (let j = 1; j <= k; j++) { + dp[i] = Math.max(dp[i], dp[i - j] + cur * j); + cur = Math.max(cur, arr[i - j]); + } + } + } + + return dp[arr.length - 1]; +} +// @lc code=end + +(() => { + LCT.func(maxSumAfterPartitioning).auto(); +})(); diff --git "a/src/1046.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217.ts" "b/src/1046.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217.ts" new file mode 100644 index 00000000..5846d75b --- /dev/null +++ "b/src/1046.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217.ts" @@ -0,0 +1,88 @@ +/* + * @lc app=leetcode.cn id=1046 lang=typescript + * + * [1046] 最后一块石头的重量 + * + * https://leetcode.cn/problems/last-stone-weight/description/ + * + * algorithms + * Easy (65.62%) + * Likes: 350 + * Dislikes: 0 + * Total Accepted: 129.1K + * Total Submissions: 196.5K + * Testcase Example: '[2,7,4,1,8,1]' + * + * 有一堆石头,每块石头的重量都是正整数。 + * + * 每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x 。那么粉碎的可能结果如下: + * + * + * 如果 x == y,那么两块石头都会被完全粉碎; + * 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 + * + * + * 最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。 + * + * + * + * 示例: + * + * + * 输入:[2,7,4,1,8,1] + * 输出:1 + * 解释: + * 先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1], + * 再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1], + * 接着是 2 和 1,得到 1,所以数组转换为 [1,1,1], + * 最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。 + * + * + * + * 提示: + * + * + * 1 + * 1 + * + * + */ + +// @lc code=start +function lastStoneWeight(stones: number[]): number { + stones.sort((a, b) => a - b); + + while (stones.length > 1) { + const x = stones.pop(); + const y = stones.pop(); + + const temp = Math.abs(x - y); + if (temp === 0) continue; + + const len = stones.length; + if (len === 0) { + stones.push(temp); + break; + } + + if (temp >= stones[len - 1]) { + stones.push(temp); + continue; + } + for (let i = 0; i < len; i++) { + if (temp < stones[i]) { + stones.splice(i, 0, temp); + break; + } + } + } + + return stones.length ? stones[0] : 0; +} +// @lc code=end + +// @lc code=end + +(() => { + LCT.func(lastStoneWeight).auto(); +})(); diff --git "a/src/1049.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217-ii.ts" "b/src/1049.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217-ii.ts" new file mode 100644 index 00000000..bb73b741 --- /dev/null +++ "b/src/1049.\346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217-ii.ts" @@ -0,0 +1,105 @@ +/* + * @lc app=leetcode.cn id=1049 lang=typescript + * + * [1049] 最后一块石头的重量 II + * + * https://leetcode.cn/problems/last-stone-weight-ii/description/ + * + * algorithms + * Medium (71.97%) + * Likes: 1059 + * Dislikes: 0 + * Total Accepted: 270K + * Total Submissions: 375.4K + * Testcase Example: '[2,7,4,1,8,1]' + * + * 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 + * + * 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下: + * + * + * 如果 x == y,那么两块石头都会被完全粉碎; + * 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 + * + * + * 最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。 + * + * + * + * 示例 1: + * + * + * 输入:stones = [2,7,4,1,8,1] + * 输出:1 + * 解释: + * 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], + * 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], + * 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], + * 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。 + * + * + * 示例 2: + * + * + * 输入:stones = [31,26,33,21,40] + * 输出:5 + * + * + * + * + * 提示: + * + * + * 1 <= stones.length <= 30 + * 1 <= stones[i] <= 100 + * + * + */ + +// @lc code=start +function lastStoneWeightII(stones: number[]): number { + // 本质上是目标和,01背包问题,选出部分和接近但不超过 sum/2 的部分 A,这样可以保证两者差B-A最小 + const sum = stones.reduce((a, b) => a + b, 0); + const target = Math.floor(sum / 2); + + // dp[i] 为是否可以选出总和容量为 i 的选择 + const dp = Array(target + 1).fill(false); + dp[0] = true; + + // 01背包问题,先遍历选择,内部再倒序遍历背包容量 + for (const weight of stones) { + for (let j = target; j >= weight; j--) { + // 如果容量为 j 时选当前石头,则 j - weight 容量也需要可以被其他石头组成 + dp[j] = dp[j] || dp[j - weight]; + } + } + + // 设选出的选出的容量为 A,则另一部分 B = sum - A + // 则两者差为 B - A = sum - A - A = sum - 2*A + // 那么最小差即为最接近 sum/2 的,且 dp[i] = true 的组合 + let k = target; + while (k >= 0) { + if (dp[k]) { + return sum - 2 * k; + } + k--; + } + + // 上述一定可以出结果的(因为k=0时 dp[0] = true),下面这行其实永远不会走到 + return -1; +} +// @lc code=end + +(() => { + LCT.func(lastStoneWeightII).auto(); + LCT.func(lastStoneWeightII).cases([ + { + input: [[2, 7, 4, 1, 8, 1]], + output: 1 + }, + { + input: [[31, 26, 33, 21, 40]], + output: 5 + } + ]); +})(); diff --git "a/src/12.\346\225\264\346\225\260\350\275\254\347\275\227\351\251\254\346\225\260\345\255\227.ts" "b/src/12.\346\225\264\346\225\260\350\275\254\347\275\227\351\251\254\346\225\260\345\255\227.ts" new file mode 100644 index 00000000..3e8f9bd4 --- /dev/null +++ "b/src/12.\346\225\264\346\225\260\350\275\254\347\275\227\351\251\254\346\225\260\345\255\227.ts" @@ -0,0 +1,168 @@ +/* + * @lc app=leetcode.cn id=12 lang=typescript + * + * [12] 整数转罗马数字 + * + * https://leetcode.cn/problems/integer-to-roman/description/ + * + * algorithms + * Medium (69.30%) + * Likes: 1483 + * Dislikes: 0 + * Total Accepted: 626.9K + * Total Submissions: 903.8K + * Testcase Example: '3749' + * + * 七个不同的符号代表罗马数字,其值如下: + * + * + * + * + * 符号 + * 值 + * + * + * + * + * I + * 1 + * + * + * V + * 5 + * + * + * X + * 10 + * + * + * L + * 50 + * + * + * C + * 100 + * + * + * D + * 500 + * + * + * M + * 1000 + * + * + * + * + * 罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则: + * + * + * 如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。 + * 如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (V) 减 1 (I): IV ,9 是 10 (X) 减 + * 1 (I):IX。仅使用以下减法形式:4 (IV),9 (IX),40 (XL),90 (XC),400 (CD) 和 900 (CM)。 + * 只有 10 的次方(I, X, C, M)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V),50 (L) 或 500 + * (D)。如果需要将符号附加4次,请使用 减法形式。 + * + * + * 给定一个整数,将其转换为罗马数字。 + * + * + * + * 示例 1: + * + * + * 输入:num = 3749 + * + * 输出: "MMMDCCXLIX" + * + * 解释: + * + * + * 3000 = MMM 由于 1000 (M) + 1000 (M) + 1000 (M) + * ⁠700 = DCC 由于 500 (D) + 100 (C) + 100 (C) + * ⁠ 40 = XL 由于 50 (L) 减 10 (X) + * ⁠ 9 = IX 由于 10 (X) 减 1 (I) + * 注意:49 不是 50 (L) 减 1 (I) 因为转换是基于小数位 + * + * + * + * 示例 2: + * + * + * 输入:num = 58 + * + * 输出:"LVIII" + * + * 解释: + * + * + * 50 = L + * ⁠8 = VIII + * + * + * + * 示例 3: + * + * + * 输入:num = 1994 + * + * 输出:"MCMXCIV" + * + * 解释: + * + * + * 1000 = M + * ⁠900 = CM + * ⁠ 90 = XC + * ⁠ 4 = IV + * + * + * + * + * + * 提示: + * + * + * 1 <= num <= 3999 + * + * + */ + +// @lc code=start +function intToRoman(num: number): string { + const map: string[][] = [ + ['I', 'IV', 'V', 'IX'], + ['X', 'XL', 'L', 'XC'], + ['C', 'CD', 'D', 'CM'] + ]; + + let res: string = ''; + + if (num >= 1000) { + res += 'M'.repeat(Math.floor(num / 1000)); + } + + let temp = num % 1000; + let ans = 2; + while (ans >= 0) { + const cur = Math.floor(temp / Math.pow(10, ans)); + if (cur > 0) { + if (cur === 4) { + res += map[ans][1]; + } else if (cur === 9) { + res += map[ans][3]; + } else { + if (cur >= 5) res += map[ans][2]; + res += map[ans][0].repeat(cur % 5); + } + } + temp = temp % Math.pow(10, ans); + ans -= 1; + } + return res; +} +// @lc code=end + +(() => { + LCT.func(intToRoman).auto(); +})(); diff --git "a/src/139.\345\215\225\350\257\215\346\213\206\345\210\206.ts" "b/src/139.\345\215\225\350\257\215\346\213\206\345\210\206.ts" new file mode 100644 index 00000000..6a589b99 --- /dev/null +++ "b/src/139.\345\215\225\350\257\215\346\213\206\345\210\206.ts" @@ -0,0 +1,55 @@ +/* + * @lc app=leetcode.cn id=139 lang=typescript + * @lcpr version=30403 + * + * [139] 单词拆分 + */ + +// @lc code=start +function wordBreak(s: string, wordDict: string[]): boolean { + const dp: boolean[] = Array(s.length + 1).fill(false); + dp[0] = true; + + for (let i = 1; i < s.length + 1; i++) { + for (const word of wordDict) { + const wordLen = word.length; + if (i < wordLen) continue; + const postix = s.substring(i - wordLen, i); + if (postix === word && dp[i - wordLen]) { + dp[i] = true; + break; + } + } + } + + return dp[s.length]; +} +// @lc code=end + +(() => { + LCT.func(wordBreak).cases([ + { + input: ['leetcode', ['leet', 'code']], + output: true + }, + { + input: ['catsandog', ['cats', 'dog', 'sand', 'and', 'cat']], + output: false + } + ]); +})(); + +/* +// @lcpr case=start +// "leetcode"\n["leet","code"]\n +// @lcpr case=end + +// @lcpr case=start +// "applepenapple"\n["apple","pen"]\n +// @lcpr case=end + +// @lcpr case=start +// "catsandog"\n["cats","dog","sand","and","cat"]\n +// @lcpr case=end + + */ diff --git "a/src/16.\346\234\200\346\216\245\350\277\221\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.ts" "b/src/16.\346\234\200\346\216\245\350\277\221\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.ts" new file mode 100644 index 00000000..667487ea --- /dev/null +++ "b/src/16.\346\234\200\346\216\245\350\277\221\347\232\204\344\270\211\346\225\260\344\271\213\345\222\214.ts" @@ -0,0 +1,79 @@ +/* + * @lc app=leetcode.cn id=16 lang=typescript + * + * [16] 最接近的三数之和 + * + * https://leetcode.cn/problems/3sum-closest/description/ + * + * algorithms + * Medium (44.85%) + * Likes: 1814 + * Dislikes: 0 + * Total Accepted: 702.8K + * Total Submissions: 1.6M + * Testcase Example: '[-1,2,1,-4]\n1' + * + * 给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个在 不同下标位置 的整数,使它们的和与 target + * 最接近。 + * + * 返回这三个数的和。 + * + * 假定每组输入只存在恰好一个解。 + * + * + * + * 示例 1: + * + * + * 输入:nums = [-1,2,1,-4], target = 1 + * 输出:2 + * 解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2)。 + * + * + * 示例 2: + * + * + * 输入:nums = [0,0,0], target = 1 + * 输出:0 + * 解释:与 target 最接近的和是 0(0 + 0 + 0 = 0)。 + * + * + * + * 提示: + * + * + * 3 <= nums.length <= 1000 + * -1000 <= nums[i] <= 1000 + * -10^4 <= target <= 10^4 + * + * + */ + +// @lc code=start +function threeSumClosest(nums: number[], target: number): number { + nums.sort((a, b) => a - b); + + let res = Infinity; + for (let i = 0; i < nums.length; i++) { + let l = i + 1; + let r = nums.length - 1; + while (l < r) { + const ans = nums[l] + nums[r] + nums[i]; + if(ans===target) return ans; + if (Math.abs(target - ans) < Math.abs(target - res)) { + res = ans; + } + if (ans < target) { + l++; + } else { + r--; + } + } + } + return res; +} +// @lc code=end + +(() => { + LCT.func(threeSumClosest).auto(); +})(); diff --git "a/src/2140.\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.ts" "b/src/2140.\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.ts" new file mode 100644 index 00000000..5be11724 --- /dev/null +++ "b/src/2140.\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.ts" @@ -0,0 +1,88 @@ +/* + * @lc app=leetcode.cn id=2140 lang=typescript + * + * [2140] 解决智力问题 + * + * https://leetcode.cn/problems/solving-questions-with-brainpower/description/ + * + * algorithms + * Medium (52.14%) + * Likes: 230 + * Dislikes: 0 + * Total Accepted: 51.8K + * Total Submissions: 99.1K + * Testcase Example: '[[3,2],[4,3],[4,4],[2,5]]' + * + * 给你一个下标从 0 开始的二维整数数组 questions ,其中 questions[i] = [pointsi, brainpoweri] 。 + * + * 这个数组表示一场考试里的一系列题目,你需要 按顺序 (也就是从问题 0 开始依次解决),针对每个问题选择 解决 或者 跳过 操作。解决问题 i 将让你 + * 获得  pointsi 的分数,但是你将 无法 解决接下来的 brainpoweri 个问题(即只能跳过接下来的 brainpoweri + * 个问题)。如果你跳过问题 i ,你可以对下一个问题决定使用哪种操作。 + * + * + * 比方说,给你 questions = [[3, 2], [4, 3], [4, 4], [2, 5]] : + * + * + * 如果问题 0 被解决了, 那么你可以获得 3 分,但你不能解决问题 1 和 2 。 + * 如果你跳过问题 0 ,且解决问题 1 ,你将获得 4 分但是不能解决问题 2 和 3 。 + * + * + * + * + * 请你返回这场考试里你能获得的 最高 分数。 + * + * + * + * 示例 1: + * + * 输入:questions = [[3,2],[4,3],[4,4],[2,5]] + * 输出:5 + * 解释:解决问题 0 和 3 得到最高分。 + * - 解决问题 0 :获得 3 分,但接下来 2 个问题都不能解决。 + * - 不能解决问题 1 和 2 + * - 解决问题 3 :获得 2 分 + * 总得分为:3 + 2 = 5 。没有别的办法获得 5 分或者多于 5 分。 + * + * + * 示例 2: + * + * 输入:questions = [[1,1],[2,2],[3,3],[4,4],[5,5]] + * 输出:7 + * 解释:解决问题 1 和 4 得到最高分。 + * - 跳过问题 0 + * - 解决问题 1 :获得 2 分,但接下来 2 个问题都不能解决。 + * - 不能解决问题 2 和 3 + * - 解决问题 4 :获得 5 分 + * 总得分为:2 + 5 = 7 。没有别的办法获得 7 分或者多于 7 分。 + * + * + * + * + * 提示: + * + * + * 1 <= questions.length <= 10^5 + * questions[i].length == 2 + * 1 <= pointsi, brainpoweri <= 10^5 + * + * + */ + +// @lc code=start +function mostPoints(questions: number[][]): number { + const len = questions.length; + const dp: number[] = Array(len + 1).fill(0); + + for (let i = len - 1; i >= 0; i--) { + const cold = i + questions[i][1] + 1; + const past = cold >= len ? 0 : dp[cold]; + // 不选择和做选择并等待冷却期 + dp[i] = Math.max(dp[i + 1], past + questions[i][0]); + } + return dp[0]; +} +// @lc code=end + +(() => { + LCT.func(mostPoints).auto(); +})(); diff --git "a/src/238.\351\231\244\344\272\206\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.ts" "b/src/238.\351\231\244\344\272\206\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.ts" new file mode 100644 index 00000000..909b5c48 --- /dev/null +++ "b/src/238.\351\231\244\344\272\206\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.ts" @@ -0,0 +1,20 @@ +function productExceptSelf(nums: number[]): number[] { + const len = nums.length; + + // 先把后缀和算出来 + const res: number[] = Array(len).fill(1); + for (let i = len - 2; i >= 0; i--) { + res[i] = res[i + 1] * nums[i + 1]; + } + // 暂存前缀和 + let prefix = 1; + for (let i = 1; i < len; i++) { + prefix *= nums[i - 1]; + res[i] *= prefix; + } + return res; +} + +(() => { + LCT.func(productExceptSelf).cases([{ input: [[1, 2, 3, 4]], output: [24, 12, 8, 6] }]); +})(); diff --git "a/src/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/239.\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.ts" new file mode 100644 index 00000000..7325d204 --- /dev/null +++ "b/src/239.\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.ts" @@ -0,0 +1,90 @@ +/* + * @lc app=leetcode.cn id=239 lang=typescript + * + * [239] 滑动窗口最大值 + * + * https://leetcode.cn/problems/sliding-window-maximum/description/ + * + * algorithms + * Hard (50.16%) + * Likes: 3467 + * Dislikes: 0 + * Total Accepted: 1.2M + * Total Submissions: 2.3M + * Testcase Example: '[1,3,-1,-3,5,3,6,7]\n3' + * + * 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k + * 个数字。滑动窗口每次只向右移动一位。 + * + * 返回 滑动窗口中的最大值 。 + * + * + * + * 示例 1: + * + * + * 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 + * 输出:[3,3,5,5,6,7] + * 解释: + * 滑动窗口的位置 最大值 + * --------------- ----- + * [1 3 -1] -3 5 3 6 7 3 + * ⁠1 [3 -1 -3] 5 3 6 7 3 + * ⁠1 3 [-1 -3 5] 3 6 7 5 + * ⁠1 3 -1 [-3 5 3] 6 7 5 + * ⁠1 3 -1 -3 [5 3 6] 7 6 + * ⁠1 3 -1 -3 5 [3 6 7] 7 + * + * + * 示例 2: + * + * + * 输入:nums = [1], k = 1 + * 输出:[1] + * + * + * + * + * 提示: + * + * + * 1 <= nums.length <= 10^5 + * -10^4 <= nums[i] <= 10^4 + * 1 <= k <= nums.length + * + * + */ + +// @lc code=start +function maxSlidingWindow(nums: number[], k: number): number[] { + const stack: number[] = []; + const res: number[] = []; + + let i = 0; + for (; i < k; i++) { + while (stack.length > 0 && nums[stack[stack.length - 1]] < nums[i]) { + stack.pop(); + } + stack.push(i); + } + res.push(nums[stack[0]]); + + while (i < nums.length) { + // 如果单调队列最大值位置被移出滑动窗口了,则去掉队头 + if (stack[0] === i - k) stack.shift(); + + while (stack.length > 0 && nums[stack[stack.length - 1]] < nums[i]) { + stack.pop(); + } + stack.push(i); + res.push(nums[stack[0]]); + i++; + } + + return res; +} +// @lc code=end + +(() => { + LCT.func(maxSlidingWindow).auto(); +})(); diff --git "a/src/25.K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.ts" "b/src/25.K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.ts" new file mode 100644 index 00000000..2090d958 --- /dev/null +++ "b/src/25.K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.ts" @@ -0,0 +1,70 @@ +/* + * @lc app=leetcode.cn id=25 lang=typescript + * @lcpr version=30403 + * + * [25] K 个一组翻转链表 + */ + +// @lc code=start +/** + * Definition for singly-linked list. + * class ListNode { + * val: number + * next: ListNode | null + * constructor(val?: number, next?: ListNode | null) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + * } + */ + +function reverseKGroup(head: ListNode | null, k: number): ListNode | null { + const dummy = new ListNode(0, head); + let l = dummy; + let r = l.next; + while (r !== null) { + let i = k; + while (r !== null && i > 0) { + r = r.next; + i--; + } + // 当 i 没执行完时,说明剩余的链表长度不足 k,不翻转,退出 + if (i > 0) break; + + // 此时 l 表示要反转的 head 的前一个节点,r 表示右开区间边界,即反转链表外的下一个节点 + const newTail = l.next; + const newHead = reverse(newTail, r); + l.next = newHead; + newTail.next = r; + // 翻转后排序:l => newHead => 反转链表 => newTail => r + + // 重新设置好 l 和 r,进下一轮循环 + l = newTail; + r = l.next; + } + return dummy.next; +} + +function reverse(head: ListNode, tail: ListNode | null): ListNode | null { + let cur = head; + let prev = null; + while (cur !== tail) { + const next = cur.next; + cur.next = prev; + prev = cur; + cur = next; + } + return prev; +} +// @lc code=end + +/* +// @lcpr case=start +// [1,2,3,4,5]\n2\n +// @lcpr case=end + +// @lcpr case=start +// [1,2,3,4,5]\n3\n +// @lcpr case=end + + */ diff --git "a/src/2874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" "b/src/2874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" new file mode 100644 index 00000000..effee4ea --- /dev/null +++ "b/src/2874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" @@ -0,0 +1,62 @@ +/* + * @lc app=leetcode.cn id=874 lang=typescript + * @lcpr version=30403 + * + * [874] 模拟行走机器人 + */ + +// @lc code=start +function robotSim(commands: number[], obstacles: number[][]): number { + const dirs = [ + [0, 1], + [1, 0], + [0, -1], + [-1, 0] + ]; + let cur = 0; + + let spot = [0, 0]; + const obstaclesMap = new Set(obstacles.map(o => `${o[0]},${o[1]}`)); + let res = 0; + + for (const command of commands) { + if (command === -2) { + cur = (cur + 3) % 4; + } else if (command === -1) { + cur = (cur + 1) % 4; + } else { + const dir = dirs[cur]; + let k = command; + while (k--) { + const next = [spot[0] + dir[0], spot[1] + dir[1]]; + if (obstaclesMap.has(`${next[0]},${next[1]}`)) { + break; + } + spot = next; + } + res = Math.max(res, Math.pow(spot[0], 2) + Math.pow(spot[1], 2)); + } + } + + return res; +} +// @lc code=end + +/* +// @lcpr case=start +// [4,-1,3]\n[]\n +// @lcpr case=end + +// @lcpr case=start +// [4,-1,4,-2,4]\n[[2,4]]\n +// @lcpr case=end + +// @lcpr case=start +// [6,-1,-1,6]\n[[0,0]]\n +// @lcpr case=end + + */ + +(() => { + LCT.func(robotSim).auto(); +})(); diff --git "a/src/300.\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.ts" "b/src/300.\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.ts" new file mode 100644 index 00000000..84bed1b4 --- /dev/null +++ "b/src/300.\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.ts" @@ -0,0 +1,16 @@ +function lengthOfLIS(nums: number[]): number { + const dp: number[] = Array(nums.length).fill(1); + + for (let i = 1; i < nums.length; i++) { + for (let j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + return Math.max(...dp); +} + +(() => { + LCT.func(lengthOfLIS).cases([{ input: [[10, 9, 2, 5, 3, 7, 101, 18]], output: 4 }]); +})(); diff --git "a/src/42.\346\216\245\351\233\250\346\260\264.ts" "b/src/42.\346\216\245\351\233\250\346\260\264.ts" new file mode 100644 index 00000000..16c6e791 --- /dev/null +++ "b/src/42.\346\216\245\351\233\250\346\260\264.ts" @@ -0,0 +1,48 @@ +/* + * @lc app=leetcode.cn id=42 lang=typescript + * @lcpr version=30403 + * + * [42] 接雨水 + */ + +// @lc code=start +function trap(height: number[]): number { + const stack: number[] = []; + let res = 0; + + for (let i = 0; i < height.length; i++) { + while (stack.length && height[stack[stack.length - 1]] < height[i]) { + // 每一个被出栈的index都代表了可以存雨水的位置 + const index = stack.pop(); + // 出栈后栈顶元素为存水左边界,准备入单调栈的 i 为右边界,如果没有左边界自然不能存水 + if(stack.length === 0) continue; + const left = stack[stack.length - 1]; + + const h = Math.min(height[left], height[i]) - height[index]; + res += (i - left - 1) * h; + } + stack.push(i); + } + return res; +} +// @lc code=end + +(() => { + LCT.func(trap).cases([ + { + input: [[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]], + output: 6 + } + ]); +})(); + +/* +// @lcpr case=start +// [0,1,0,2,1,0,1,3,2,1,2,1]\n +// @lcpr case=end + +// @lcpr case=start +// [4,2,0,3,2,5]\n +// @lcpr case=end + + */ diff --git "a/src/46.\345\205\250\346\216\222\345\210\227.ts" "b/src/46.\345\205\250\346\216\222\345\210\227.ts" new file mode 100644 index 00000000..e5c466aa --- /dev/null +++ "b/src/46.\345\205\250\346\216\222\345\210\227.ts" @@ -0,0 +1,86 @@ +/* + * @lc app=leetcode.cn id=46 lang=typescript + * + * [46] 全排列 + * + * https://leetcode.cn/problems/permutations/description/ + * + * algorithms + * Medium (80.31%) + * Likes: 3353 + * Dislikes: 0 + * Total Accepted: 1.7M + * Total Submissions: 2.2M + * Testcase Example: '[1,2,3]' + * + * 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 + * + * + * + * 示例 1: + * + * + * 输入:nums = [1,2,3] + * 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] + * + * + * 示例 2: + * + * + * 输入:nums = [0,1] + * 输出:[[0,1],[1,0]] + * + * + * 示例 3: + * + * + * 输入:nums = [1] + * 输出:[[1]] + * + * + * + * + * 提示: + * + * + * 1 <= nums.length <= 6 + * -10 <= nums[i] <= 10 + * nums 中的所有整数 互不相同 + * + * + */ + +// @lc code=start +function permute(nums: number[]): number[][] { + const res: number[][] = []; + const len = nums.length; + const visited: boolean[] = Array(nums.length).fill(false); + dfs([]); + + function dfs(path: number[]) { + if (path.length === len) { + res.push([...path]); + return; + } + for (let i = 0; i < len; i++) { + if (visited[i]) continue; + + visited[i] = true; + path.push(nums[i]); + + dfs(path); + + visited[i] = false; + path.pop(); + } + } + + return res; +} +// @lc code=end + +(() => { + LCT.func(permute).auto({ + ignoreArrayOrder: true + }); +})(); diff --git "a/src/714.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.ts" "b/src/714.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.ts" new file mode 100644 index 00000000..060e1dcf --- /dev/null +++ "b/src/714.\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.ts" @@ -0,0 +1,78 @@ +/* + * @lc app=leetcode.cn id=714 lang=typescript + * + * [714] 买卖股票的最佳时机含手续费 + * + * https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/ + * + * algorithms + * Medium (77.56%) + * Likes: 1187 + * Dislikes: 0 + * Total Accepted: 354K + * Total Submissions: 456.2K + * Testcase Example: '[1,3,2,8,4,9]\n2' + * + * 给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。 + * + * 你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 + * + * 返回获得利润的最大值。 + * + * 注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。 + * + * + * + * 示例 1: + * + * + * 输入:prices = [1, 3, 2, 8, 4, 9], fee = 2 + * 输出:8 + * 解释:能够达到的最大利润: + * 在此处买入 prices[0] = 1 + * 在此处卖出 prices[3] = 8 + * 在此处买入 prices[4] = 4 + * 在此处卖出 prices[5] = 9 + * 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8 + * + * 示例 2: + * + * + * 输入:prices = [1,3,7,5,10,3], fee = 3 + * 输出:6 + * + * + * + * + * 提示: + * + * + * 1 <= prices.length <= 5 * 10^4 + * 1 <= prices[i] < 5 * 10^4 + * 0 <= fee < 5 * 10^4 + * + * + */ + +// @lc code=start +function maxProfit(prices: number[], fee: number): number { + // dp[i][j] 表示:第 i 天时当前的状态为 j(0 表示未持有,1 表示持有),此时持有现金利润的最大值 + const dp: number[][] = Array(prices.length) + .fill(0) + .map(x => Array(2).fill(-Infinity)); + dp[0][0] = 0; + dp[0][1] = -prices[0]; + + for (let i = 1; i < prices.length; i++) { + // 此时如果状态是未持有,则由前一天不动或者卖出状态得来;如果是持有,则由前一天不动或者买入状态得来 + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + } + + return dp[prices.length - 1][0]; +} +// @lc code=end + +(() => { + LCT.func(maxProfit).auto(); +})(); diff --git "a/src/76.\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.ts" "b/src/76.\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.ts" new file mode 100644 index 00000000..3f9fb830 --- /dev/null +++ "b/src/76.\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.ts" @@ -0,0 +1,57 @@ +function minWindow(s: string, t: string): string { + const hash: number[] = Array(60).fill(0); + let res: string = ''; + for (let i = 0; i < t.length; i++) { + const index = t.charCodeAt(i) - 65; + hash[index] += 1; + } + + const window: number[] = hash.map(x => x); + let cnt = t.length; + + let l = 0; + let r = 0; + while (r < s.length) { + const tempR = s.charCodeAt(r) - 65; + if (hash[tempR]) { + if (window[tempR] > 0) cnt--; + window[tempR]--; + } + r++; + + while (cnt === 0) { + const substr = s.substring(l, r); + // 满足要求时 + if (res.length === 0 || substr.length < res.length) { + res = substr; + } + + // 缩小左窗口 + const tempL = s.charCodeAt(l) - 65; + if (hash[tempL]) { + window[tempL]++; + if (window[tempL] > 0) cnt++; + } + l++; + } + } + + return res; +} + +(() => { + LCT.func(minWindow).cases([ + { + input: ['ADOBECODEBANC', 'ABC'], + output: 'BANC' + }, + { + input: ['a', 'a'], + output: 'a' + }, + { + input: ['a', 'aa'], + output: '' + } + ]); +})(); diff --git "a/src/874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" "b/src/874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" new file mode 100644 index 00000000..0c8d4350 --- /dev/null +++ "b/src/874.\346\250\241\346\213\237\350\241\214\350\265\260\346\234\272\345\231\250\344\272\272.ts" @@ -0,0 +1,143 @@ +/* + * @lc app=leetcode.cn id=874 lang=typescript + * + * [874] 模拟行走机器人 + * + * https://leetcode.cn/problems/walking-robot-simulation/description/ + * + * algorithms + * Medium (50.36%) + * Likes: 274 + * Dislikes: 0 + * Total Accepted: 56.8K + * Total Submissions: 112.7K + * Testcase Example: '[4,-1,3]\n[]' + * + * 机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands + * : + * + * + * -2 :向左转 90 度 + * -1 :向右转 90 度 + * 1 <= x <= 9 :向前移动 x 个单位长度 + * + * + * 在网格上有一些格子被视为障碍物 obstacles 。第 i 个障碍物位于网格点  obstacles[i] = (xi, yi) 。 + * + * 机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,并继续执行下一个命令。 + * + * 返回机器人距离原点的 最大欧式距离 的 平方 。(即,如果距离为 5 ,则返回 25 ) + * + * + * + * + * + * + * + * + * + * 注意: + * + * + * 北方表示 +Y 方向。 + * 东方表示 +X 方向。 + * 南方表示 -Y 方向。 + * 西方表示 -X 方向。 + * 原点 [0,0] 可能会有障碍物。 + * + * + * + * + * + * + * + * + * 示例 1: + * + * + * 输入:commands = [4,-1,3], obstacles = [] + * 输出:25 + * 解释: + * 机器人开始位于 (0, 0): + * 1. 向北移动 4 个单位,到达 (0, 4) + * 2. 右转 + * 3. 向东移动 3 个单位,到达 (3, 4) + * 距离原点最远的是 (3, 4) ,距离为 3^2 + 4^2 = 25 + * + * 示例 2: + * + * + * 输入:commands = [4,-1,4,-2,4], obstacles = [[2,4]] + * 输出:65 + * 解释:机器人开始位于 (0, 0): + * 1. 向北移动 4 个单位,到达 (0, 4) + * 2. 右转 + * 3. 向东移动 1 个单位,然后被位于 (2, 4) 的障碍物阻挡,机器人停在 (1, 4) + * 4. 左转 + * 5. 向北走 4 个单位,到达 (1, 8) + * 距离原点最远的是 (1, 8) ,距离为 1^2 + 8^2 = 65 + * + * 示例 3: + * + * + * 输入:commands = [6,-1,-1,6], obstacles = [] + * 输出:36 + * 解释:机器人开始位于 (0, 0): + * 1. 向北移动 6 个单位,到达 (0, 6). + * 2. 右转 + * 3. 右转 + * 4. 向南移动 6 个单位,到达 (0, 0). + * 机器人距离原点最远的点是 (0, 6),其距离的平方是 6^2 = 36 个单位。 + * + * 提示: + * + * + * 1 <= commands.length <= 10^4 + * commands[i] 的值可以取 -2、-1 或者是范围 [1, 9] 内的一个整数。 + * 0 <= obstacles.length <= 10^4 + * -3 * 10^4 <= xi, yi <= 3 * 10^4 + * 答案保证小于 2^31 + * + * + */ + +// @lc code=start +function robotSim(commands: number[], obstacles: number[][]): number { + const dirs = [ + [0, 1], + [1, 0], + [0, -1], + [-1, 0] + ]; + let cur = 0; + + let spot = [0, 0]; + const obstaclesMap = new Set(obstacles.map(o => `${o[0]},${o[1]}`)); + let res = 0; + + for (const command of commands) { + if (command === -2) { + cur = (cur + 3) % 4; + } else if (command === -1) { + cur = (cur + 1) % 4; + } else { + const dir = dirs[cur]; + let k = command; + while (k--) { + const next = [spot[0] + dir[0], spot[1] + dir[1]]; + if (obstaclesMap.has(`${next[0]},${next[1]}`)) { + break; + } + spot = next; + } + res = Math.max(res, Math.pow(spot[0], 2) + Math.pow(spot[1], 2)); + } + } + + return res; +} +// @lc code=end + +(() => { + LCT.func(robotSim).auto(); +})(); diff --git "a/src/dynamic-programming/279.\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.ts" "b/src/dynamic-programming/279.\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.ts" index b3b48507..83fe8734 100644 --- "a/src/dynamic-programming/279.\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.ts" +++ "b/src/dynamic-programming/279.\345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.ts" @@ -45,48 +45,69 @@ // @lc code=start function numSquares(n: number): number { - const maxFactor = Math.floor(Math.pow(n, 0.5)); - const maxNum = Number.MAX_SAFE_INTEGER; + const max = Math.floor(Math.sqrt(n)); - const dp = Array(n + 1).fill(maxNum); + // dp[i]表示组成整数 i 的完全平方数的最小数量 + const dp: number[] = Array(n + 1).fill(Infinity); dp[0] = 0; - for (let i = 1; i <= maxFactor; i++) { - const square = Math.pow(i, 2); - for (let j = 1; j <= n; j++) { - if (j >= square) { - dp[j] = Math.min(dp[j], dp[j - square] + 1); + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= max; j++) { + const rest = i - j * j; + if (rest >= 0 && dp[rest] !== Infinity) { + dp[i] = Math.min(dp[i], dp[rest] + 1); } } } - return dp[n]; } // @lc code=end (() => { - // function numSquares(n: number): number { - // const maxFactor = Math.floor(Math.pow(n, 0.5)); - // const maxNum = Number.MAX_SAFE_INTEGER; + LCT.func(numSquares).auto(); + LCT.func(numSquares).cases([{ + input: [1], + output: 1 + }]) +})(); - // const dp = Array(maxFactor + 1) - // .fill(0) - // .map(x => Array(n + 1).fill(maxNum)); +// function numSquares(n: number): number { +// const maxFactor = Math.floor(Math.pow(n, 0.5)); +// const maxNum = Number.MAX_SAFE_INTEGER; - // for (let i = 1; i <= maxFactor; i++) { - // dp[i][0] = 0; - // const square = Math.pow(i, 2); +// const dp = Array(n + 1).fill(maxNum); +// dp[0] = 0; - // for (let j = 1; j <= n; j++) { - // dp[i][j] = dp[i - 1][j]; - // if (j >= square) { - // dp[i][j] = Math.min(dp[i][j], dp[i][j - square] + 1); - // } - // } - // } +// for (let i = 1; i <= maxFactor; i++) { +// const square = Math.pow(i, 2); +// for (let j = 1; j <= n; j++) { +// if (j >= square) { +// dp[j] = Math.min(dp[j], dp[j - square] + 1); +// } +// } +// } +// return dp[n]; +// } - // return dp[maxFactor][n]; - // } - const n = 13; - console.log(numSquares(n)); -})(); +// function numSquares(n: number): number { +// const maxFactor = Math.floor(Math.pow(n, 0.5)); +// const maxNum = Number.MAX_SAFE_INTEGER; + +// const dp = Array(maxFactor + 1) +// .fill(0) +// .map(x => Array(n + 1).fill(maxNum)); + +// for (let i = 1; i <= maxFactor; i++) { +// dp[i][0] = 0; +// const square = Math.pow(i, 2); + +// for (let j = 1; j <= n; j++) { +// dp[i][j] = dp[i - 1][j]; +// if (j >= square) { +// dp[i][j] = Math.min(dp[i][j], dp[i][j - square] + 1); +// } +// } +// } + +// return dp[maxFactor][n]; +// } diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/utils/global-utils.d.ts b/src/utils/global-utils.d.ts index a3f3e44a..7ca79dbf 100644 --- a/src/utils/global-utils.d.ts +++ b/src/utils/global-utils.d.ts @@ -45,6 +45,8 @@ declare global { /** Options for a test suite; `judge` overrides the default deep-equality comparison. */ type LCTOptions = { + /** Compare array outputs without considering the order of their top-level elements. */ + ignoreArrayOrder?: boolean; /** Params are `any` so each call site can annotate its own actual/expected types without casts. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any judge?: (actual: any, expected: any) => boolean; diff --git a/src/utils/lct.ts b/src/utils/lct.ts index 46e329e4..8547f9e5 100644 --- a/src/utils/lct.ts +++ b/src/utils/lct.ts @@ -10,6 +10,8 @@ type AnyConstructor = new (...args: any[]) => any; type Judge = (actual: unknown, expected: unknown) => boolean; type Options = { + /** Compare array outputs without considering the order of their top-level elements. */ + ignoreArrayOrder?: boolean; /** * Custom check between the actual result and the expected value (overrides deep equality). * Params are `any` so each call site can annotate its own actual/expected types without casts. @@ -91,6 +93,13 @@ class LCT { } }; + private arrayOrderInsensitiveEqual: Judge = (actual, expected) => { + if (!Array.isArray(actual) || !Array.isArray(expected)) return false; + + const normalize = (rows: unknown[]) => rows.map(row => this.formatValue(row)).sort(); + return this.deepEqual(normalize(actual), normalize(expected)); + }; + /** * The single execution core: run every row, printing its input/actual/expect * and a PASS/FAIL/ERROR line, then print a summary. @@ -135,7 +144,7 @@ class LCT { * value comes from `getActual`, and expose `.cases()` / `.auto()`. */ private runner(getActual: (input: unknown[]) => unknown) { - const exec = (examples: ReadonlyArray, judge?: Judge) => + const exec = (examples: ReadonlyArray, options?: Options) => this.run( examples.map((example, index) => ({ tag: this.tag('case', index), @@ -144,7 +153,7 @@ class LCT { hasExpected: example.hasExpected, call: () => getActual(example.input) })), - judge + options?.judge ?? (options?.ignoreArrayOrder ? this.arrayOrderInsensitiveEqual : undefined) ); return { cases: (cases: ReadonlyArray, options?: Options) => @@ -156,7 +165,7 @@ class LCT { hasExpected: true }) ), - options?.judge + options ), auto: (options?: Options) => { const examples = this.parseFuncExamples(); @@ -164,7 +173,7 @@ class LCT { console.log('⚠️ No examples found in comment or LCPR blocks'); return; } - exec(examples, options?.judge); + exec(examples, options); } }; }