1. 两数之和

题目连接:https://leetcode.cn/problems/two-sum/description/

题解

解题思路:画解算法:1. 两数之和
本人思路:使用两个 for 循环。
解题代码:

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 {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) { // j = i + 1 确保j不会等于i,也减少了重复。
if (nums[i] + nums[j] == target) return [i, j]
}
}
};

// 看了解题思路之后的代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
const m = new Map(); // 新建一个Map
nums.forEach((v, i) => m.set(v, i)); // 建立 值:索引 对应关系
for (let i = 0; i < nums.length; i++) {
let index = m.get(target - nums[i]) // 查找 map 里有没有target - nums[i],有的话返回索引。
if (index && index != i) return [i, index] // 如果两个索引不重复则返回
}
};

console.log(twoSum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 9));

2. 两数相加

题目连接:https://leetcode.cn/problems/add-two-numbers/

题解

解题思路:画解算法:2. 两数相加
本人思路:没有思路,因为这是链表相关题目,我没做过这类题,导致我tm用数组写了大半个小时。。。
解题代码:

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
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function (l1, l2) {
let head = null, tail = null, carry = 0
while (l1 || l2) {
// 获取值
let num1 = l1 ? l1.val : 0
let num2 = l2 ? l2.val : 0
let sum = num1 + num2 + carry
if (!head) head = tail = new ListNode(sum % 10) // 没有头结点的话创建链表。
else {
// 链表已经创建,不断往尾结点添加元素,并将尾结点向后移动。
tail.next = new ListNode(sum % 10)
tail = tail.next
}
carry = Math.floor(sum / 10)
if (l1) l1 = l1.next
if (l2) l2 = l2.next
}
// 如果最后 l1 和 l2 都没有了但是进位却不为0的话,将进位挂载到尾结点。
if (carry) tail.next = new ListNode(carry)
return head
};

// 下面函数在提交时没必要带,只是为了保证本地可以成功运行。
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val)
this.next = (next === undefined ? null : next)
}
console.log(addTwoNumbers(new ListNode(2, new ListNode(4, new ListNode(3))), new ListNode(5, new ListNode(6, new ListNode(4)))));

3. 无重复字符的最长子串

题目连接:https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/

题解

解题思路:画解算法: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
/**
* @param {string} s
* @return {number}
*/

// 右指针一直动,找到重复的之后左指针会 跳 到指定位置右指针再开始动。
var lengthOfLongestSubstring = function (s) {
let m = new Map()
// start 是左索引,i 是右索引,start 和 i 之间是无重复的字符
let start = max = 0
for (let i = 0; i < s.length; i++) {
if (m.get(s[i])) start = Math.max(m.get(s[i]), start) // 如果Map里有重复的值,把这个值的索引和start作比较,选取大的那个。
m.set(s[i], i + 1) // 如果 map 内没有则会添加,如果 map 内已经有该值则会覆盖。
max = Math.max(i - start + 1, max) // 更新最大值
}
return max
};


// 右指针先动,直到找到重复的。右指针停止,左指针开始动,直到删除重复的。以此往复,类似于毛毛虫蠕动般丝滑。
var lengthOfLongestSubstring = function (s) {
const occ = new Set();
const n = s.length;
// rk 是右索引,i 是左索引
let rk = -1, ans = 0;
for (let i = 0; i < n; ++i) {
if (i != 0) occ.delete(s.charAt(i - 1)); // 配合下面一直删除最左侧,直到删除重复的值才会继续进行下面代码
while (rk + 1 < n && !occ.has(s.charAt(rk + 1))) { // 如果rk后面还有值且这个值不再occ中的话,向occ内添加该值,并将rk向后移动。
occ.add(s.charAt(rk + 1));
++rk;
}
ans = Math.max(ans, rk - i + 1); // 更新最大值
}
return ans;
};

console.log(lengthOfLongestSubstring('abba'));

4. 寻找两个正序数组的中位数

题目连接:https://leetcode.cn/problems/median-of-two-sorted-arrays/

题解

