Leetcode
0000-readme
Leetcode 题目众多,实际上前 100题已经可以代表 90%的情况,把前 100 题目做好做透,就有质的提升
后面几千题考点类似
0001-two-sum.js
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 示例:
// 给定 nums = [2, 7, 11, 15], target = 9
// 因为 nums[0] + nums[1] = 2 + 7 = 9
// 所以返回 [0, 1]
// 使用哈希表
// 68 ms, 在所有 javascript 提交中击败了85.69%
function twoSum2(nums, target) {
const len = nums.length;
const hash = {};
for (let i = 0; i < len; i++) {
const item = nums[i];
const index = hash[`${item}`];
if (index > -1) {
return [index, i];
}
hash[`${target - item}`] = i;
}
}
|
0002-addTwoNumbers.js
02-addTwoNumbers.js
给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
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
|
// 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
// 输出:7 -> 0 -> 8
// 原因:342 + 465 = 807
// 特殊情况
// [9, 9], [9] => [8, 0, 1]
// [5], [5] => [0, 1]
// 思路1:112 ms, 在所有 JavaScript 提交中击败了82.57%
function ListNode(val) {
this.val = val;
this.next = null;
}
// 如果当前节点是10,那么判断下一个节点是否存在,把当前节点变成1,然后下一个节点加1
// 优化:应该判断大于10
function handleTen(node) {
if (node.val === 10) {
if (node.next) {
node.val = 0;
node.next.val++;
handleTen(node.next);
} else {
node.val = 0;
node.next = new ListNode(1);
}
}
}
// 递归计算两个链表的每一项的和,并返回到 result 中
function addTwoNumbers(l1, l2) {
// 处理两个链表不存在的情况
if (!l1 && !l2) {
return null;
}
if (!l1) {
handleTen(l2);
return l2;
}
if (!l2) {
handleTen(l1);
return l1;
}
let value = l1.val + l2.val;
if (value > 9) {
if (l1.next) {
l1.next.val++;
} else {
l1.next = new ListNode(1);
}
value -= 10;
}
const result = new ListNode(value);
result.next = addTwoNumbers(l1.next, l2.next);
return result;
}
// 思路2
// 112 ms, 在所有 JavaScript 提交中击败了82.57%
const addTwoNumbers2 = function (l1, l2) {
// 辅助函数
const getVal = function (list1, list2, plus = 0) {
const val1 = list1 && list1.val;
const val2 = list2 && list2.val;
const total = val1 + val2 + plus;
return {
val: total % 10,
next: parseInt(total / 10, 10),
};
};
let l = null;
let plus = 0;
// 辅助函数
// eslint-disable-next-line no-shadow
function setNode(vm, l1, l2) {
if (!l1 && !l2 && !plus) {
return vm;
}
const totalObj = getVal(l1, l2, plus);
if (totalObj.next) {
plus = totalObj.next;
} else {
plus = 0;
}
if (!l) {
l = new ListNode(totalObj.val);
vm = l;
} else {
vm.next = new ListNode(totalObj.val);
vm = vm.next;
}
if ((l1 && l1.next) || (l2 && l2.next) || plus) {
return setNode(vm, l1 && l1.next, l2 && l2.next);
}
}
setNode(l, l1, l2);
return l;
};
export { addTwoNumbers, addTwoNumbers2 };
|
0003-lengthOfLongestSubstring.js
[3] 无重复字符的最长子串
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
|
// 初步思路:双指针(快慢指针实现)
// 两个指针移动,然后不断判断子序列内部是否重复,然后累计计算当前最长的距离。
// 1、循环获取子串 ——双指针
// 2、如何判断一个子串是否有重复字符?——这个直接IndexOf行不行
// 如果这个方法不好,那么使用对象判断是否重复
function lengthOfLongestSubstring(s) {
// 辅助函数:true 没有重复字符串(有没有更好的办法)
const checkStr = (str) => {
const len = str.length;
for (let i = 0; i < len; i++) {
const current = str[i];
if (str.lastIndexOf(current) !== str.indexOf(current)) {
return false;
}
}
return true;
};
const len = s.length;
// 处理特殊情况(如果长度是0或者是1,那么直接返回长度)
if (len <= 1) {
return len;
}
// 设置初始值
let start = 0;
let end = 1;
let num = 1;
// 循环
while (end <= len - 1) {
const sub = s.slice(start, end + 1);
// result 这个变量名不好,可以改成是否有重复的子字符串
const result = checkStr(sub);
if (result) {
// 如果不重复,累积最大值,快指针加一
end++;
num = num > sub.length ? num : sub.length;
} else {
// 如果重复,重新开始
if (end === len - 1) {
return num;
}
start++;
end++;
}
}
return num;
}
export { lengthOfLongestSubstring };
|
04
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。(如果有一个是空的情况)
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
102
103
104
105
106
107
108
109
|
// 思路:双指针:把排序数组的最小的一个放在第三个数组中,如果一个是空,那么就放另一个,如果结果数组的长度大于index2,那么返回结果数组的最后两项
// 尝试用双指针解决: 这个本地单元测试通过,严格逻辑可能有问题,需要注意——算法逻辑不太好
function findMedianSortedArrays(nums1, nums2) {
const len1 = nums1.length;
const len2 = nums2.length;
const middle = (len1 + len2) / 2;
let index1 = null;
let index2 = null;
// 这里判断奇数还是偶数
if (middle % 1 !== 0) {
index1 = middle - 0.5;
} else {
index1 = middle - 1;
index2 = middle;
}
// len 是长度,index 是数组下标,需要减一;先处理1个数组是0的情况
if (len1 === 0) {
if (index2) {
return (nums2[index1] + nums2[index2]) / 2;
}
return nums2[index1];
}
if (len2 === 0) {
if (index2) {
return (nums1[index1] + nums1[index2]) / 2;
}
return nums1[index1];
}
if (len1 === 1 && len2 === 1) {
return (nums1[0] + nums2[0]) / 2;
}
// 处理两个数组都不是空的情况,使用第三个数组和双指针
if (index2) {
// 偶数,需要获取两个数并计算平均数
let i = 0; let j = 0;
const sum = [];
// 能否改成一个变量?这里不需要数组,直接使用一个临时变量,记录上一个参数是多少就行
while (i + j <= index2) {
if (nums1[i] <= nums2[j] && nums1[i] !== ) {
sum.push(nums1[i]);
i++;
} else if (nums2[j] !== ) {
sum.push(nums2[j]);
j++;
} else {
if (i + 1 === len1) {
// 第一个数列结束了,第二个剩余
const tmp = index2 - i;
return ((nums2[tmp] + nums2[tmp - 1]) / 2);
}
// 第二个结束了,第一个剩余
const tmp = index2 - j;
return ((nums1[tmp] + nums1[tmp - 1]) / 2);
}
if (i + j - 1 === index2) {
return ((sum[i + j - 1] + sum[i + j - 2]) / 2);
}
}
} else {
// 奇数,计算当时的那个中位数即可
let i = 0;
let j = 0;
while (i + j <= index1) {
if (nums1[i] <= nums2[j] && nums1[i] !== ) {
i++;
} else if (nums2[j] !== ) {
j++;
} else {
if (i + 1 === len1) {
return nums2[index1 - i];
}
return nums1[index1 - j];
// 一个已经结束,另一个还有很多
// i + j < index1 其中一个已经循环结束了
// 那么直接返回剩余的一个数组的 index1 - i 即可
}
if (i + j === index1) {
if (nums1[i] === ) return nums2[j];
if (nums2[j] === ) return nums1[i];
return nums1[i] < nums2[j] ? nums1[i] : nums2[j];
}
}
}
}
// 思路二:把排序后的数组合并到一起,然后重新排序,获取中位数即可,想法最简单,性能有点差
// 156 ms, 在所有 javascript 提交中击败了48.38%
function findMedianSortedArrays2(nums1, nums2) {
const arr = nums1.concat(nums2);
arr.sort((a, b) => a - b);
const len = arr.length;
const middle = len / 2;
let index1 = null;
let index2 = null;
if (middle % 1 !== 0) {
index1 = middle - 0.5;
} else {
index1 = middle - 1;
index2 = middle;
}
if (index2) {
return (arr[index1] + arr[index2]) / 2;
}
return arr[index1];
}
export { findMedianSortedArrays, findMedianSortedArrays2 };
|
0005-longestPalindrome.js
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
|
const longestPalindrome = function(s) {
// 辅助函数:判断子字符串是否是回文
// 循环一半字符串即可
const checkPalindrome = (start, end) => {
const subLen = Math.ceil((end - start) / 2);
for (let i = 0; i <= subLen; i++) {
if (s[start + i] !== s[end - i]) {
return false;
}
}
return true;
};
const len = s.length;
for (let i = len - 1; i > 0; i--) {
// 这是当前子字符串的长度
const currentLen = i;
// 内部循环,遍历全部的情况(获取开始节点和结束节点的指针)
for (let j = 0; j < len - currentLen; j++) {
const start = j;
const end = j + currentLen;
// 判断两个指针构成的子字符串,是否是回文的
// 注意指针的边界判断
if (checkPalindrome(start, end)) {
return s.slice(start, end + 1);
}
}
}
return s[0];
};
export { longestPalindrome };
|
0006-convertZ.js
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
|
/**
* [6] Z 字形变换
* @lc app=leetcode.cn id=6 lang=javascript
* @param {string} s
* @param {number} numRows
* @return {string}
*/
const convert = function(s, numRows) {
// 注意处理 num是1-2的情况
if (numRows === 1) return s;
// 建一个对象,设置不同的键,然后每一个numRows是空字符串
const dict = {};
for (let key = 1; key <= numRows; key++) {
dict[key] = '';
}
// 如果当前的序号是 0 或者是 numRows.length - 1
let current = 1;
let direction = true;
for (let i = 0; i < s.length; i++) {
// 获取当前的字符串,并加入到字典中
const item = s[i];
dict[current] = dict[current] + item;
// 当循环到第一个或者最后一个,换向
if (current === numRows) {
direction = false;
} else if (current === 1) {
direction = true;
}
// 当前的序号增加或者减少
if (direction) {
current++;
} else {
current--;
}
}
let result = '';
for (let i = 1; i <= numRows; i++) {
const item = dict[i];
result += item;
}
return result;
// 然后遍历完,把对应的字符串从对象中拿出
// 然后再拼接成新的字符串
};
export { convert };
|
0007-reverse.js
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
|
// 07-整数反转
// 给出一个32位的有符号整数,你需要将这个整数中每位上的数字进行反转。
// 方法1:使用数组和字符串反转数字
// 难点:没有看清楚整数溢出说明,前两次调试错误
// 假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
// 时间复杂度不好,128 ms 35.8 MB weak
/**
* @param {number[]} x
* @return {number[]}
*/
function reverse1(x) {
const isMinus = x < 0;
const arr = String(Math.abs(x)).split('').reverse();
const result = Number(arr.join(''));
if (result >= (2 ** 31) - 1 || result <= ((-2) ** 31) + 1) {
return 0;
}
return isMinus ? -result : result;
}
// 算法二:遍历这个数字,依次获取个位数并放在新的数中。96 ms 较好
function reverse(x) {
if (x === 0) return x;
const isMinus = x < 0;
let result = 0;
x = Math.abs(x);
while (x > 0) {
const a = x % 10;
result = result * 10 + a;
x = (x - a) / 10;
}
if (result >= (2 ** 31) - 1 || result <= ((-2) ** 31) + 1) {
return 0;
}
return isMinus ? -result : result;
}
export { reverse, reverse1 };
|
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
|
// 08 字符串转化成数值(特殊情况很多)
// 请你来实现一个 atoi 函数,使其能将字符串转换成整数。
/**
*
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
*/
/**
* @param {string} str
* @return {number}
*/
// const myAtoi = function(str) {
// const number = Number(str);
// if (isNaN(number)) {
// let result = 0;
// while (str.length > 0) {
// if (str.slice(0, 1) === ' ') break;
// const first = 1 * str.slice(0, 1);
// if (isNaN(first)) break;
// result = result * 10 + first;
// str = str.substring(1);
// }
// return result;
// }
// // handle number max and min
// const max = Math.pow(2, 31);
// const min = Math.pow(-2, 31);
// return number > max ? max : ( number < min ? min : number);
// };
// 标准答案都会使用正则表达式,直接使用字符串API无法处理复杂情况
// 更快的答案直接把最值计算转化成具体的数值比较
// 尽快巩固常见的正则表达式,日常使用正则表达式不多
function myAtoi(str) {
str = str.trim();
// [+|-] 表示一个或者多个字符
// d+ 表示一个或者多个数值
const reg = new RegExp(/^[+|-]?\d+/);
if (!reg.test(str)) {
return 0;
}
const val = parseInt(str.match(reg), 0);
const min = -(2 ** 31);
const max = -min - 1;
return Math.max(Math.min(val, max), min);
}
export { myAtoi };
|
0009-isPalindrome.js
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
|
// 09-判断回文数
// https://leetcode.com/problems/palindrome-number/description/
// 如果是0,是回文数
// 如果是0结尾,一定不是回文数;如果是负数,一定不是回文数
// 如果是小数,一定不是回文数(输入整数,不考虑这个情况)
// 执行用时: 220 ms 45.2 MB
function isPalindrome(x) {
if (x === 0) return true;
if (x < 0 || x % 10 === 0) return false;
// 其他的进入循环判断
const arr = String(x).split('');
for (let i = 0, len = arr.length; i < len / 2; i++) {
if (arr[i] !== arr[len - i - 1]) {
return false;
}
}
return true;
}
// 方法二
// 判断回文数:原数字取反相同即可
// 204 ms 46.8 MB
function isPalindrome2(x) {
if (x < 0) return false;
if (x === 0) return true;
let a = x;
let b = 0;
while (a > 0) {
b = b * 10 + (a % 10);
a = (a - (a % 10)) / 10;
}
return b === x;
}
export { isPalindrome, isPalindrome2 };
|
0010-isMatch.js
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
|
/*
* @lc app=leetcode.cn id=10 lang=javascript
* [10] 正则表达式匹配
*/
// 思路:
// 从左向右匹配比较复杂,从右向左匹配
// 这个是动态规划:F(n)= n && F(n - 1)
// 具体就是:一个字符串是否满足 === 最后一个字符是否满足 && 前面的字串是否满足,然后迭代实现
// 参考链接:https://leetcode.cn/problems/regular-expression-matching/solution/shou-hui-tu-jie-wo-tai-nan-liao-by-hyj8/
// 这个比较难,未来多做几次
// @lc code=start
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
const isMatch = (s, p) => {
if (s == null || p == null) {
return false;
}
const sLen = s.length;
const pLen = p.length;
const dp = new Array(sLen + 1);
for (let i = 0; i < dp.length; i++) {
dp[i] = new Array(pLen + 1).fill(false);
}
// 基本情况
dp[0][0] = true;
for (let j = 1; j < pLen + 1; j++) {
if (p[j - 1] == '*') {
dp[0][j] = dp[0][j - 2];
}
}
// 迭代(动态规划)
for (let i = 1; i < sLen + 1; i++) {
for (let j = 1; j < pLen + 1; j++) {
// 1.如果最后一个字符相等,或者最后一个是通配符. 满足匹配下一个
if (s[i - 1] === p[j - 1] || p[j - 1] === '.') {
dp[i][j] = dp[i - 1][j - 1];
}
// 2. 如果最后一个是 *,分情况
else if (p[j - 1] == '*') {
// 2.1 如果前一位相等,或者是通配符
if (s[i - 1] === p[j - 2] || p[j - 2] === '.') {
// 下面三种情况满足一种即可(这里没有考虑到)
// 这个情况比较复杂
dp[i][j] = dp[i][j - 2] || dp[i - 1][j - 2] || dp[i - 1][j];
}
// 2.2 如果前一位不相等,呢么等于前面两位的字符串
else {
dp[i][j] = dp[i][j - 2];
}
}
}
}
// 返回对应的匹配结果
return dp[sLen][pLen];
};
export { isMatch };
|
0011-maxArea.js
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
|
// 11
// 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,
// 垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
// 说明:你不能倾斜容器,且 n 的值至少为 2。
// 输入: [1,8,6,2,5,4,8,3,7]
// 输出: 49
// 方法1:两次循环,逐步比较计算最大值
// 两次循环性能不好:1868 ms, 在所有 javascript 提交中击败了5.11%
function maxArea(height) {
if (height.length === 2) {
return height[0] > height[1] ? height[1] : height[0];
}
let max = 0;
const { length } = height;
for (let i = 0; i < length; i++) {
for (let j = i + 1; j < length; j++) {
if (height[j + 1] && height[j + 1] > height[j]) {
continue;
}
const area = (height[i] > height[j] ? height[j] : height[i]) * (j - i);
if (area > max) {
max = area;
}
}
}
return max;
}
// 方法2:双指针逼近最大值
// 84 ms, 在所有 JavaScript 提交中击败了82.20%
// 一次循环实现(算法复杂度满足)
function maxArea2(height) {
const len = height.length;
if (len === 2) {
return Math.min(height[0], height[1]);
}
let left = 0;
let right = len - 1;
let max = Math.min(height[left], height[right]) * (right - left);
while (left !== right) {
if (height[left] < height[right]) {
left++;
} else {
right--;
}
const curr = Math.min(height[left], height[right]) * (right - left);
if (curr > max) {
max = curr;
}
}
return max;
}
export { maxArea, maxArea2 };
|
0012-intToRoman.js
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
|
// 12-罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
// 字符 数值
// I 1
// V 5
// X 10
// L 50
// C 100
// D 500
// M 1000
// 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
// 这几种情况特殊处理
// 不断取余数,然后存放在数组中,最后遍历数组,转化成字符串输出
// I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
// X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
// C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
// 148 ms, 在所有 javascript 提交中击败了94.84%
function intToRoman(num) {
const res = [];
res[0] = (num - (num % 1000)) / 1000;
num %= 1000;
res[1] = (num - (num % 500)) / 500;
num %= 500;
res[2] = (num - (num % 100)) / 100;
num %= 100;
res[3] = (num - (num % 50)) / 50;
num %= 50;
res[4] = (num - (num % 10)) / 10;
num %= 10;
res[5] = (num - (num % 5)) / 5;
num %= 5;
res[6] = num;
// 1994 [ 1, 1, 4, 1, 4, 0, 4 ] M CM XC IV
// if null , ''
let result = '';
// 1000
while (res[0] > 0) {
result += 'M';
res[0]--;
}
// 100
if (res[2] === 4) {
result = res[1] === 1 ? `${result}CM` : `${result}CD`;
} else {
// while (res[1] > 0) {
// result += 'D';
// res[1]--;
// }
if (res[1] === 1) {
result += 'D';
}
while (res[2] > 0) {
result += 'C';
res[2]--;
}
}
// 10
if (res[4] === 4) {
result = res[3] === 1 ? `${result}XC` : `${result}XL`;
} else {
// while (res[3] > 0) {
// result += 'L';
// res[3]--;
// }
if (res[3] === 1) {
result += 'L';
}
while (res[4] > 0) {
result += 'X';
res[4]--;
}
}
// 1
if (res[6] === 4) {
result = res[5] === 1 ? `${result}IX` : `${result}IV`;
} else {
// while (res[5] > 0) {
// result += 'V';
// res[5]--;
// }
if (res[5] === 1) {
result += 'V';
}
while (res[6] > 0) {
result += 'I';
res[6]--;
}
}
return result;
}
export { intToRoman };
|
0013-romanToInt.js
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
|
// 罗马字符串转化成数值
// https://leetcode.com/problems/roman-to-integer/description/
// https://en.wikipedia.org/wiki/Roman_numerals
// 思路1:不同字符串对应不同的数值,这里使用一个Map存储对应表;
// 首先把字符串转化一个数值(while字符串长度大于0)
// 如果当前的值比后面的大,直接转化后加入数值;如果当前的值和后面的相等,直接加入这个数值(这两个情况属于一类)
// 如果当期的值比后面的小(XV),使用后面的减去前面的值,字符串减去2;
// 168 ms, 在所有 JavaScript 提交中击败了 94.41% 的用户
function romanToInt(s) {
const dir = {
I: 1,
V: 5,
X: 10,
L: 50,
C: 100,
D: 500,
M: 1000,
};
let result = 0;
while (s.length > 0) {
const s0 = s.charAt(0);
const s1 = s.charAt(1);
if (s1 === '' || s0 === s1 || dir[s0] > dir[s1]) {
result += dir[s0];
s = s.substring(1);
} else if (dir[s0] < dir[s1]) {
result = result - dir[s0] + dir[s1];
s = s.substring(2);
}
}
return result;
}
// 方案2
// 上面的方案中,遇到4和9直接减法
// 这个方案,直接获取4和9存放到对象中,然后进行相加(这样可以减少一定的加法计算)
// 可以不使用数组存放,两种方法的复杂度都是N
function romanToInt2(s) {
const romanLetters = {
M: 1000,
CM: 900,
D: 500,
CD: 400,
C: 100,
XC: 90,
L: 50,
XL: 40,
X: 10,
IX: 9,
V: 5,
IV: 4,
I: 1,
};
const arr = s.split('');
let num = 0;
for (let i = 0; i < arr.length; i++) {
const key = arr[i];
if (key !== 'V' || key !== 'M') {
if (romanLetters[key + arr[i + 1]]) {
num += romanLetters[key + arr[i + 1]];
i += 1;
continue;
}
}
num += romanLetters[key];
}
return num;
}
export { romanToInt, romanToInt2 };
|
0014-longestCommonPrefix.js
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
|
// 14-编写一个函数来查找字符串数组中的最长公共前缀。
// 给定一个字符串数组,返回公共前缀(字符串都是小写)
// Write a function to find the longest common prefix string amongst an array of strings.
// https://leetcode.com/problems/longest-common-prefix/description/
// 方案1
// 执行用时 : 60 ms, 在所有 JavaScript 提交中击败了 99.08% 的用户
/**
* @param {string[]} strs
* @return {string}
*/
function longestCommonPrefix(strs) {
if (strs.length === 0) return '';
if (strs.length === 1) {
return strs[0];
}
// 如果长度大于1,那么需要获取公共字符串前缀
let commonPrefix = '';
for (let i = 0, len = strs[0].length; i < len; i++) {
const str = strs[0].charAt(i);
for (let j = 0; j < strs.length; j++) {
if (str !== strs[j].charAt(i)) {
return commonPrefix;
}
}
commonPrefix += str;
}
return commonPrefix;
}
// 方案2:减少第一次的对比(j=1,获取一次字符串的长度)
// 64 ms, 在所有 javascript 提交中击败了 94.05%
// 两次测试时间不同。原理上第二次更简单
function longestCommonPrefixPro(strs) {
const { length } = strs;
if (length === 0) return '';
if (length === 1) {
return strs[0];
}
// 如果长度大于1,那么需要获取公共字符串前缀
let commonPrefix = '';
for (let i = 0, len = strs[0].length; i < len; i++) {
const str = strs[0].charAt(i);
for (let j = 1; j < length; j++) {
if (str !== strs[j].charAt(i)) {
return commonPrefix;
}
}
commonPrefix += str;
}
return commonPrefix;
}
// 方案3
function findLongestCommonPrefix(strs) {
if (strs.length === 0) {
return '';
}
let key = '';
const fn = function (str) {
for (let i = 0; i < strs.length; i++) {
if (strs[i].indexOf(str) !== 0) {
return false;
}
}
return true;
};
let j = 0;
while (key !== strs[0]) {
const q = (key + strs[0][j++]);
if (!fn(q)) {
break;
}
key = q;
}
return key;
}
export { longestCommonPrefix, longestCommonPrefixPro, findLongestCommonPrefix };
|
0015-threeSum.js
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
|
/**
* @param {number[]} nums
* @return {number[][]}
*/
// 思路1 超时
// 现在这种方法超出时间限制(数组长度是1000,那么两层循环性能不好)
const threeSumTmp = function (nums) {
const resultArr = [];
const len = nums.length;
// 把数组先排序
nums.sort((a, b) => a - b);
for (let i = 0; i < len; i++) {
// 当前的值是 current,那么剩下两个值的和应该是 -current;
const current = nums[i];
const target = current * -1;
const hash = {};
for (let j = i + 1; j < len; j++) {
const item = nums[j];
if (hash[item] > -1) {
const result = [nums[i], nums[j], nums[hash[item]]];
resultArr.push(result);
}
hash[target - item] = j;
}
}
// 现在的结果有重复
// [[-1,1,0],[-1,2,-1],[-1,1,0]]
// 最后结果数组去重
let tmp = [];
resultArr.forEach((item) => {
const str = item.join(',');
tmp.push(str);
});
tmp = [...new Set(tmp)];
const res = [];
tmp.forEach((item) => {
const arr = item.split(',');
res.push(arr);
});
// [ -4, -1, -1, 0, 1, 2 ]
return res;
};
// 思路二:可以实现,性能不好
// 820 ms , 在所有 JavaScript 提交中击败了5.09%的用户
const threeSum = function (nums) {
const resultArr = [];
const len = nums.length;
// 把数组先排序
nums.sort((a, b) => a - b);
// 排序后,如果第一个元素大于0,或者最后一个元素小于0,那么无解,返回空数组
if (nums[0] > 0 || nums[len - 1] < 0) {
return [];
}
const hash = {};
for (let i = 0; i < len; i++) {
// 当前的值是 current,那么剩下两个值的和应该是 -current;
const current = nums[i];
const target = current * -1;
// 使用双指针实现
let start = i + 1;
let end = len - 1;
while (start < end) {
if (nums[start] + nums[end] === target) {
const hs = [nums[i], nums[start], nums[end]];
// 因为现在已经是排序的,写一个哈希表,避免重复数据
const key = `${nums[i]}+${nums[start]}+${nums[end]}`;
if (!hash[key]) {
resultArr.push(hs);
hash[key] = true;
}
start++;
// 这里性能不好,处理重复数据会消耗大量时间[00000000]
} else if (nums[start] + nums[end] < target) {
start++;
} else if (nums[start] + nums[end] > target) {
end--;
}
}
}
return resultArr;
};
export { threeSum, threeSumTmp };
|
0017-letterCombinations.js
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
|
// 17 电话号码九键字母组合
// 在九键按键电话,写短信需要对应(2-9分别对应字母)
// 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
// 思路一:如果每一个数值遍历一次,那么时间复杂度是2N次方(不好)
// 思路二:用空间换时间
// 数字的遍历是必须的,那么结果数组中,使用新的数组迭代旧数组,这样会用到 n 个中间变量
// 1 遇到第一个2,创建一个长度是3的数组,每一个填充[abc]
// 2 遇到3,遍历上面的数组,复制每一条信息,插入对应的三条 splice(i, 1, newArray) [ad,ae,af,...];
// 执行用时 : 64 ms , 在所有 JavaScript 提交中击败了90.94% 的用户
// 内存消耗 : 33.8 MB , 在所有 JavaScript 提交中击败了23.48% 的用户
function letterCombinations(digits) {
let resultArr = [];
if (digits.length === 0) return resultArr; // 处理传入为空
const dir = {
2: 'abc',
3: 'def',
4: 'ghi',
5: 'jkl',
6: 'mno',
7: 'pqrs',
8: 'tuv',
9: 'wxyz',
};
// 循环数字
while (digits.length > 0) {
// 获取当前数字对应的字符串
const firstNumber = digits.slice(0, 1);
digits = digits.slice(1, digits.length);
const firstStr = dir[firstNumber];
// 把这个字符串加入到结果数组中
const newResultArr = [];
if (resultArr.length === 0) {
resultArr = firstStr.split('');
} else {
for (let i = 0; i < resultArr.length; i++) {
const item = resultArr[i];
newResultArr.push(item + firstStr[0], item + firstStr[1], item + firstStr[2]);
// 处理一下特殊的7和9
if (firstStr[3]) newResultArr.push(item + firstStr[3]);
}
// 使用新的结果数组迭代旧的结果数组
resultArr = newResultArr;
}
}
return resultArr;
}
export { letterCombinations };
|
0018-fourSum.js
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
|
// 18 四数之和
// 给定一个包含 n 个整数的数组 nums(有重复值) 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。答案中不可以包含重复的四元组。
// 思路一:暴力循环4次,这样可以数组的内部四个数的全部子集。时间复杂度极高,不使用
// 思路二:四个数的和,分解成两个数的和,两个数的和。这两个和加起来的目标就是4。
// 这样先进行n2遍历,获取前两个数的和,然后计算余下的 n - 2 个数,获取两个,然后输出。
// 1748 ms, 在所有 JavaScript 提交中击败了 6.30% 的用户
// 时间复杂度的问题:主要是大量的数据转化,以及多重循环
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
function fourSum(nums, target) {
// 先计算两个数的和数组,把四个数的和问题转化成两个数的和问题,降维减少时间复杂度
const twoSumArr = [];
const len = nums.length;
for (let i = 0; i < len; i++) {
for (let j = i; j < len; j++) {
const obj = {};
obj.x = i;
obj.y = j;
obj.twoSum = nums[i] + nums[j];
// 这里最好过滤一下,如果和已有的重复,就不需要加入
twoSumArr.push(obj);
}
}
// 遍历这个和矩阵,如果和是目标,那么把对应的项数放到一个新的数组中
const resultArr = [];
const len2 = twoSumArr.length;
for (let i = 0; i < len2; i++) {
for (let j = i; j < len2; j++) {
if ((twoSumArr[i].twoSum + twoSumArr[j].twoSum) === target) {
const set = new Set([twoSumArr[i].x, twoSumArr[i].y, twoSumArr[j].x, twoSumArr[j].y]);
if (set.size === 4) {
// 这四个数都不等,才不会重用数字;现在可以保证项数不重复;但是原始数组中可能有重复的元素,例如0出现了两次
const arr = [nums[twoSumArr[i].x], nums[twoSumArr[i].y], nums[twoSumArr[j].x], nums[twoSumArr[j].y]];
// 然后,二维数组排序后,转化成字符串去重
const arrStr = arr.sort().join(',');
if (!resultArr.includes(arrStr)) {
resultArr.push(arrStr);
}
}
}
}
}
// 结果数组转化成二维数组(升维)
for (let i = 0; i < resultArr.length; i++) {
resultArr[i] = resultArr[i].split(',').map(Number);
// array.map(Number) 把字符串数组转化成数值类型
}
return resultArr;
}
export { fourSum };
|
0020-valid-parenthese.js
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
|
// 检测括号有效性
// 如果是左半部分,不用管,直接放在另一个字符串;
// 如果是右半部分,必须在左边找到这个对应的字符串,位置需要正确,然后删除,否则返回false
// 如果最后结果字符串长度是0,那么就是有效的括号字符串
// 2019年:56 ms , 在所有提交中击败了 99.60% 的用户
// 2020-01-11:92 ms, 在所有提交中击败了16.05%
function isValid(s) {
let result = '';
const left = ['(', '[', '{'];
for (let i = 0, len = s.length; i < len; i++) {
if (left.includes(s.charAt(i))) {
result += s.charAt(i);
} else if ((s.charAt(i) === ')' && result.charAt(result.length - 1) === '(') || (s.charAt(i) === ']' && result.charAt(result.length - 1) === '[') || (s.charAt(i) === '}' && result.charAt(result.length - 1) === '{')) {
result = result.substr(0, result.length - 1);
} else {
return false;
}
}
return result.length === 0;
}
// 优化方法 不需要数组
// 68 ms, 在所有提交中击败了60.38%
function isValid2(s) {
let result = '';
for (let i = 0, len = s.length; i < len; i++) {
const item = s.charAt(i);
if (item === '(' || item === '[' || item === '{') {
result += item;
} else if ((item === ')' && result.charAt(result.length - 1) === '(') || (item === ']' && result.charAt(result.length - 1) === '[') || (item === '}' && result.charAt(result.length - 1) === '{')) {
result = result.substr(0, result.length - 1);
} else {
return false;
}
}
return result.length === 0;
}
export { isValid, isValid2 };
|
0095-generateTrees.js
0095. 不同的二叉搜索树
95 不同的二叉搜索树(以1-N为节点,可以建立多少个不同的二叉搜索树?)
问题关键是:二叉搜索树左子树小于当前节点,右子树大于当前节点。那么遍历1-N作为根节点,然后设置 1——1-i是左子树,i+1 —— n 是右子树,然后递归遍历子树的情况。最后把左右子树双重循环即可找到全部的结果。注意:如果一个位置是空的,也需要返回 Null 节点。
0097-isInterleave.js
0097. 交错字符串
97 交错字符串:
思路一,使用DFS递归字符串,并先判断是否全部字符的个数相同,这样可以实现,性能比较差。
思路二:使用动态规划,从开头一直递推到最后的一个值。
思路三:回溯算法+三指针+字典记忆化
0098-isValidBST.js
0098. 验证二叉搜索树
98 验证二叉搜索树
二叉搜索树的性质是,节点值大于左子树最大值,节点值小于右子树最小值。
思路一:使用递归方法,先构建一个递归函数 checkTree(node, small, large),判断每一个子树的最大最小值。只需要判断当前节点,在递归中判断子节点即可.
思路二:二叉搜索树的中序遍历结果,是一个升序数组,那么根据这个性质,遍历二叉树。如果不是升序数组,就不是二叉搜索树。
0103-zigzagLevelOrder.js
0103. 二叉树的锯齿形层序遍历
题目 0103 难度简单,认真一次性可以做出来
锯齿状层序遍历二叉树(先把二叉树 BFS 层序遍历,然后加一个层序号属性;然后把列表根据层序号再转换成二维数组即可)
时间复杂度是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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
const zigzagLevelOrder = function(root) {
if (!root) return [];
// 先把二叉树层序遍历(广度优先遍历),同时增加layer层数
const res = [];
const list = [];
list.push({
node: root,
layer: 0,
});
while (list.length > 0) {
const tmp = list.shift();
res.push({ val: tmp.node.val, layer: tmp.layer });
const layer = tmp.layer;
const left = tmp.node.left;
const right = tmp.node.right;
if (left) {
list.push({ node: left, layer: layer + 1 });
}
if (right) {
list.push({ node: right, layer: layer + 1 });
}
}
// 再次遍历数组,把每一层的结果都放入临时数组,然后返回
const result = [];
let flagLayer = 0;
let tmpArr = [];
for (let i = 0; i < res.length; i++) {
const { layer, val } = res[i];
if (layer === flagLayer) {
tmpArr.push(val);
} else {
// 这里需要根据层数,决定是否取反
if (flagLayer % 2 === 0) {
result.push([...tmpArr]);
} else {
result.push([...tmpArr].reverse());
}
tmpArr = [];
tmpArr.push(val);
flagLayer = layer;
}
}
// 最后还需要处理一层
if (flagLayer % 2 === 0) {
result.push([...tmpArr]);
} else {
result.push([...tmpArr].reverse());
}
return result;
};
|
0105-buildTree.js
0105. 从前序遍历和中序遍历数组中恢复二叉树
从前序遍历和中序遍历数组中(无重复元素),恢复二叉树.找到根元素和左右子树,然后递归即可
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
|
const buildTree = function(preorder, inorder) {
// 如果根节点不存在,直接返回空树节点
if (!preorder || preorder.length === 0) {
return new TreeNode(null);
}
// 如果只有一个根节点,那么直接返回这个节点(没有子树)
if (preorder.length === 1) {
return new TreeNode(preorder[0]);
}
const rootVal = preorder[0];
const index = inorder.indexOf(rootVal);
const rootNode = new TreeNode(rootVal);
const leftInorder = inorder.slice(0, index);
const rightInorder = inorder.slice(index + 1);
const leftPreOrder = preorder.slice(1, index + 1);
const rightPreOrder = preorder.slice(index + 1);
if (leftPreOrder.length > 0) {
rootNode.left = buildTree(leftPreOrder, leftInorder);
}
if (rightPreOrder.length > 0) {
rootNode.right = buildTree(rightPreOrder, rightInorder);
}
return rootNode;
};
|
0106-buildTree.js
0106.从中序遍历和后续遍历中恢复二叉树
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
|
const buildTree = function(inorder, postorder) {
// 如果根节点不存在,直接返回空树节点
if (!postorder || postorder.length === 0) {
return new TreeNode(null);
}
// 如果只有一个根节点,那么直接返回这个节点(没有子树)
if (postorder.length === 1) {
return new TreeNode(postorder[0]);
}
const rootVal = postorder[postorder.length - 1];
const index = inorder.indexOf(rootVal);
const rootNode = new TreeNode(rootVal);
const leftInorder = inorder.slice(0, index);
const rightInorder = inorder.slice(index + 1);
const leftPostOrder = postorder.slice(0, index);
const rightPostOrder = postorder.slice(index, postorder.length - 1);
if (leftPostOrder.length > 0) {
rootNode.left = buildTree(leftInorder, leftPostOrder);
}
if (rightPostOrder.length > 0) {
rootNode.right = buildTree(rightInorder, rightPostOrder);
}
return rootNode;
};
|
0109-sortedListToBST.js
0109. 有序链表转换成二叉搜索树
题目109
有序链表转换成二叉搜索树
1、思路:先把有序链表转换成有序数组,然后转换成二叉搜索树。
2、官方思路:使用快慢指针,获取有序链表的中点,然后左右分别作为开始和结束,转换成子树作为树节点。关键是记录开始节点和结束节点的位置(链表中记录位置的方案)
二叉搜索树的核心:就是找到中位数作为根节点
1、可以使用快慢指针,获取排序链表的中位数。当快指针到达最后一位时,慢指针正好是中位数
2、设置中位数的是根节点,然后左子树和右子树的边界
可以确定,递归左右子树即可注意:链表找到元素的索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
const sortedListToBST = function(head) {
// 先把有序列表转换成有序数组
const arr = [];
while (head) {
arr.push(head.val);
head = head.next;
}
// 然后把有序数组转换成二叉搜索树
function arr2Tree(start, end) {
if (start > end) {
return null;
} else if (start === end) {
return new TreeNode(arr[start]);
} else {
const middle = Math.floor((start + end) / 2);
const node = new TreeNode(arr[middle]);
node.left = arr2Tree(start, middle - 1);
node.right = arr2Tree(middle + 1, end);
return node;
}
}
return arr2Tree(0, arr.length - 1);
};
|
0114-flatten.js
0114. 二叉树展开为链表
题目114 简单
二叉树展开为链表
先把二叉树先序遍历成数组,然后遍历数组,给数组的每一个元素增加引用,实现单链表
0116-connect.js
0116. 完美二叉树中填充每个节点的next指针
116 完美二叉树中,填充每个节点的next指针,下一个右侧节点。实际上就是先把二叉树层序遍历成矩阵,然后遍历每一个子数组,设置指针指向下一个节点即可。
0135-candy.js
0135. 分糖果
困难
关键是双向贪心算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
const candy = function(ratings) {
let result = Array(ratings.length).fill(1);
// 从左向右遍历
for (let i = 1; i < result.length; i++) {
if (ratings[i] > ratings[i - 1]) {
result[i] = result[i - 1] + 1;
}
}
// 从右向左遍历(技巧点)
for (let i = result.length - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
result[i] = Math.max(result[i], result[i + 1] + 1);
}
}
// 计算结果
return result.reduce((a, b) => a + b, 0);
};
|
0152-maxProduct.js
0152. 最大乘积子数组
最大乘积子数组:动态规划思路,和最大和子数组类似。Fx = F(x-1) 的递推式。如果全部是正数,那么完全一致。如果有正数有负数,当前的最大值,可能是前面的最小值和当前值的乘积,所以需要维护两个数组,分别存放最大值和最小值的递推式。
0164-maximumGap.js
0164. 最大间距
最大间距:常规的思路:数组快速排序,然后循环一次,获取最大间距(快速排序是N*logN)。
因为题目要求是N,这个方法严格上不合适。
官方给出的思路是基数排序或者桶排序。实际中直接使用内置的排序即可。
0166-fractionToDecimal.js
0166. 分数到小数
166 分数到小数(需要细心)
关键点:
1、两个整数相除,结果可能是整数,小数,或者循环小数。不可能是无限不循环小数,这个是数学原理。
2、如何判断是循环小数?每次计算,如果余数出现相同,那么就是循环小数,所以适应一个对象记录出现的余数,使用一个数组记录出现的位置,即可把循环的部分获取到。
0174-calculateMinimumHP.js
0174. 地下城与勇士
困难,二维矩阵反向动态规划,每一个单元格条件判断。
如果使用递归,需要加入记忆化搜索(缓存)
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
|
var calculateMinimumHP2 = function (dungeon) {
const m = dungeon.length;
const n = dungeon[0].length;
// 初始化一个二维数组来存储所需的最小生命值
const dp = Array.from({ length: m }, () => new Array(n));
// 初始化右下角单元格
dp[m - 1][n - 1] = Math.max(1, 1 - dungeon[m - 1][n - 1]);
// 初始化最右侧一列
for (let i = m - 2; i >= 0; i--) {
dp[i][n - 1] = Math.max(1, dp[i + 1][n - 1] - dungeon[i][n - 1]);
}
// 初始化最下面一行
for (let j = n - 2; j >= 0; j--) {
dp[m - 1][j] = Math.max(1, dp[m - 1][j + 1] - dungeon[m - 1][j]);
}
// 填充其余的单元格
for (let i = m - 2; i >= 0; i--) {
for (let j = n - 2; j >= 0; j--) {
const minNext = Math.min(dp[i + 1][j], dp[i][j + 1]);
dp[i][j] = Math.max(1, minNext - dungeon[i][j]);
}
}
return dp[0][0];
}
|
0199-rightSideView.js
0199. 二叉树的右视图
二叉树的右视图:先把二叉树层序遍历,然后获取每一层的最后一个元素即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
const rightSideView = function(root) {
// 复用102代码(层序遍历)
const matrix = [];
if (!root) return [];
// 辅助函数:二叉树层序遍历
const runTree = function(node, layer) {
if (!node) return;
if (!matrix[layer]) {
matrix[layer] = [];
}
matrix[layer].push(node.val);
runTree(node.left, layer + 1);
runTree(node.right, layer + 1);
};
const layer = 0;
if (!matrix[layer]) {
matrix[layer] = [];
}
matrix[layer].push(root.val);
runTree(root.left, layer + 1);
runTree(root.right, layer + 1);
return matrix.map((arr) => arr[arr.length - 1]);
};
|
1052-maxSatisfied.js
1052.爱生气的书店老板
困难,运行通过
https://leetcode.cn/problems/grumpy-bookstore-owner/
1
2
3
4
5
6
7
|
/**
* @param {number[]} customers
* @param {number[]} grumpy
* @param {number} minutes
* @return {number}
* 最大顾客数 = 各种情况下都不生气的顾客(staticValue) + 老板心情好时不生气的顾客(slide window, max)
*/
|
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
|
var maxSatisfied = function(customers, grumpy, minutes) {
const len = customers.length;
// 各种情况下都不生气的顾客(固定值)
let staticValue = 0;
for (let i = 0; i < len; i++) {
// 不生气 0 时,有效的顾客
if (grumpy[i] === 0) {
staticValue += (customers[i]);
}
}
// 计算窗口初始化的最大值
let max = 0;
let curr = 0;
for (let i = 0; i < minutes; i++) {
if (grumpy[i] === 1) {
curr += customers[i];
}
}
// 这是窗口初始化的最大值
max = curr;
// 滑动窗口开始
for (let i = minutes; i < len; i++) {
if (grumpy[i] === 1) {
curr += customers[i];
}
if (grumpy[i - minutes] === 1) {
curr -= customers[i - minutes];
}
if (curr > max) {
max = curr;
}
}
return staticValue + max;
};
|
单元测试
1
2
3
4
5
6
7
8
|
// console.log(maxSatisfied([4,10,10], [1,1,0], 2) === 24);
// console.log(maxSatisfied([3], [1], 1) === 3);
// console.log(maxSatisfied([1], [1], 1) === 1);
// console.log(maxSatisfied([1,0,1,2,1,1,2,5,2,2,2,1], [0,1,0,1,0,1,0,1,0,0,1,1], 5) === 17);
// console.log(maxSatisfied([1,0,1,2,1,1,7,5], [0,1,0,1,0,1,0,1], 3) === 16);
// console.log(maxSatisfied([2,6,6,9], [0,0,1,1], 1) === 17);
// python 解答 https://zhuanlan.zhihu.com/p/352407806
|
1146-SnapshotArray.js
1146. 快照数组 SnapshotArray
困难
https://leetcode.cn/problems/snapshot-array/solutions/2016347/zhi-ji-lu-xiu-gai-by-masx200-zguh/
因为操作比较多,需要综合考虑空间复杂度和时间复杂度,所以需要字典存储+二分查找
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
|
// 解法三:优化,字典数组+二分查找(时间复杂度和空间复杂度都可以满足)
// https://leetcode-cn.com/problems/snapshot-array/solution/zi-jie-leetcode1146kuai-zhao-shu-zu-by-user7746o/
let SnapshotArray = function(length) {
// 这里必须 fill 否则 map 会跳过
this.arr = new Array(length).fill(0).map(() => new Map());
this.snapId = 0;
};
SnapshotArray.prototype.set = function(index, val) {
this.arr[index].set(this.snapId, val);
};
SnapshotArray.prototype.snap = function() {
this.snapId++;
return this.snapId - 1;
};
SnapshotArray.prototype.get = function(index, snap_id) {
// 找到这个数的所有记录
let snapIds = [...this.arr[index].keys()]
// 二分查找,找到 <= snap_id 的值
let start = 0;
let end = snapIds.length - 1;
let mid;
while (start <= end) {
mid = Math.floor((start + end) / 2);
if (snapIds[mid] < snap_id) {
start = mid + 1;
} else if (snapIds[mid] > snap_id) {
end = mid - 1;
} else if (snapIds[mid] === snap_id) {
return this.arr[index].get(snap_id);
}
}
return this.arr[index].get(snapIds[start - 1]) || null;
};
|
1267-countServers.js
1267. 统计参与通信的服务器
简单:循环矩阵,哈希表计数,一次可以写出来
https://leetcode.cn/problems/count-servers-that-communicate/description/
1
2
3
4
5
6
7
8
|
/**
* @param {number[][]} grid
* @return {number}
* BFD 或者 DFS 都可以实现,当然也可以用其他思路
* 先获取每行每列中是 1 的个数,构成两个字典
* 然后遍历全部的节点,如果在字典中任意一个大于1,那么就可以通信
* 这个可以实现,性能略有点差
*/
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var countServers = function(grid) {
let rowDict = {};
let columnDict = {};
// 循环全部节点,获取出现的次数,写入到对应的字典中
for (let row = 0; row < grid.length; row++) {
for (let col = 0; col < grid[row].length; col++) {
if (grid[row][col] === 1) {
rowDict[row] = (rowDict[row] || 0) + 1;
columnDict[col] = (columnDict[col] || 0) + 1;
}
}
}
let result = 0;
for (let row = 0; row < grid.length; row++) {
for (let col = 0; col < grid[row].length; col++) {
if (grid[row][col] === 1 && (rowDict[row] > 1 || columnDict[col] > 1)) {
result++;
}
}
}
return result;
};
|
2682-circularGameLosers.js
2682. 找出转圈游戏输家
中等,运行通过
数组循环,注意特殊情况就行
https://leetcode.cn/problems/find-the-losers-of-the-circular-game/description/
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
|
/**
* @param {number} n
* @param {number} k
* @return {number[]}
*/
var circularGameLosers = function(n, k) {
let dict = {};
dict[1] = true;
let index = 1; // 当前球的位置
// 循环,直到某个朋友接到球
// 中间某个人接到球,就记录在字典中
for (let i = 1; i < 10000; i++) {
let time = i * k;
index = (index + time) % n;
// 如果转了一圈,就是最后一个数字
if (index === 0) {
index = n;
}
// 有人第二次接到球,游戏结束
if (dict[index]) {
break;
} else {
dict[index] = true;
}
}
// 把全部字典的键遍历一次,然后求差值,就是结果数组
let result = [];
for (let i = 2; i <= n; i++) {
if (!dict[i]) {
result.push(i);
}
}
return result;
};
// console.log(circularGameLosers(5, 2)); // [4,5]
// console.log(circularGameLosers(4, 4)); // [2,3,4]
// console.log(circularGameLosers(2, 1)); // []
// console.log(circularGameLosers(5, 3)); // [2,3]
|
2703-argumentsLength.js
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* @return {number}
*/
const argumentsLength = function(...args) {
return args.length;
};
/**
* argumentsLength(1, 2, 3); // 3
*/
export { argumentsLength };
|
2704-expect.js
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
|
/**
* @param {string} val
* @return {Object}
*/
const expect = function(val) {
return {
toBe: (value) => {
if (val === value) {
return true;
}
throw new Error('Not Equal');
},
notToBe: (value) => {
if (val !== value) {
return true;
}
throw new Error('Equal');
},
};
};
/**
* expect(5).toBe(5); // true
* expect(5).notToBe(5); // throws "Equal"
*/
// () => expect(5).toBe(null)
// {"error":"Not Equal"}
export { expect };
|
2705-compactObject.js
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
|
/**
* @param {Object|Array} obj
* @return {Object|Array}
*/
const compactObject1 = function (obj) {
// 基本思路:DFS 递归对象; 复杂对象超时
const dfs = (node) => {
if (typeof obj !== 'object') {
return obj;
}
if (Array.isArray(node)) {
for (let i = 0; i < node.length; i++) {
if (!node[i]) {
// 数组 splice 耗时
node.splice(i, 1);
i--;
}
// 如果是对象,直接递归
else {
dfs(node[i]);
}
}
} else {
// 这里存在性能问题
for (const key in node) {
if (!node[key]) {
// 删除对象的键耗时
delete node[key];
}
if (typeof node[key] === 'object') {
dfs(node[key]);
}
}
}
};
dfs(obj);
return obj;
};
// https://leetcode.cn/problems/compact-object/description/
// 现在还是超时,需要看一下官方的方案是什么,自己哪一步存在性能问题
// console.log(compactObject([null, 0, false, 1]));
// 输出:[1]
// console.log(compactObject({ a: null, b: [false, 1] }));
// 输出:{"b": [1]}
// console.log(compactObject([null, 0, 5, [0], [false, 16]]));
// 输出:[5, [], [16]]
// 思路2
const compactObject2 = function (obj) {
if (obj == null) {
return obj;
}
if (typeof obj !== 'object') {
return obj;
}
// 改进后的方法,新建一个数组和对象,然后向内部填充
if (Array.isArray(obj)) {
const res = [];
for (const value of obj) {
const val = compactObject(value);
if (val) {
res.push(val);
}
}
return res;
} else {
const res = {};
for (const key of Object.keys(obj)) {
const val = compactObject(obj[key]);
if (val) {
res[key] = val;
}
}
return res;
}
};
export { compactObject1, compactObject2 };
|
2706-buyChoco.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* @param {number[]} prices
* @param {number} money
* @return {number}
*/
const buyChoco = function(prices, money) {
prices = prices.sort((a, b) => a > b ? 1 : -1);
if (prices[0] + prices[1] > money) {
return money;
}
return money - prices[0] - prices[1];
};
export { buyChoco };
|
2710-removeTrailingZeros.js
1
2
3
4
5
6
7
8
9
|
/**
* @param {string} num
* @return {string}
*/
const removeTrailingZeros = function(num) {
return num.replace(/0+$/, '');
};
export { removeTrailingZeros };
|
2722-join.js
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
|
/**
* @param {Array} arr1
* @param {Array} arr2
* @return {Array}
*/
const join = function(arr1, arr2) {
// 1 把 arr1 转换成 map
const dict = {};
arr1.forEach((item) => {
dict[item.id] = item;
});
// 2 遍历 arr2 如果不重复,直接放入 map; 如果 ID 重复,那么 Object.assign() 合并属性
arr2.forEach((item) => {
const id = item.id;
if (!dict[id]) {
dict[id] = item;
} else {
dict[id] = { ...dict[id], ...item };
}
});
// 3 遍历 map-key 转换成数组,然后根据 ID 排序输出
let arr3 = [];
for (const key in dict) {
arr3.push(dict[key]);
}
arr3 = arr3.sort((a, b) => {
return a.id > b.id ? 1 : -1;
});
return arr3;
};
export { join };
|
2723-addTwoPromises.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* @param {Promise} promise1
* @param {Promise} promise2
* @return {Promise}
*/
const addTwoPromises = async function(promise1, promise2) {
return await promise1 + await promise2;
};
/**
* addTwoPromises(Promise.resolve(2), Promise.resolve(2))
* .then(console.log); // 4
*/
export { addTwoPromises };
|
2724-sortBy.js
test
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* @param {Array} arr
* @param {Function} fn
* @return {Array}
*/
const sortBy = function(arr, fn) {
return arr.sort((a, b) => {
return fn(a) > fn(b) ? 1 : -1;
});
};
export { sortBy };
|
2725-cancellable.js
test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* @param {Function} fn
* @param {Array} args
* @param {number} t
* @return {Function}
*/
const cancellable = function(fn, args, t) {
// 0s 先执行一次函数
fn(...args);
// 间隔 t 执行一次函数
const timer = setInterval(() => {
fn(...args);
}, t);
// 返回取消定时器的函数
return () => { clearInterval(timer); };
};
export { cancellable };
|
2726-Calculator.js
test
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
|
class Calculator {
/**
* @param {number} value
*/
constructor(value) {
this.value = value;
this.error = false;
}
/**
* @param {number} value
* @return {Calculator}
*/
add(value) {
if (!this.error) this.value = this.value + value;
return this;
}
/**
* @param {number} value
* @return {Calculator}
*/
subtract(value) {
if (!this.error) this.value = this.value - value;
return this;
}
/**
* @param {number} value
* @return {Calculator}
*/
multiply(value) {
if (!this.error) this.value = this.value * value;
return this;
}
/**
* @param {number} value
* @return {Calculator}
*/
divide(value) {
if (!this.error) {
if (value === 0) {
this.value = 'Division by zero is not allowed';
this.error = true;
return this;
}
this.value = this.value / value;
}
return this;
}
/**
* @param {number} value
* @return {Calculator}
*/
power(value) {
if (!this.error) this.value = this.value ** value;
return this;
}
/**
* @return {number}
*/
getResult() {
if (this.error) {
return 'Division by zero is not allowed';
}
return this.value;
}
}
export { Calculator };
|
2727-isEmpty.js
简单
1
2
3
4
5
6
7
8
9
|
/**
* @param {Object | Array} obj
* @return {boolean}
*/
const isEmpty = function(obj) {
return Object.keys(obj).length === 0;
};
export { isEmpty };
|
2778-sumOfSquares.js
2778. 特殊元素平方和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* @param {number[]} nums
* @return {number}
*/
var sumOfSquares = function(nums) {
const len = nums.length;
let result = 0;
for (let i = 0; i < len; i++) {
if (len % (i + 1) === 0) {
result += (nums[i] * nums[i]);
}
}
return result;
};
|
2784-isGood.js
2784. 检查数组是否是好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var isGood = function(nums) {
const len = nums.length;
nums = nums.sort((a, b) => a > b ? 1 : -1);
for (let i = 0; i < len; i++) {
if (i === len - 1) {
if (nums[i] !== nums[i - 1]) {
return false;
}
} else {
if (nums[i] !== i + 1) {
return false;
}
}
}
return true;
};
|
2798-numberOfEmployeesWhoMetTarget.js
test
1
2
3
4
5
6
7
8
9
10
11
|
const numberOfEmployeesWhoMetTarget = function(hours, target) {
let num = 0;
for (let i = 0; i < hours.length; i++) {
if (hours[i] >= target) {
num++;
}
}
return num;
};
export { numberOfEmployeesWhoMetTarget };
|
2811-canSplitArray.js
2811. 判断是否能拆分数组
困难
https://leetcode.cn/problems/check-if-it-is-possible-to-split-array/description/
考点:动态规划,贪心算法(脑筋急转弯一般想不出来),难度中等(自己尝试几遍,想到了正确思路,然后做出来了,可能有点费时间)
思路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
|
var canSplitArray = function(nums, m) {
// 思路:先求数组的和,然后双指针(前后指针分别向中间靠拢),然后和逐渐减少
// 如果左右同时满足,那么减去左右中最小的一个(贪心算法?)
const len = nums.length;
if (len < 3) {
return true;
}
let sum = nums.reduce((a, b) => { return a + b; }, 0);
let left = 0;
let right = len - 1;
// left right
// 如果这样的思路,不满足实例3——这需要动态规划?
while (sum >= m && left + 1 < right) {
// 左边的小于右边的,且减去左边后还满足条件
if (nums[left] < nums[right] && sum - nums[left] > m) {
sum = sum - nums[left];
left++;
}
// 左边的大于右边的,且减去右边后还满足条件
else if (nums[left] < nums[right] && sum - nums[right] > m) {
sum = sum - nums[right];
right--;
}
// 两边都不满足,直接跳出循环
else {
break;
}
}
if (left + 1 === right) {
return true;
} else {
return false;
}
};
|
思路2:动态规划(递归调用,超时了),思路基本正确,代码需要改进
递推公式
Fn(start, end) = Fn(start + 1, end) || Fn(start, end - 1)
F(1, 3) = (a1 + a2) >= m || (a2 + a3) >= m;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
var canSplitArray = function(nums, m) {
const len = nums.length;
if (len < 3) {
return true;
}
let dict = {};
function fn(a, b) {
// 从缓存中获取结果,避免大量递归
let key = `${a}-${b}`;
if (dict[key]) {
return dict[key];
}
let result;
if (a + 2 === b) {
result = (nums[a] + nums[a + 1]) >= m || (nums[a + 1] + nums[a + 2]) >= m;
} else {
result = fn(a + 1, b) || fn(a, b - 1);
}
dict[key] = result;
return result;
}
return fn(0, nums.length - 1);
};
|
思路3:动态规划改进版
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
|
var canSplitArray = function(nums, m) {
const len = nums.length;
if (len < 3) {
return true;
}
if (len === 3) {
return (nums[0] + nums[1]) >= m || (nums[1] + nums[2]) >= m;
}
// 先计算每个三元数对的值
let dict = {};
for (let i = 0; i < len; i++) {
if (nums[i] && nums[i + 2]) {
let result = (nums[i] + nums[i + 1]) >= m || (nums[i + 1] + nums[i + 2]) >= m;
dict[`${i}-${i+2}`] = result;
}
}
// 然后循环长度,计算最终的结果
for (let length = 3; length < len; length++) {
for (let start = 0; start < len; start++) {
if (nums[start] && nums[start+length]) {
let result = dict[`${start+1}-${start+length}`] || dict[`${start}-${start+length-1}`];
dict[`${start}-${start+length}`] = result;
}
}
}
return dict[`${0}-${len - 1}`];
};
|
继续优化代码(减少了两层 if 判断,但是时间复杂度提升不大)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
var canSplitArray = function(nums, m) {
const len = nums.length;
if (len < 3) {
return true;
}
if (len === 3) {
return (nums[0] + nums[1]) >= m || (nums[1] + nums[2]) >= m;
}
// 先计算每个三元数对的值
let dict = {};
for (let i = 0; i < len - 2; i++) {
let result = (nums[i] + nums[i + 1]) >= m || (nums[i + 1] + nums[i + 2]) >= m;
dict[`${i}-${i+2}`] = result;
}
// 然后循环长度,计算最终的结果
for (let length = 3; length < len; length++) {
for (let start = 0; start < len - length; start++) {
let result = dict[`${start+1}-${start+length}`] || dict[`${start}-${start+length-1}`];
dict[`${start}-${start+length}`] = result;
}
}
return dict[`${0}-${len - 1}`];
};
|
可以参考高赞答案,这个确实没有想到,性能差距很大
深度搜索 + 记忆化 + 前缀和;
https://leetcode.cn/problems/check-if-it-is-possible-to-split-array/solutions/2375201/todoline-shen-du-sou-suo-ji-yi-hua-qian-mqn2a/
或者是脑筋急转弯
https://leetcode.cn/problems/check-if-it-is-possible-to-split-array/solutions/2375178/nao-jin-ji-zhuan-wan-by-endlesscheng-0l19/
2815-maxSum.js
2815. 数组中的最大数对和
简单:考点:数组循环+哈希表,可以直接做出来
https://leetcode.cn/problems/max-pair-sum-in-an-array/description/
思路:第一次循环,计算每一个数字的位数最大值,并存放在哈希表中。第二次直接双重循环数组,判断每一个数对是否满足条件;如果满足条件就求和,然后计算和的最大值。时间复杂度是 N^2 。这个思路比较好理解。
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
|
/**
* @param {number[]} nums
* @return {number}
*/
var maxSum = function(nums) {
// 基本思路:
let dict = {};
let max = -1;
// 辅助函数:获取某个数位最大的值
function getMax(num) {
let result = num % 10;
num = (num - num % 10) / 10;
while (num > 0) {
let curr = num % 10;
result = curr > result ? curr : result;
num = (num - num % 10) / 10;
}
return result;
}
// 第一次循环数组,把数字和对应的最大位数拿到,然后写入哈希表中
for (let i = 0; i < nums.length; i++) {
let item = nums[i];
dict[item] = getMax(item);
}
// 第二次直接循环两次数组,找到每一个数对,然后看最大是否满足,满足的话求和
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (dict[nums[i]] === dict[nums[j]]) {
let currMax = nums[i] + nums[j];
if (currMax > max) {
max = currMax;
}
}
}
}
return max;
};
|
其他大神有一次循环做出来的,类似桶排序,不是很好理解,思路如下。
https://leetcode.cn/problems/max-pair-sum-in-an-array/solutions/2385996/yi-ci-bian-li-by-endlesscheng-6zt9/
2624-snail
实现 snail(rowsCount,colsCount) 方法,该方法将 1D 数组转换为以蜗牛排序的模式的 2D 数组。无效的输入值应该输出一个空数组。当 rowsCount * colsCount !==``nums.length 时。这个输入被认为是无效的。
这个逻辑不太难,注意一维数组转换成二维矩阵,index 和 xy 值如何计算,然后就是反向填充
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
|
// https://leetcode.cn/problems/snail-traversal/
/**
* @param {number} rowsCount
* @param {number} colsCount
* @return {Array<Array<number>>}
*/
Array.prototype.snail = function(rowsCount, colsCount) {
// 和其他题目类似,把一维数组转换成 Z 自行二维数组输出
// 注意点:转换前后的 index 怎么处理;计数列反向填充
// 1、处理特殊情况
const arrLen = this.length;
if (arrLen !== rowsCount * colsCount) {
return [];
}
// 2、新建目标空数组
let matrix = new Array(rowsCount).fill(1);
matrix = matrix.map(col => new Array(colsCount));
// 3、循环原始数组,根据 index 计算出新的 matrix 的 xy 值,然后填入即可
for (let i = 0; i < arrLen; i++) {
let colIndex = Math.floor(i / rowsCount);
let rowIndex = (i - colIndex * rowsCount);
// 偶数列正向填充,奇数列反向填充
if (colIndex % 2 === 1) {
// 反向
matrix[rowsCount - 1 - rowIndex][colIndex] = this[i];
} else {
// 正向
matrix[rowIndex][colIndex] = this[i];
}
}
return matrix;
}
/**
* const arr = [1,2,3,4];
* arr.snail(1,4); // [[1,2,3,4]]
*/
|
2631-groupBy
编写一段可应用于所有数组的代码,使任何数组调用 array. groupBy(fn) 方法时,它返回对该数组 分组后 的结果。
数组 分组 是一个对象,其中的每个键都是 fn(arr[i]) 的输出的一个数组,该数组中含有原数组中具有该键的所有项。
提供的回调函数 fn 将接受数组中的项并返回一个字符串类型的键。
每个值列表的顺序应该与元素在数组中出现的顺序相同。任何顺序的键都是可以接受的。
请在不使用 lodash 的 _.groupBy 函数的前提下解决这个问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* @param {Function} fn
* @return {Object}
*/
Array.prototype.groupBy = function(fn) {
let resultObj = {};
for (let i = 0; i < this.length; i++) {
let item = this[i];
let key = fn(item);
if (!resultObj[key]) {
resultObj[key] = [];
}
resultObj[key].push(item);
}
return resultObj;
};
/**
* [1,2,3].groupBy(String) // {"1":[1],"2":[2],"3":[3]}
*/
|
2629-compose
请你编写一个函数,它接收一个函数数组 [f1, f2, f3,…, fn] ,并返回一个新的函数 fn ,它是函数数组的 复合函数 。
[f(x), g(x), h(x)] 的 复合函数 为 fn(x) = f(g(h(x))) 。
一个空函数列表的 复合函数 是 恒等函数 f(x) = x 。
你可以假设数组中的每个函数接受一个整型参数作为输入,并返回一个整型作为输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* @param {Function[]} functions
* @return {Function}
*/
var compose = function(functions) {
if (!functions || functions.length === 0) {
return function(x) {
return x;
}
}
// 把最后一个函数拿出来执行,然后递归前面的函数
let fn = functions.pop();
return function(x) {
return compose(functions)(fn(x));
}
};
export { compose };
|
2824-countPairs.js
2824. 统计和小于目标的下标对数目
JS
1
2
3
4
5
6
7
8
9
10
11
|
var countPairs = function(nums, target) {
let result = 0;
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if ((nums[i] + nums[j]) < target) {
result++;
}
}
}
return result;
};
|
TS
1
2
3
4
5
6
7
8
9
10
11
|
function countPairs(nums: number[], target: number): number {
let result: number = 0;
for (let i: number = 0; i < nums.length; i++) {
for (let j: number = i + 1; j < nums.length; j++) {
if ((nums[i] + nums[j]) < target) {
result++;
}
}
}
return result;
};
|
python3
1
2
3
4
5
6
7
8
|
class Solution:
def countPairs(self, nums: List[int], target: int) -> int:
result = 0
for i in range(0, len(nums)):
for j in range(i + 1, len(nums)):
if (nums[i] + nums[j]) < target:
result = result + 1
return result
|