T1 两数之和
1.暴力遍历法:
function twoSum(nums, target) {
const n = nums.length;
for(let i = 0; i <= n - 1; i++) {
for(let j = i + 1; j <= n; j++) {
if(nums[i]+nums[j] === target) {
return [i, j];
}
}
}
}
2.哈希表法(双循环):
function twoSum(nums, target) {
const MyMap = new Map();
nums.forEach((num, index) => MyMap.set(num, index));
for(let index1 = 0; index1 <= nums.length; index1++) {
let num1 = nums[index1];
let num2 = target - num1;
let index2 = MyMap.get(num2)
//根据题意两个下标不能重复
if(index2 !== void 0 && index1 !== index2) {
return [index1, index2];
}
}
}
3.哈希表法(单循环):
function twoSum(nums, target) {
const MyMap = new Map();
for(let index2 = 0; index2 <= nums.length; index2++) {
let num2 = nums[index2];
let num1 = target - num2;
let index1 = MyMap.get(num1);
//根据题意两个下标不能重复
if(index1 !== void 0 && index1 !== index2) {
return [index1, index2];
} else {
MyMap.set(num2, index2);
}
}
}
T3 无重复字符的最长子串
错解:单指针
拿到这道题目,有的同学可能会认为只需要从左遍历整个字符串,依次统计每个子串的长度即可。
以下是这类同学写出的代码:
function lengthOfLongestSubstring(str) {
let MySet = new Set();
let len = 0;
let ans = 0;
for(let i = 0; i < str.length; i++) {
let ch = str[i];
if(MySet.has(ch)) {
ans = Math.max(ans, len);
len = 1;
(MySet = new Set()).add(ch);
} else {
len++;
MySet.add(ch);
}
}
ans = Math.max(ans, len);
return ans;
}
这种做法咋一看似乎没问题,但仔细思考之后便会发现这么做是错误的。假如传入str的值为"dvdf",如果运用前述的解法,那么程序分解出来的子串就分别为"dv"、"df"。然而显而易见的是,最长的子串明明就是"vdf",可见这种解法是存在问题的!
正解:双指针+哈希表
我们已经知道,前述的「错解」的失败之处在于只会往后扫描,但不会兼顾前面、统筹全局。
我们可以引入「双指针」解决这个问题。基本思路如下:
- 设指针
start、end,使它们的初位置指向字符串str最左端;- 向右移动指针
end对字符串进行扫描,同时维护哈希表map记录扫描过程中碰到的字符;- 如果扫描发现
map中已记录的字符,调整指针start位置至该字符先前出现位置的右侧,以确保区间[start, end]中始终不存在重复字符;- 整个遍历过程中,区间
[start, end]的最大长度即为所求.
通过指针start的加入,我们在end指针「瞻前」的基础上实现了「顾后」,完美解决了前述「错解」的问题。
以下为实现代码:
function lengthOfLongestSubstring(str) {
let map = new Map();
let start = 0;
let end = 0;
let ans = 0;
for(; end < str.length; end++) {
let ch = str[end];
//发现重复字符,表明上一区间已全部遍历完成
if(map.has(ch)) {
let oldStart = start;
start = map.get(ch) + 1;
ans = Math.max(ans, end - oldStart);
//更新哈希表,删除已处于新区间左侧的记录
for(let i = oldStart; i < start; i++) {
map.delete(str[i]);
}
}
//由于指针end一直在向右扫描,故无论有没有发现重复字符,都要更新map
map.set(ch, end);
}
//为了防止指针end一直扫描到最右端也没有结束当前子串
//在遍历结束之后需要再求解一次ans
ans = Math.max(ans, end - start);
return ans;
}
编码过程中的要点已经写在代码的注释里了,故本文不再赘述!
写在文末
我是来自在校学生编程兴趣小组江南游戏开发社的PAK向日葵,我们目前正在致力于开发自研的非营利性网页端同人游戏《植物大战僵尸:旅行》,以实战锻炼我们的前端应用开发能力。
我们诚挚邀请您体验我们的这款优秀作品,如果您喜欢TA的话,欢迎向您的同事和朋友推荐。如果您有技术方面的问题希望与我们探讨,欢迎直接与我联系。您的支持是我们最大的动力!