解题思路:寻找两个有序数组的中位数
本人思路:不考虑时间复杂度的话这题很简单,合并排序找出中位数即可。(我也只会这样了…)
解题代码:

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
// 不考虑时间复杂度
var findMedianSortedArrays = function (nums1, nums2) {
let ls = [...nums1, ...nums2].sort((a, b) => a > b ? 1 : -1)
if (ls.length % 2 == 0) return (ls[ls.length / 2 - 1] + ls[ls.length / 2]) / 2
else return ls[Math.floor(ls.length / 2)]
};
// 看了解题思路之后的代码
var findMedianSortedArrays = function (nums1, nums2) {
// 先获取总长度和k(中间值的位置)
let len = nums1.length + nums2.length
let k = Math.floor(len % 2 == 0 ? len / 2 - 1 : len / 2)

while (nums1.length && nums2.length && k != 0) {
// 使用二分查找,设p指针为k的一半减一(因为有许多需要用k+1,就不直接减一了。)
let p = Math.floor(k / 2);
if (p < 1) p = 1
// 如果有数组长度少于p,则让p等于该数组长度的最后一个索引
if (nums1.length < p || nums2.length < p) p = nums1.length < p ? nums1.length : nums2.length;
// 比较两个值,小的一个数组p之前的都删除
if (nums1[p - 1] <= nums2[p - 1]) nums1.splice(0, p)
else nums2.splice(0, p)
// 减去删除的个数
k -= p
}
// 判断len是奇偶数
if (len % 2 == 0) {
// 偶数情况
// 如果有一个数组为空,则直接返回另一个数组的k和k+1的平均值
if (!nums1.length || !nums2.length) return nums1.length ? (nums1[k] + nums1[k + 1]) / 2 : (nums2[k] + nums2[k + 1]) / 2
else {
// 如果两个数组都不为空,则将两个数组的第一和第二个值进行排序,取前两个的平均值
let tmp = [nums1[0], nums1[1], nums2[0], nums2[1]].sort((a, b) => a > b ? 1 : -1)
return (tmp[0] + tmp[1]) / 2
}
} else {
// 奇数情况
// 如果有一个数组为空,则直接返回另一个数组的k对应的值
if (!nums1.length || !nums2.length) return nums1.length ? nums1[k] : nums2[k]
// 都不为空比较两个数组的第一个值。返回小的
else return nums1[0] < nums2[0] ? nums1[0] : nums2[0]
}
};

// 其他解法
var findMedianSortedArrays = (nums1, nums2) => {
let len1 = nums1.length, len2 = nums2.length
if (len1 > len2) return findMedianSortedArrays(nums2, nums1)//对nums1和nums2中长度较小的二分
let len = len1 + len2//总长
let start = 0, end = len1 //进行二分的开始和结束位置
let partLen1, partLen2

while (start <= end) {
partLen1 = (start + end) >> 1//nums1二分的位置
partLen2 = ((len + 1) >> 1) - partLen1//nums2二分的位置

//L1:nums1二分之后左边的位置,L2,nums1二分之后右边的位置
//R1:nums2二分之后左边的位置,R2,nums2二分之后右边的位置

//如果左边没字符了,就定义成-Infinity,让所有数都大于它,否则就是nums1二分的位置左边一个
let L1 = partLen1 === 0 ? -Infinity : nums1[partLen1 - 1]
//如果左边没字符了,就定义成-Infinity,让所有数都大于它,否则就是nums2二分的位置左边一个
let L2 = partLen2 === 0 ? -Infinity : nums2[partLen2 - 1]
//如果右边没字符了,就定义成Infinity,让所有数都小于它,否则就是nums1二分的位置
let R1 = partLen1 === len1 ? Infinity : nums1[partLen1]
//如果右边没字符了,就定义成Infinity,让所有数都小于它,否则就是nums1二分的位置
let R2 = partLen2 === len2 ? Infinity : nums2[partLen2]

if (L1 > R2) {//不符合交叉小于等于 继续二分
end = partLen1 - 1
} else if (L2 > R1) {//不符合交叉小于等于 继续二分
start = partLen1 + 1
} else { // L1 <= R2 && L2 <= R1 符合交叉小于等于
return len % 2 === 0 ?
(Math.max(L1, L2) + Math.min(R1, R2)) / 2 : //长度为偶数返回作左侧较大者和右边较小者和的一半
Math.max(L1, L2) //长度为奇数返回作左侧较大者
}
}
}

