From cd8c6e3c944b55477998c03db6dad1055e16bfbb Mon Sep 17 00:00:00 2001 From: BoBoooooo <17746714@qq.com> Date: Sat, 14 Sep 2024 10:45:36 +0800 Subject: [PATCH 1/5] feat: add 952 --- ...\273\266\345\244\247\345\260\217__hard.js" | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 "\345\271\266\346\237\245\351\233\206/952__\346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217__hard.js" diff --git "a/\345\271\266\346\237\245\351\233\206/952__\346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217__hard.js" "b/\345\271\266\346\237\245\351\233\206/952__\346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217__hard.js" new file mode 100644 index 0000000..9458386 --- /dev/null +++ "b/\345\271\266\346\237\245\351\233\206/952__\346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217__hard.js" @@ -0,0 +1,86 @@ +/** + * 952. 按公因数计算最大组件大小 +给定一个由不同正整数的组成的非空数组 nums ,考虑下面的图: + +有 nums.length 个节点,按从 nums[0] 到 nums[nums.length - 1] 标记; +只有当 nums[i] 和 nums[j] 共用一个大于 1 的公因数时,nums[i] 和 nums[j]之间才有一条边。 +返回 图中最大连通组件的大小 。 + + + + +解法:并查集 + +方法一:并查集 +为了得到数组 nums 中的每个数和哪些数属于同一个组件,需要得到数组 nums 中的最大值 m,对于每个不超过 m 的正整数 num 计算 num 和哪些数属于同一个组件。对于范围 [2, +num +​ + ] 内的每个正整数 i,如果 i 是 num 的因数,则 num 和 i、 +i +num +​ + 都属于同一个组件。 + +可以使用并查集实现组件的计算。初始时,每个数分别属于不同的组件。如果两个正整数满足其中一个正整数是另一个正整数的因数,则这两个正整数属于同一个组件,将这两个正整数的组件合并。 + +当所有不超过 m 的正整数都完成合并操作之后。遍历数组 nums,对于每个数得到其所在的组件并更新该组件的大小,遍历结束之后即可得到最大组件的大小。 + +作者:力扣官方题解 +链接:https://leetcode.cn/problems/largest-component-size-by-common-factor/solutions/1706239/an-gong-yin-shu-ji-suan-zui-da-zu-jian-d-amdx/ +来源:力扣(LeetCode) + + */ + +/** + * @param {number[]} nums + * @return {number} + */ +var largestComponentSize = function(nums) { + const m = _.max(nums);; + const uf = new UnionFind(m + 1); + for (const num of nums) { + for (let i = 2; i * i <= num; i++) { + if (num % i === 0) { + uf.union(num, i); + uf.union(num, Math.floor(num / i)); + } + } + } + const counts = new Array(m + 1).fill(0); + let ans = 0; + for (let num of nums) { + const root = uf.find(num); + counts[root]++; + ans = Math.max(ans, counts[root]); + } + return ans; +}; + +class UnionFind { + constructor(n) { + this.parent = new Array(n).fill(0).map((_, i) => i); + this.rank = new Array(n).fill(0); + } + + union(x, y) { + let rootx = this.find(x); + let rooty = this.find(y); + if (rootx !== rooty) { + if (this.rank[rootx] > this.rank[rooty]) { + this.parent[rooty] = rootx; + } else if (this.rank[rootx] < this.rank[rooty]) { + this.parent[rootx] = rooty; + } else { + this.parent[rooty] = rootx; + this.rank[rootx]++; + } + } + } + + find(x) { + if (this.parent[x] !== x) { + this.parent[x] = this.find(this.parent[x]); + } + return this.parent[x]; + } +} From f6bf66d8e0ce6109d7506d18155c2f85c500bec7 Mon Sep 17 00:00:00 2001 From: BoBoooooo <17746714@qq.com> Date: Sat, 14 Sep 2024 10:58:31 +0800 Subject: [PATCH 2/5] feat: add 1734 --- ...10\226\346\216\222\345\210\227__medium.js" | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 "\351\235\236\344\270\273\346\265\201/1734__\350\247\243\347\240\201\345\220\216\347\232\204\345\274\202\346\210\226\346\216\222\345\210\227__medium.js" diff --git "a/\351\235\236\344\270\273\346\265\201/1734__\350\247\243\347\240\201\345\220\216\347\232\204\345\274\202\346\210\226\346\216\222\345\210\227__medium.js" "b/\351\235\236\344\270\273\346\265\201/1734__\350\247\243\347\240\201\345\220\216\347\232\204\345\274\202\346\210\226\346\216\222\345\210\227__medium.js" new file mode 100644 index 0000000..b85291e --- /dev/null +++ "b/\351\235\236\344\270\273\346\265\201/1734__\350\247\243\347\240\201\345\220\216\347\232\204\345\274\202\346\210\226\346\216\222\345\210\227__medium.js" @@ -0,0 +1,49 @@ +/** + * 1734. 解码异或后的排列 +中等 +相关标签 +相关企业 +提示 +给你一个整数数组 perm ,它是前 n 个正整数的排列,且 n 是个 奇数 。 + +它被加密成另一个长度为 n - 1 的整数数组 encoded ,满足 encoded[i] = perm[i] XOR perm[i + 1] 。比方说,如果 perm = [1,3,2] ,那么 encoded = [2,1] 。 + +给你 encoded 数组,请你返回原始数组 perm 。题目保证答案存在且唯一。 + + + +示例 1: + +输入:encoded = [3,1] +输出:[1,2,3] +解释:如果 perm = [1,2,3] ,那么 encoded = [1 XOR 2,2 XOR 3] = [3,1] +示例 2: + +输入:encoded = [6,5,4,6] +输出:[2,4,1,5,3] + + +提示: + +3 <= n < 105 +n 是奇数。 +encoded.length == n - 1 + +题解:位运算 + * https://leetcode.cn/problems/decode-xored-permutation/solutions/729101/jie-ma-yi-huo-hou-de-pai-lie-7xing-by-gr-ngs4 + + */ +/** + * @param {number[]} encoded + * @return {number[]} + */ +var decode = function(encoded) { + const x = Array.from({length: encoded.length + 1}, (v, k) => k + 1).reduce((acc, cur) => acc ^ cur); + const perm = [x, ...new Array(encoded.length)]; + encoded.forEach((v, i) => i % 2 !== 0 && (perm[0] ^= v)); + for (let i = 1; i < perm.length; i++) { + perm[i] = perm[i - 1] ^ encoded[i - 1]; + } + return perm; + }; + \ No newline at end of file From 71054799668f2fe7617720f70a86f0d90dabde6b Mon Sep 17 00:00:00 2001 From: BoBoooooo <17746714@qq.com> Date: Sat, 14 Sep 2024 11:38:52 +0800 Subject: [PATCH 3/5] feat: init 906 --- ...\233\236\346\226\207\346\225\260__hard.js" | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 "\351\235\236\344\270\273\346\265\201/906__\350\266\205\347\272\247\345\233\236\346\226\207\346\225\260__hard.js" diff --git "a/\351\235\236\344\270\273\346\265\201/906__\350\266\205\347\272\247\345\233\236\346\226\207\346\225\260__hard.js" "b/\351\235\236\344\270\273\346\265\201/906__\350\266\205\347\272\247\345\233\236\346\226\207\346\225\260__hard.js" new file mode 100644 index 0000000..965e5d2 --- /dev/null +++ "b/\351\235\236\344\270\273\346\265\201/906__\350\266\205\347\272\247\345\233\236\346\226\207\346\225\260__hard.js" @@ -0,0 +1,68 @@ +/**906. 超级回文数 +如果一个正整数自身是回文数,而且它也是一个回文数的平方,那么我们称这个数为超级回文数。 + +现在,给定两个正整数 L 和 R (以字符串形式表示),返回包含在范围 [L, R] 中的超级回文数的数目。 + + +示例: + +输入:L = "4", R = "1000" +输出:4 +解释: +4,9,121,以及 484 是超级回文数。 +注意 676 不是一个超级回文数: 26 * 26 = 676,但是 26 不是回文数。 + + +提示: + +1 <= len(L) <= 18 +1 <= len(R) <= 18 +L 和 R 是表示 [1, 10^18) 范围的整数的字符串。 +int(L) <= int(R) + + * + * 题解: https://leetcode.cn/problems/super-palindromes/solutions/1711358/js-by-a-ba-li-9fcy + * @param {*} left + * @param {*} right + * @returns + */ +var superpalindromesInRange = function (left, right) { + const bigLeft = BigInt(left); + const bigRight = BigInt(right); + return buildPalindrome(1, bigLeft, bigRight) + buildPalindrome(2, bigLeft, bigRight); + }; + + // type 1:数字总长度为奇数个 + // type 2:数字总长度为偶数个 + function buildPalindrome(type, bigLeft, bigRight) { + let result = 0; + for (let k = 1; k < 100000; ++k) { + let num = String(k); + // 将后续的回文拼上来,如num为1234,拼上后就是1234321了,这时num已经是回文数了 + for (let i = num.length - type; i >= 0; --i) { + num += num[i]; + } + // 这里的v是计算的平方值 + let v = BigInt(num); + v *= v; + // 如果v越界了,则直接break + if (v > bigRight) break; + // 如果v也是回文数,则加入结果集 + if (v >= bigLeft && isPalindrome(String(v))) result++; + } + return result; + } + + // 判断s是否是回文字符串 + var isPalindrome = function (s) { + const len = s.length; + if (len === 1) return true; + let start = 0; + let end = len - 1; + while (start < end) { + if (s[start++] !== s[end--]) { + return false; + } + } + return true; + } \ No newline at end of file From 8e605ea4feebf29bd16e15aede3ef703f34cda14 Mon Sep 17 00:00:00 2001 From: BoBoooooo <17746714@qq.com> Date: Sat, 14 Sep 2024 15:18:12 +0800 Subject: [PATCH 4/5] docs: update readme --- README.md | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index abb79ca..fb68922 100644 --- a/README.md +++ b/README.md @@ -1 +1,124 @@ -记录下题解 +#【数学题】超级回文数 + +1. 转 BigInt,从 1 - 100000 构造偶数回文 + 构造奇数回文,再判断平方数是否是回文数,结果转成字符串 +2. 最后结果升序排列 + +#【DFS】计算岛屿最大面积 + +1. 定义 dfs 函数 + - 越界判断/递归终止条件:`i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 0`,返回 0 + - 走过的标记为 0 + - 遍历上下左右四个方向,累加结果 +2. 遍历 grid,更新最大面积 + +#【DFS】二叉树的最大路径和 + +1. `maxSum = 0` 初始化 +2. DFS,后序遍历 + - 遍历到 null 节点,返回 0 + - 计算当前节点的内部最大路径和(经过左右子树的最大和) + - 更新全局的最大路径和 maxSum + - 返回当前节点对其父节点能够贡献的最大路径和(只能选择左子树或右子树中的一个),如果小于 0 则返回 0 +3. 最终返回 maxSum,即二叉树中的最大路径和 + +#【双指针】最多能完成排序的块 + +双指针 sum1, sum2,对排序后的数组和原数组同时按顺序分别累加,两者只要相等一次,就视为找到一个块,最后返回 count + +#【双指针】无重复字符的最长子串 + +1. 滑动窗口,`left = 0, right = 1` 指针,right 指针遍历字符串,left 指针收缩窗口 +2. 当前窗口子串包含下一位字符,left 右移,不包含则 right 右移 +3. 返回窗口最大长度 `max = Math.max(max, right - left)` + +#【哈希表】按位与为零的三元组 + +用哈希表 map 存储所有二元组按位与的结果与出现次数,再枚举数组 nums 与 map 所有的键,如果二者位与结果为 0 就累计其次数 + +#【动态规划】最长有效括号 + +- dp 定义:记录以坐标 i 结尾的最长有效括号子串长度 +- 状态转移方程 `dp[i] = 2 + dp[i-1] + dp[i - dp[i-1] -2]` 【最小单元 ()(()) 】 +- 注意判断 `i - dp[i-1] -1` 以及 `i - dp[i-1] -2` 是否越界,越界则表示不存在,直接为 0 + + +#【动态规划】最长递增子序列 + +1. 动态规划,`dp[i]` 表示以 nums[i] 结尾的最长递增子序列的长度 +2. 初始化 dp 数组,`dp[i] = 1`,`result = 1`;默认最长为 1 +3. 遍历 nums,对于每个 nums[i],遍历 nums[0] - nums[i-1],如果 `nums[j] < nums[i]`,则 `dp[i] = max(dp[i], dp[j] + 1)` +4. `result = max(result, dp[i])` +5. 返回 result + +#【动态规划 & 二维 DP】不相交的线 (最长公共子序列) + +1. 动态规划,`dp[i][j]` 表示 nums1 前 i 个元素和 nums2 前 j 个元素的最长公共子序列长度 +2. 初始化 dp 数组,`dp[i][j] = 0`,长度为 num1 + 1, num2 + 1 +3. 两层遍历 + - 遍历条件 `i = 1, i < num1.length, ++i`,`j = 1, j < num2.length, ++j` + - 如果 `nums1[i - 1] == nums2[j - 1]`,则 `dp[i][j] = dp[i-1][j-1] + 1` + - 否则 `dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])` (可能从左边或者上边转移过来) +4. 返回 `dp[num1.length][num2.length]` + +#【并查集】按公因数计算最大组件大小 + +迷之并查集 + +#【单调栈】拼接最大数 + +迷之单调栈,先接个雨水吧 + +#【链表】按格式合并两个链表 + +迷之合并 + +#【位运算】解码异或后的排列 + +迷之位运算 + +```js +/** + * @param {number[]} encoded + * @return {number[]} + */ +var decode = function(encoded) { + const x = Array.from({length: encoded.length + 1}, (v, k) => k + 1).reduce((acc, cur) => acc ^ cur); + const perm = [x, ...new Array(encoded.length)]; + encoded.forEach((v, i) => i % 2 !== 0 && (perm[0] ^= v)); + for (let i = 1; i < perm.length; i++) { + perm[i] = perm[i - 1] ^ encoded[i - 1]; + } + return perm; + }; +``` + +#【状态压缩】最长超赞子字符串 +迷之超赞 +```js +var longestAwesome = function (s) { + let prefixSum = 0; + let map = new Map(); + let res = 1; + map.set(0, -1); + for (let i = 0; i < s.length; i++) { + prefixSum = prefixSum ^ (1 << s[i]); + + // 考虑中间计算为偶数部分 + if (map.has(prefixSum)) { + res = Math.max(res, i - map.get(prefixSum)); + } else { + map.set(prefixSum, i); + } + + // 考虑中间为奇数部分 + for (let j = 0; j < 10; j++) { + let next = prefixSum ^ (1 << j); + if (map.has(next)) { + res = Math.max(res, i - map.get(next)); + } + } + } + + return res; +}; +``` \ No newline at end of file From 143dd1cfc79661e9459efdd748a419d4c6a05927 Mon Sep 17 00:00:00 2001 From: BoBoooooo <17746714@qq.com> Date: Sat, 14 Sep 2024 15:38:34 +0800 Subject: [PATCH 5/5] docs: update --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fb68922..f059f23 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +![image.png](https://kmpvt.pfp.ps.netease.com/file/66e5345f7a4c09b4528a9d5aVK0OJNbM01?sign=dNjNbSubY9wWMgZGsV5u0-H_v44=&expire=1726301251&type=image/png) + +先接个雨水吧 + #【数学题】超级回文数 1. 转 BigInt,从 1 - 100000 构造偶数回文 + 构造奇数回文,再判断平方数是否是回文数,结果转成字符串 @@ -35,14 +39,14 @@ 用哈希表 map 存储所有二元组按位与的结果与出现次数,再枚举数组 nums 与 map 所有的键,如果二者位与结果为 0 就累计其次数 -#【动态规划】最长有效括号 +#【DP】最长有效括号 - dp 定义:记录以坐标 i 结尾的最长有效括号子串长度 - 状态转移方程 `dp[i] = 2 + dp[i-1] + dp[i - dp[i-1] -2]` 【最小单元 ()(()) 】 - 注意判断 `i - dp[i-1] -1` 以及 `i - dp[i-1] -2` 是否越界,越界则表示不存在,直接为 0 -#【动态规划】最长递增子序列 +#【DP】最长递增子序列 1. 动态规划,`dp[i]` 表示以 nums[i] 结尾的最长递增子序列的长度 2. 初始化 dp 数组,`dp[i] = 1`,`result = 1`;默认最长为 1 @@ -50,7 +54,7 @@ 4. `result = max(result, dp[i])` 5. 返回 result -#【动态规划 & 二维 DP】不相交的线 (最长公共子序列) +#【DP 二维】不相交的线 (最长公共子序列) 1. 动态规划,`dp[i][j]` 表示 nums1 前 i 个元素和 nums2 前 j 个元素的最长公共子序列长度 2. 初始化 dp 数组,`dp[i][j] = 0`,长度为 num1 + 1, num2 + 1 @@ -64,6 +68,69 @@ 迷之并查集 +1. 初始化并查集:unionfind 对象用于存储每个数或因子的根节点。 +2. 遍历数组 A:对每个数 num,计算它的所有因子,并将 num 与这些因子“合并”到同一个集合中。 +3. 合并操作:union 函数负责将 num 及其因子连在一起。并查集的 find 函数用来查找元素所属的根节点,并带有路径压缩优化。 +4. 统计最大连通分量:遍历数组 A,通过 find 找到每个数所属的连通分量的根节点,记录该连通分量的大小,并找出最大的连通分量。 + +```js +/** + * @param {number[]} A + * @return {number} + */ +var largestComponentSize = function(A) { + // unionfind 用来记录每个数和因子的连通关系 + const unionfind = {}; + + // 对数组 A 中的每个数进行因子分解,并将它与其因子加入同一个集合 + for (const num of A) { + // 对当前数 num 进行因子分解,i 代表因子 + for (let i = 2; i <= Math.sqrt(num); i++) { + // 如果 i 是 num 的因子,说明 num 可以被 i 整除 + if (num % i === 0) { + // 将 num 的两个因子 i 和 num / i 以及 num 本身加入同一个集合 + union(i, num / i, num); + } + } + } + + // count 用于统计每个连通分量中的元素个数 + const count = {}; + let res = 0; + + // 遍历数组 A,找到每个数所在的连通分量的根节点,并统计每个分量的大小 + for (const num of A) { + const p = find(num); // 找到 num 所在连通分量的根节点 + if (!count[p]) { + count[p] = 0; + } + count[p] += 1; // 统计该连通分量的大小 + res = Math.max(res, count[p]); // 更新最大连通分量的大小 + } + return res; // 返回最大连通分量的大小 + + // find 函数用于查找元素 i 所在集合的根节点(带路径压缩) + function find(i) { + // 如果 i 不在 unionfind 中,初始化它的根为自己 + if (unionfind[i] === undefined) { + return (unionfind[i] = i); + } + // 如果 i 不是根节点,递归查找它的根,并路径压缩 + return unionfind[i] === i ? i : (unionfind[i] = find(unionfind[i])); + } + + // union 函数将 x, y, z 三个元素合并到同一个集合 + function union(x, y, z) { + x = find(x); // 找到 x 的根节点 + y = find(y); // 找到 y 的根节点 + z = find(z); // 找到 z 的根节点 + // 将 x 和 y 的根节点指向 z,将它们合并 + unionfind[x] = unionfind[y] = z; + } +}; + +``` + #【单调栈】拼接最大数 迷之单调栈,先接个雨水吧