Skip to content

Commit c6ea6bc

Browse files
committed
w1: stanley
1 parent 4a129a4 commit c6ea6bc

File tree

4 files changed

+333
-2
lines changed

4 files changed

+333
-2
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ LeetCode
385385
|30|[Substring with Concatenation of All Words](https://leetcode.com/problems/substring-with-concatenation-of-all-words/)| |Hard|
386386
|29|[Divide Two Integers](https://leetcode.com/problems/divide-two-integers/)| |Medium|
387387
|28|[Implement strStr()](https://leetcode.com/problems/implement-strstr/)| |Easy|
388-
|27|[Remove Element](https://leetcode.com/problems/remove-element/)| |Easy|
388+
|27|[Remove Element](https://leetcode.com/problems/remove-element/)| [js](./algorithms/removeElement/removeElement-js.md) |Easy|
389389
|26|[Remove Duplicates from Sorted Array](https://leetcode.com/problems/remove-duplicates-from-sorted-array/)| |Easy|
390390
|25|[Reverse Nodes in k-Group](https://leetcode.com/problems/reverse-nodes-in-k-group/)| |Hard|
391391
|24|[Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/)| |Medium|
@@ -398,7 +398,7 @@ LeetCode
398398
|17|[Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/)| |Medium|
399399
|16|[3Sum Closest](https://leetcode.com/problems/3sum-closest/)| |Medium|
400400
|15|[3Sum](https://leetcode.com/problems/3sum/)| |Medium|
401-
|14|[Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/)| |Easy|
401+
|14|[Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/)| [js](./algorithms/longestCommonPrefix/longestCommonPrefix-js.md) |Easy|
402402
|13|[Roman to Integer](https://leetcode.com/problems/roman-to-integer/)| |Easy|
403403
|12|[Integer to Roman](https://leetcode.com/problems/integer-to-roman/)| |Medium|
404404
|11|[Container With Most Water](https://leetcode.com/problems/container-with-most-water/)| |Medium|

algorithms/3Sum/3Sum-js.md

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# 15. 三数之和
2+
3+
## 方法一:排序 + 双指针
4+
5+
**解题思想:**
6+
7+
这道题如果使用暴力求解法,也就是三重循环来枚举,首先时间复杂度是O(N^3),还得使用哈希表来进行去重,得到最终的一个不重复的三元数组,时间复杂度与空间复杂度都非常高。
8+
9+
可以通过先排序,再用三重循环,来保证循环的元素不重复。
10+
11+
同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。举个例子,如果排完序的数组为
12+
13+
**代码:**
14+
15+
```js
16+
[0, 1, 2, 2, 2, 3]
17+
^ ^ ^
18+
```
19+
20+
当枚举的第一个三元数组是 (0, 1, 2),后面一次枚举依旧是 (0, 1, 2),因此要将第三重循环跳到一个完全不同的元素,即跳过2也就是最后一个元素3。
21+
22+
用暴力法试一下:
23+
24+
```js
25+
var threeSum = function (nums) {
26+
nums = nums.sort((a, b) => a - b);
27+
const len = nums.length,
28+
result = [];
29+
30+
const check = (i, j, k) => nums[i] + nums[j] + nums[k] === 0;
31+
32+
for (let i = 0; i < len - 2; i++) {
33+
if (i > 0 && nums[i] === nums[i - 1]) {
34+
continue;
35+
}
36+
37+
if (nums[i] === 0 && nums[i + 1] === 0 && nums[i + 2] === 0) {
38+
result.push([0, 0, 0]);
39+
break;
40+
}
41+
42+
for (let j = i + 1; j < len - 1; j++) {
43+
if (j > i + 1 && nums[j] === nums[j - 1]) {
44+
continue;
45+
}
46+
47+
for (let k = j + 1; k < len; k++) {
48+
if (k > j + 1 && nums[k] === nums[k - 1]) {
49+
continue;
50+
}
51+
check(i, j, k) && result.push([nums[i], nums[j], nums[k]]);
52+
}
53+
}
54+
}
55+
return result;
56+
};
57+
```
58+
59+
时间复杂度仍然为 O(N^3),这是无法接受的,因此需要进行优化。
60+
61+
因为 a + b + c = 0,在保持 a 不变的前提下,随着 b 的增大,c 就会逐渐变小,因此我们让第三重循环的指针从最右边开始往右遍历。这样的话,将枚举的时间复杂度从 O(N^2)减少至 O(N)。这就是双指针的方法。
62+
63+
这道题的难点在于重复项的判断。
64+
65+
```js
66+
var threeSum = function(nums) {
67+
const result = [];
68+
69+
const len = nums.length;
70+
if (len < 3) {
71+
return result;
72+
}
73+
74+
nums = nums.sort((a, b) => a - b);
75+
for (let i = 0; i < len; i++) {
76+
// 如果当前数组大于0,则三数之和一定大于0,所以结束循环
77+
if (nums[i] > 0) break;
78+
// 重复项跳过循环
79+
if (i > 0 && nums[i] === nums[i-1]) continue;
80+
let L = i + 1;
81+
let R = len - 1;
82+
while (L < R) {
83+
const sum = nums[i] + nums[L] + nums[R];
84+
if (sum === 0) {
85+
// 跳过重复项
86+
result.push([nums[i], nums[L], nums[R]])
87+
while (L < R && nums[L] === nums[L + 1]) L++;
88+
while (L < R && nums[R] === nums[R - 1]) R--;
89+
L++;
90+
R--;
91+
}
92+
else if (sum < 0) L++;
93+
else if (sum > 0) R--;
94+
}
95+
}
96+
return result;
97+
};
98+
```
99+
100+
**复杂度分析:**
101+
102+
- 时间复杂度:O(N^2)。
103+
104+
- 空间复杂度:O(logN)。额外排序的空间复杂度为 O(logN)。使用了一个额外的数组存储了 nums 的副本并进行排序,空间复杂度为 O(N)。
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# 14. 最长公共前缀
2+
3+
## 方法一:取第一个字符串,与剩下的依次比较
4+
5+
写法一:
6+
7+
**代码:**
8+
9+
```js
10+
var longestCommonPrefix = function(strs) {
11+
if (strs.length == 0) return "";
12+
let ans = strs[0];
13+
for (let i = 1; i < strs.length; i++) {
14+
let j = 0;
15+
for (; j < ans.length && j < strs[i].length; j++) {
16+
if (ans[j] != strs[i][j]) break;
17+
}
18+
ans = ans.substr(0, j);
19+
if (ans === "") return ans;
20+
}
21+
return ans;
22+
};
23+
```
24+
25+
写法二:
26+
27+
**代码:**
28+
29+
```js
30+
var longestCommonPrefix = function(strs) {
31+
// 当字符串数组长度为 0 时则公共前缀为空,直接返回
32+
if (strs.length === 0) return '';
33+
// 令最长公共前缀 ans 的值为第一个字符串,进行初始化
34+
const ans = strs[0];
35+
const len = ans.length;
36+
// ans 是空字符串的特殊情况
37+
if (len === 0) return '';
38+
for (let i = 0; i < len; i++) {
39+
if (!strs.every(str => str[i] === ans[i])) {
40+
// 首次判定的时候就有不满足的项,直接抛空出去
41+
if (i === 0) return '';
42+
// 当有不满足项出现的时候,把之前满足的字符串抛出去
43+
return ans.slice(0, i);
44+
}
45+
}
46+
// 循环跑完也没有return出去,说明全部满足,把判断的这个字符return出去
47+
return ans;
48+
};
49+
```
50+
51+
**复杂度分析:**
52+
53+
- 时间复杂度:O(n),n 是数组的长度
54+
- 空间复杂度:O(1)
55+
56+
## 方法二:直接比较最长字符串与最短字符串
57+
58+
**解题思路:**
59+
60+
找出最大字符串与最小字符串,然后比较出来的公共前缀也为其它字符串的公共前缀。
61+
62+
这里需要注意的是,比较大小是按照字符的大小,比如abac是小于abc的。
63+
64+
**代码:**
65+
66+
```js
67+
var longestCommonPrefix = function(strs) {
68+
if (strs === null || strs.length === 0) return "";
69+
if(strs.length === 1) return strs[0]
70+
let min = 0, max = 0
71+
for(let i = 1; i < strs.length; i++) {
72+
if(strs[min] > strs[i]) min = i
73+
if(strs[max] < strs[i]) max = i
74+
}
75+
for(let j = 0; j < strs[min].length; j++) {
76+
if(strs[min].charAt(j) !== strs[max].charAt(j)) {
77+
return strs[min].substring(0, j)
78+
}
79+
}
80+
return strs[min];
81+
};
82+
```
83+
84+
**复杂度分析:**
85+
86+
- 时间复杂度:O(n+m),n是数组的长度, m 是字符串数组中最短字符的长度
87+
- 空间复杂度:O(1)
88+
89+
## 方法三:分治策略 归并思想
90+
91+
**解题思路:**
92+
93+
把问题分解为子问题:找2个字符串的最长公共前缀
94+
95+
**代码:**
96+
97+
```js
98+
var longestCommonPrefix = function(strs) {
99+
if (strs === null || strs.length === 0) return "";
100+
return lCPrefixRec(strs)
101+
};
102+
103+
// 若分裂后的两个数组长度不为 1,则继续分裂
104+
// 直到分裂后的数组长度都为 1,
105+
// 然后比较获取最长公共前缀
106+
function lCPrefixRec(arr) {
107+
let length = arr.length
108+
if(length === 1) {
109+
return arr[0]
110+
}
111+
let mid = Math.floor(length / 2),
112+
left = arr.slice(0, mid),
113+
right = arr.slice(mid, length)
114+
return lCPrefixTwo(lCPrefixRec(left), lCPrefixRec(right))
115+
}
116+
117+
// 求 str1 与 str2 的最长公共前缀
118+
function lCPrefixTwo(str1, str2) {
119+
let j = 0
120+
for(; j < str1.length && j < str2.length; j++) {
121+
if(str1.charAt(j) !== str2.charAt(j)) {
122+
break
123+
}
124+
}
125+
return str1.substring(0, j)
126+
}
127+
```
128+
129+
**复杂度分析:**
130+
131+
- 时间复杂度:O(s),s 是所有字符串中字符数量的总和
132+
- 空间复杂度:O(m*logn),n是数组的长度,m为字符串数组中最长字符的长度
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# 27. 移除元素
2+
3+
直接对原数组中进行更改,返回目标数组的长度,超过这个长度的数组项可以继续留在原数组中,长度以内的项则就是结果项。
4+
5+
## 方法一:直接覆盖
6+
7+
**解题思想:**
8+
9+
遇到不同于val的项,就将其直接覆盖到nums数组中,并记一次累加值,当遍历完之后,所有不同于val的项就都放到nums数组的前面了,而这个累加值就是这个前面数组的长度。
10+
11+
**代码:**
12+
13+
```js
14+
var removeElement = (nums, val) => {
15+
let count= 0
16+
for (let i = 0; i < nums.length; i++) {
17+
if (nums[i] !== val) {
18+
nums[count] = nums[i];
19+
count++;
20+
}
21+
}
22+
return count;
23+
}
24+
```
25+
26+
**复杂度分析:**
27+
28+
- 时间复杂度:O(n),就一次循环遍历
29+
30+
## 方法二:双指针法
31+
32+
**解题思想:**
33+
34+
左指针在数组开始的位置,右指针在数组结尾的位置。当左指针的项等于val的时候,把右指针的项给拿过来,同时右指针往左移一位;而当等于的时候,左指针往右移一位。当两个指针相遇的时候,左指针的位置就是所有不等于val项的数组的长度。
35+
36+
**代码:**
37+
38+
```js
39+
var removeElement = function(nums, val) {
40+
let L = 0, R = nums.length - 1;
41+
while (L <= R) {
42+
if (nums[L] === val) {
43+
nums[L] = nums[R];
44+
R--;
45+
} else {
46+
L++;
47+
}
48+
}
49+
return L;
50+
};
51+
```
52+
53+
## 方法三:直接删除元素
54+
55+
遇到和val相同的项就直接用splice删除,后面的项就都会往前移一位。
56+
57+
**代码:**
58+
59+
```js
60+
var removeElement = function(nums, val) {
61+
for (let i = 0; i < nums.length ; i++) {
62+
if (nums[i] === val) {
63+
nums.splice(i, 1);
64+
// 指针 i 要 -1,新项需重新判定,不然会漏掉
65+
i--;
66+
}
67+
}
68+
return nums.length;
69+
};
70+
```
71+
72+
> 为了避免遍历时执行多遍计算数组长度的操作,影响效率,,建议在循环开始以变量的形式缓存下数组长度,若在循环内部有可能改变数组长度,请务必慎重处理,避免数组越界。因此这里会改变数组长度的做法,是有危险的,不推荐。
73+
74+
另一种写法:
75+
76+
只要数组中还存在和val相同的项,就删除。
77+
78+
```js
79+
var removeElement = (nums, val) => {
80+
while (nums.indexOf(val) !== -1) {
81+
nums.splice(nums.indexOf(val),1)
82+
}
83+
return nums.length
84+
}
85+
```
86+
87+
**复杂度分析:**
88+
89+
- 时间复杂度:O(n)
90+
91+
> 这几种方法都需要遍历一次数组,因此时间复杂度都是O(n)
92+
93+
- 空间复杂度:O(1)
94+
95+
> 没有占用额外的空间

0 commit comments

Comments
 (0)