5. 最长回文子串

题目连接:https://leetcode.cn/problems/longest-palindromic-substring/description/

题解

解题思路:最长回文子串
本人思路:起初想的是用两个for循环来判断,也可以实现,直到提交的时候给了我一个特别的字符串,直接超时了。所以短的可行,长的不行。
略微思索后发现回文字符串的特点就是对称,那么既然对称了直接找出中心点不就可以了。改进之后跑出了不错的成绩。时间击败了70%,内存击败了96.5%。
解题代码:

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
/**
* @param {string} s
* @return {string}
*/
// 改进前
var longestPalindrome = function (s) {
let result = ''
for (let i = 0; i < s.length; i++) {
for (let j = i; j < s.length; j++) {
let str = s.slice(i, j + 1)
if (str == str.split('').reverse().join('') && (j - i + 1) > result.length) result = str
}
}
return result
};

// 改进之后
var longestPalindrome = function (s) {
let result = ''
for (let i = 0; i < s.length; i++) {
let l = -1, r = 1
// 处理连续的重复的字符,如aaa,aaaa等
while (true) {
if (s[i] == s[i + r]) r++;
else {
// 如果有重复的字符,直接跳转到这些字符的最后一个,减少检测次数。
if (r != 1) {
i += r - 1
l -= r - 1
r = 1
}
break
}
}
// 当重复内容都检测完之后,开始向两边检测,如果左右内容一致,则继续向下检测
while (true) {
// 超出范围判断
if (r - l > s.length) break
if (s[i + l] == s[i + r]) {
l--;
r++;
}
else break
}
// 判断是否比当前result长,长的话赋值
if ((r - l - 1) > result.length) result = s.slice(i + l + 1, i + r)
}
return result
};

6. N 字形变换

题目连接:https://leetcode.cn/problems/zigzag-conversion/description/

题解

解题思路:Z 字形变换
本人思路:找出规律,发现行的顺序是先加后减,如 3 行的排列顺序就是 0121 0121,4 行的规律是 012321 012321,而这每一个规律的长度是 2 * numRows - 2 设置一个p,让他根据拐点递增或者递减即可。
解题代码:

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
/**
* @param {string} s
* @param {number} numRows
* @return {string}
*/
var convert = function (s, numRows) {
// 如果行数为1或者字符少于行数就直接返回原字符串
if (s.length <= numRows || numRows == 1) return s
// 创建二维数组
let ls = []
for (let i = 0; i < numRows; i++) { ls.push([]) }
let p = 0
for (let i = 0; i < s.length; i++) {
ls[p].push(s[i])
// 控制字符串对应的规律,如:
// PAYPALISHIRING
// 01210121012101 numRows 为 3 时
// 01232101232101 numRows 为 4 时
if (i % (2 * numRows - 2) < numRows - 1) p++
else p--
}
// 将数组拼接成字符串
let result = ''
ls.forEach(item => { result += item.join('') })
return result
}


// 其他解题方法,根据规律依次找出第一行、第二行、第n行的值。
var convert = function (s, numRows) {
const n = s.length, r = numRows;
if (r === 1 || r >= n) {
return s;
}
const t = r * 2 - 2;
const ans = [];
for (let i = 0; i < r; i++) { // 枚举矩阵的行
// j < n - i 保证j不会在最后一行
// j每轮位置都是固定的几个值,根据i来添加值
for (let j = 0; j < n - i; j += t) { // 枚举每个周期的起始下标
ans.push(s[j + i]); // 当前周期的第一个字符
if (0 < i && i < r - 1 && j + t - i < n) {
ans.push(s[j + t - i]); // 当前周期的第二个字符
}
}
}
return ans.join('');
};

7. 整数反转

题目连接:https://leetcode.cn/problems/reverse-integer/description/

题解

解题思路:极简数学解法,运用JavaScript位运算
本人思路:转成字符串再反转,但这显然不满足题目要求,且速度也不是很好。
解题代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @param {number} x
* @return {number}
*/
var reverse = function(x) {
let result = 0;
while(x !== 0) {
// 反转
result = result * 10 + x % 10;
x = (x / 10) | 0;
}
// 溢出判断
return (result | 0) === result ? result : 0;
};

