【LeetCode选讲·第一期】「两数之和」「无重复字符的最长子串」

177 阅读2分钟

T1 两数之和

题目链接:leetcode.cn/problems/tw…

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 无重复字符的最长子串

题目链接:leetcode.cn/problems/lo…

错解:单指针

拿到这道题目,有的同学可能会认为只需要从左遍历整个字符串,依次统计每个子串的长度即可。

以下是这类同学写出的代码:

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",可见这种解法是存在问题的!

正解:双指针+哈希表

我们已经知道,前述的「错解」的失败之处在于只会往后扫描,但不会兼顾前面、统筹全局

我们可以引入「双指针」解决这个问题。基本思路如下:

  • 设指针startend,使它们的初位置指向字符串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的话,欢迎向您的同事和朋友推荐。如果您有技术方面的问题希望与我们探讨,欢迎直接与我联系。您的支持是我们最大的动力!

QQ图片20220701165008.png