8. 字符串转换整数

题目连接:https://leetcode.cn/problems/reverse-integer/description/

题解

解题思路:字符串转换整数 (atoi)
本人思路:一个个进行处理,先去除空格,再判断第一个字符,然后处理数字。效果并不好。
解题代码:

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* @param {string} s
* @return {number}
*/
// 本人代码
var myAtoi = function (s) {
let result = 0, isNeg = false
// 空格去除
s = for (let i = 0; i < str.length; i++) { if (str[i] != ' ') return str.slice(i) }
// 如果s为空则返回0
if (!s) return 0
// 判断是否是加减符号
if (s[0] == '-') {
isNeg = true
s = s.slice(1)
}
else if (s[0] == '+') s = s.slice(1)
// 其他符号直接返回0
if (isNaN(s[0])) return 0
let dotIndex = null
for (let i = 0; i < s.length; i++) {
if (s[i] != '.' && isNaN(s[i]) || s[i] == ' ') break
// 记录小数点值
if (s[i] == '.') {
dotIndex = i
continue
}
// 添加
if (dotIndex) result += ((1 / 10) ** (i - dotIndex)) * Number(s[i])
else if (!isNaN(s[i])) result = result * 10 + Number(s[i])
}
result = isNeg ? -result : result
if (result < -2147483648) return -2147483648
else if (result > 2147483647) return 2147483647
else return result
};


// 自动机
/**
* @param {string} str
* @return {number}
*/
var myAtoi = function(str) {
// 自动机类
class Automaton{
constructor() {
// 执行阶段,默认处于开始执行阶段
this.state = 'start';
// 正负符号,默认是正数
this.sign = 1;
// 数值,默认是0
this.answer = 0;
/*
关键点:
状态和执行阶段的对应表
含义如下:
[执行阶段, [空格, 正负, 数值, 其他]]
*/
this.map = new Map([
['start', ['start', 'signed', 'in_number', 'end']],
['signed', ['end', 'end', 'in_number', 'end']],
['in_number', ['end', 'end', 'in_number', 'end']],
['end', ['end', 'end', 'end', 'end']]
])
}

// 获取状态的索引
getIndex(char) {
if (char === ' ') {
// 空格判断
return 0;
} else if (char === '-' || char === '+') {
// 正负判断
return 1;
} else if (typeof Number(char) === 'number' && !isNaN(char)) {
// 数值判断
return 2;
} else {
// 其他情况
return 3;
}
}

/*
关键点:
字符转换执行函数
*/
get(char) {
/*
易错点:
每次传入字符时,都要变更自动机的执行阶段
*/
this.state = this.map.get(this.state)[this.getIndex(char)];

if(this.state === 'in_number') {
/*
小技巧:
在JS中,对字符串类型进行减法操作,可以将得到一个数值型(Number)的值

易错点:
本处需要利用括号来提高四则运算的优先级
*/
this.answer = this.answer * 10 + (char - 0);

/*
易错点:
在进行负数比较时,需要将INT_MIN变为正数
*/
this.answer = this.sign === 1 ? Math.min(this.answer, Math.pow(2, 31) - 1) : Math.min(this.answer, -Math.pow(-2, 31));
} else if (this.state === 'signed') {
/*
优化点:
对于一个整数来说,非正即负,
所以正负号的判断,只需要一次。
故,可以降低其判断的优先级
*/
this.sign = char === '+' ? 1 : -1;
}
}
}

// 生成自动机实例
let automaton = new Automaton();

// 遍历每个字符
for(let char of str) {
// 依次进行转换
automaton.get(char);
}

// 返回值,整数 = 正负 * 数值
return automaton.sign * automaton.answer;
};

9. 回文数

题目连接:https://leetcode.cn/problems/palindrome-number/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
/**
* @param {string} s
* @return {number}
*/
// 本人代码
/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function (x) {
if (x < 0) return false
if (x < 10) return true
let str = x.toString()
return str == str.split('').reverse().join('')
};


// 优质数字运算
/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function (x) {
if (x < 0) return false;
if (x < 10) return true;
let n = 10 ** Math.floor(Math.log10(x));
while (n > 1 && x > 0) {
if (Math.floor(x / n) !== x % 10) return false;
x = Math.floor((x % n) / 10);
n /= 100;
}
return true;
};

10. 正则表达式匹配

题目连接:https://leetcode.cn/problems/regular-expression-matching/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
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
const isMatch = (s, p) => {
if (s == null || p == null) return false;

const sLen = s.length, 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); // 将项默认为false
}
// base case
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++) {

if (s[i - 1] == p[j - 1] || p[j - 1] == ".") {
dp[i][j] = dp[i - 1][j - 1];
} else if (p[j - 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];
} else {
dp[i][j] = dp[i][j - 2];
}
}
}
}
return dp[sLen][pLen]; // 长sLen的s串 是否匹配 长pLen的p串
};

11. 盛最多水的容器

题目连接:https://leetcode.cn/problems/container-with-most-water/description/

题解

解题思路:11. 盛最多水的容器(双指针,清晰图解)
本人思路:使用两个for循环一个个计算,少的可行,多的会超时。
解题代码:

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
/**
* @param {number[]} height
* @return {number}
*/
// 最初代码
var maxArea = function (height) {
let max = 0
for (let i = 0; i < height.length - 1; i++) {
for (let j = i + 1; j < height.length; j++) {
max = Math.max(max, Math.min(height[i], height[j]) * (j - i))
}
}
return max
};

// 两端方法
var maxArea = function (height) {
let max = 0
// 头尾两个指针
let a = 0, b = height.length - 1
while (a < b) {
// 计算
max = Math.max(max, Math.min(height[a], height[b]) * (b - a))
// 哪边小移动哪边
if (height[a] < height[b]) a++
else b--
}
return max
};

12. 整数转罗马数字

题目连接:https://leetcode.cn/problems/integer-to-roman/description/

题解

解题思路:整数转罗马数字
本人思路:有点思路,但是不完全对,参考发现数组设置反了。
解题代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @param {number} num
* @return {string}
*/
var intToRoman = function (num) {
let result = ''
// 设置对照组
let map = [[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'], [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I']]
// 遍历计算
for (const item of map) {
if (num >= item[0]) {
result += item[1].repeat(Math.floor(num / item[0]))
num %= item[0]
}
}
return result
};

13. 罗马数字转整数

题目连接:https://leetcode.cn/problems/roman-to-integer/description/

题解

解题思路:用时 99.93 %,内存 98.73 %,简单解法
本人思路:直接遍历,如果本字符和后一个字符组成的字符串在对象中存在则添加此值并将i向后挪动一位。
解题代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @param {string} s
* @return {number}
*/
var romanToInt = function (s) {
let result = 0
let m = { '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 }
for (let i = 0; i < s.length; i++) {
if (m[s.slice(i, i + 2)]) {
result += m[s.slice(i, i + 2)]
i += 1
} else { result += m[s[i]] }
}
return result
};

14. 最长公共前缀

题目连接:https://leetcode.cn/problems/longest-common-prefix/description/

题解

解题思路:画解算法:14. 最长公共前缀
本人思路:暴力解法,一个个遍历
解题代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 暴力解法
var longestCommonPrefix = function (strs) {
if (strs.length == 1) return strs[0]
let last = 0
for (let i = 1; i <= strs[0].length; i++) {
let b = strs.every(item => item.slice(0, i) == strs[0].slice(0, i))
if (!b) {
last = i - 1
break
}
last = i
}
return strs[0].slice(0, last)
};

15. 三数之和

题目连接:https://leetcode.cn/problems/3sum/description/

题解

解题思路:画解算法:15. 三数之和
本人思路:一个个遍历,但是太复杂太耗时。
解题代码:

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
/**
* @param {number[]} nums
* @return {number[][]}
*/

// 自己看过解题思路写的,时间占用较大
var threeSum = function (nums) {
let result = []
nums = nums.sort((a, b) => a - b)
for (let i = 0; i < nums.length - 2; i++) {
let L = i + 1, R = nums.length - 1
while (L < R) {
let sum = nums[i] + nums[L] + nums[R]
if (sum == 0) {
if (!result.find(item => item[0] == nums[i] && item[1] == nums[L] && item[2] == nums[R])) {
result.push([nums[i], nums[L], nums[R]])
}
R--
L++
} else if (sum < 0) L++
else R--
}
}
return result
};

// 解题思路的代码
var threeSum = function(nums) {
let ans = [];
const len = nums.length;
if(nums == null || len < 3) return ans;
nums.sort((a, b) => a - b); // 排序
for (let i = 0; i < len ; i++) {
if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
let L = i+1;
let R = len-1;
while(L < R){
const sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
ans.push([nums[i],nums[L],nums[R]]);
while (L<R && nums[L] == nums[L+1]) L++; // 去重
while (L<R && nums[R] == nums[R-1]) R--; // 去重
L++;
R--;
}
else if (sum < 0) L++;
else if (sum > 0) R--;
}
}
return ans;
};

16. 最接近的三数之和

题目连接:https://leetcode.cn/problems/3sum-closest/description/

题解

解题思路:画解算法:16. 最接近的三数之和
本人思路:根据第15题思路稍作修改即可。
解题代码:

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
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
// 最初代码
var threeSumClosest = function (nums, target) {
let gap = 9999999
let result = 0
nums = nums.sort((a, b) => a - b)
let len = nums.length
for (let i = 0; i < len - 2; i++) {
let L = i + 1, R = len - 1
while (L < R) {
let sum = nums[i] + nums[L] + nums[R]
let sub = sum - target
if (Math.abs(sub) < gap) {
result = sum
gap = Math.abs(sum - target)
}
if (sub < 0) L++
else if (sub == 0) break
else R--
}
if (gap == 0) break
}
return result
};

// 改进后
var threeSumClosest = function (nums, target) {
let result = nums[0] + nums[1] + nums[2]
nums = nums.sort((a, b) => a - b)
let len = nums.length
for (let i = 0; i < len - 2; i++) {
let L = i + 1, R = len - 1
while (L < R) {
let sum = nums[i] + nums[L] + nums[R]
if (Math.abs(sum - target) < Math.abs(result - target)) result = sum
if (sum < target) L++
else if (sum > target) R--
else return result
}
}
return result
};

17. 电话号码的字母组合

题目连接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/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
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
/**
* @param {string} digits
* @return {string[]}
*/
// 我的代码
var letterCombinations = function (digits) {
if (digits == '') return []
let obj = {
'2': ['a', 'b', 'c'],
'3': ['d', 'e', 'f'],
'4': ['g', 'h', 'i'],
'5': ['j', 'k', 'l'],
'6': ['m', 'n', 'o'],
'7': ['p', 'q', 'r', 's'],
'8': ['t', 'u', 'v'],
'9': ['w', 'x', 'y', 'z'],
}
if (digits.length == 1) return obj[digits]
// 令result等于第一个值对应的数组。
let result = obj[digits[0]]
// 遍历剩余值
for (let i = 1; i < digits.length; i++) {
let tmp = []
// 把result和当前数组进行计算并短暂存储
for (const a of obj[digits[i]]) { for (const b of result) { tmp.push(b + a) } }
// 更新result值
result = tmp
}
return result
};

// 其他代码,递归
//输入:digits = "23"
//输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
var letterCombinations = (digits) => {
if (digits.length == 0) return [];
const res = [];
const map = {//建立电话号码和字母的映射关系
2: "abc",
3: "def",
4: "ghi",
5: "jkl",
6: "mno",
7: "pqrs",
8: "tuv",
9: "wxyz",
};

const dfs = (curStr, i) => {//curStr是递归每一层的字符串,i是扫描的指针
if (i > digits.length - 1) {//边界条件,递归的出口
res.push(curStr); //其中一个分支的解推入res
return; //结束递归分支,进入另一个分支
}
const letters = map[digits[i]]; //取出数字对应的字母
for (const l of letters) {
//进入不同字母的分支
dfs(curStr + l, i + 1); //参数传入新的字符串,i右移,继续递归
}
};
dfs("", 0); // 递归入口,传入空字符串,i初始为0的位置
return res;
};

// 作者:小鸡炖蘑菇
// 链接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/solutions/1509529/dai-ma-jian-ji-yi-chong-huan-bu-cuo-de-j-sbam/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

18.四数之和

题目连接:https://leetcode.cn/problems/4sum/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
40
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function (nums, target) {
const quadruplets = []
if (nums.length < 4) return quadruplets
nums.sort((x, y) => x - y)
const length = nums.length
for (let i = 0; i < length - 3; i++) {
// 去重
if (i > 0 && nums[i] === nums[i - 1]) continue
// 节省次数
if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break
if (nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) continue

for (let j = i + 1; j < length - 2; j++) {
// 去重
if (j > i + 1 && nums[j] === nums[j - 1]) continue
// 节省次数
if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) break
if (nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) continue

let left = j + 1, right = length - 1
while (left < right) {
const sum = nums[i] + nums[j] + nums[left] + nums[right]
if (sum === target) {
quadruplets.push([nums[i], nums[j], nums[left], nums[right]])
while (left < right && nums[left] === nums[left + 1]) left++
left++
while (left < right && nums[right] === nums[right - 1]) right--
right--
} else if (sum < target) left++
else right--
}
}
}
return quadruplets
}

19. 删除链表的倒数第 N 个结点

题目连接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

题解

解题思路:代码简洁 一种还不错的解法
本人思路:计算出length,然后删除指定的即可。
解题代码:

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
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
// 本人代码
var removeNthFromEnd = function (head, n) {
let len = 1
let tmp = head
// 计算长度
while (tmp.next) {
len += 1
tmp = tmp.next
}
// 计算需要删除的元素所在位置
let index = len - n
// 如果是删除第一个
if (index == 0) {
if (len == 1) return null
else return head.next
}
tmp = head
// 找到被删除的元素的前一个元素
for (let i = 0; i < index - 1; i++) { tmp = tmp.next }
// 让 被删除元素的前一个元素 的 下一个元素 等于 被删除元素的后一个元素 即可。
tmp.next = tmp.next.next
return head
};

// 题解写法
var removeNthFromEnd = function (head, n) {
let dummy = new ListNode();
dummy.next = head;
let n1 = dummy;
let n2 = dummy;
for (let i = 0; i <= n; i++) {//n2移动n+1次
n2 = n2.next;
}
while (n2 !== null) {//同时移动n1,n2
n1 = n1.next;
n2 = n2.next;
}
n1.next = n1.next.next;//删除元素
return dummy.next;
};

20. 有效的括号

题目连接:https://leetcode.cn/problems/valid-parentheses/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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* @param {string} s
* @return {boolean}
*/
// 最初
var isValid = function (s) {
let a = ['()', '[]', '{}']
let stack = []
for (let i = 0; i < s.length; i++) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{') stack.push(s[i])
else {
if (a.includes(stack[stack.length - 1] + s[i])) stack.pop()
else return false
}
}
if (stack.length) return false
return true
};
// 改进版 也没提升性能
var isValid = function (s) {
let obj = { '(': ')', '[': ']', '{': '}' }
let stack = []
for (let i = 0; i < s.length; i++) {
if (obj[s[i]]) stack.push(s[i])
else if (obj[stack.pop()] != s[i]) return false
}
if (stack.length) return false
return true
};

// 题解写法 速度快但是内存高了
var isValid = function(s) {
const n = s.length;
if (n % 2 === 1) { return false; }
const pairs = new Map([
[')', '('],
[']', '['],
['}', '{']
]);
const stk = [];
for (let ch of s){
if (pairs.has(ch)) {
if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
return false;
}
stk.pop();
}
else {
stk.push(ch);
}
};
return !stk.length;
};

21. 合并两个有序链表

题目连接:https://leetcode.cn/problems/merge-two-sorted-lists/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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function (list1, list2) {
if (!list1 && !list2) return null
else if (!list1) return list2
else if (!list2) return list1
let head = end = null
while (list1 && list2) {
if (list1.val <= list2.val) {
if (head) {
end.next = new ListNode(list1.val, null)
end = end.next
}
else head = end = new ListNode(list1.val, null)
list1 = list1.next
} else {
if (head) {
end.next = new ListNode(list2.val, null)
end = end.next
}
else head = end = new ListNode(list2.val, null)
list2 = list2.next
}
}
if (list1) end.next = list1
if (list2) end.next = list2
return head
};

// 题解写法
var mergeTwoLists = function(l1, l2) {
const prehead = new ListNode(-1);

let prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}

// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 === null ? l2 : l1;

return prehead.next;
};

22. 括号生成

题目连接:https://leetcode.cn/problems/generate-parentheses/description/

题解

解题思路:「手画图解」从 22. 括号生成 看回溯算法的三个要点
本人思路:只看出规律,但不知怎么写
解题代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @param {number} n
* @return {string[]}
*/
var generateParenthesis = function (n) {
const res = [];

const dfs = (lRemain, rRemain = lRemain, str = "") => { // 左右括号所剩的数量,str是当前构建的字符串
if (str.length == 2 * n) { // 字符串构建完成
res.push(str); // 加入解集
return; // 结束当前递归分支
}
if (lRemain > 0) { // 只要左括号有剩,就可以选它,然后继续做选择(递归)
dfs(lRemain - 1, rRemain, str + "(");
}
if (lRemain < rRemain) { // 右括号比左括号剩的多,才能选右括号
dfs(lRemain, rRemain - 1, str + ")"); // 然后继续做选择(递归)
}
};

dfs(n); // 递归的入口,剩余数量都是n,初始字符串是空串
return res;
};

23. 合并 K 个升序链表

题目连接:https://leetcode.cn/problems/merge-k-sorted-lists/description/

题解

解题思路: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
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
// 这个解法算是最简单的了,且性能还不错。
var mergeKLists = function (lists) {
function transform(l, arr) {
while (l) {
arr.push(l.val);
l = l.next;
}
}

let arr = [];
let res = new ListNode(null);

// 把所有的值都放到数组中
lists.map(item => transform(item, arr));
// 排序
arr.sort((a, b) => a - b);
// 再转成链表
for (let i = arr.length - 1; i >= 0; i--) {
let temp = new ListNode(null);
res.val = arr[i];
temp.next = res;
res = temp;
}

return res.next;
};

24. 两两交换链表中的节点

题目连接:https://leetcode.cn/problems/swap-nodes-in-pairs/description/

题解

解题思路:「手画图解」三步一换 | 24. 两两交换链表中的节点
本人思路:新建一个链表,然后一个个遍历添加,可行但是效率太低。
解题代码:

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
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
// 本人代码
var swapPairs = function (head) {
if (!head || !head.next) return head
let h = cur = new ListNode(-1)

while (head && head.next) {
cur.next = new ListNode(head.next.val)
cur.next.next = new ListNode(head.val)
cur = cur.next.next
head = head.next.next
}
if (head) { cur.next = head }
return h.next
};

// 性能不错的解法
const swapPairs = (head) => {
const dummy = new ListNode(0);
dummy.next = head;
let prev = dummy;

while (head && head.next) {
const next = head.next; // 临时保存head.next,因为head.next待会要改变
head.next = next.next;
next.next = head;
prev.next = next;

prev = head; // 指针更新
head = head.next; // 指针更新
}
return dummy.next;
};

25. K 个一组翻转链表

题目连接:https://leetcode.cn/problems/reverse-nodes-in-k-group/description/

题解

解题思路:K 个一组翻转链表
本人思路:受23题启发,转成数组处理就很简单了。
解题代码:

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
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
var reverseKGroup = function (head, k) {
let tmp = [], ls = []
let index = 0
while (head) {
if (!(index % k)) {
ls = ls.concat(tmp.reverse())
tmp = []
}
tmp.push(head.val)
index++
head = head.next
}

if (tmp.length == k) ls.push(...tmp.reverse())
else ls.push(...tmp)

let res = new ListNode(null)
for (let i = ls.length - 1; i >= 0; i--) {
let temp = new ListNode(null)
res.val = ls[i]
temp.next = res
res = temp
}
return res.next
};

模板

题目连接:

题解

解题思路:
本人思路:
解题代码:

1
//