这两道算法题,我想到了Map

118 阅读4分钟

大多数主流编程语言都有多种内置的数据集合。例如Python拥有列表(list)、元组(tuple)和字典(dictionary),Java有列表(list)、集合(set)、队列(queue) 然而 JavaScript 直到ES6的发布之前,只拥有数组(array)和对象(object)这两个内建的数据集合。

Map是一种叫做字典的数据结构; Map 对象存有键值对,其中的键可以是任何数据类型; Map 对象记得键的原始插入顺序; Map 对象具有表示映射大小的属性size,Map.size 返回 Map 中元素的数量;

平时在开发中基本没有用到es6中的Map,只知道这是es6新增的一个数据结构,直到我在leetcode中遇到这两道算法题,让我领悟了Map数据结构用来缓存数据这一思想。


常用的Map方法:

方法描述
new Map()创建新的 Map 对象
set()为 Map 对象中的键设置值
get()获取 Map 对象中键的值
entries()返回 Map 对象中键/值对的数组
keys()返回 Map 对象中键的数组
values()返回 Map 对象中值的数组
clear()删除 Map 中的所有元素
delete()删除由键指定的元素
has()如果键存在,则返回 true
forEach()为每个键/值对调用回调

我们来看看第一道题目:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target  的那两个整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]
 

提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案

这是leetcode上的第一道算法题,如果单纯的使用使用数组的方法类或者for循环的暴力法解决,时间复杂度就是O(n2),显然这不是我刷算法的目的,于是我想到了Map来做数据缓存,将我已经遍历过的数据和他的索引缓存起来,利用map的健可以是任意数据类型这一特点,还有has()方法,get()方法, set()方法。

/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
  const map = new Map()    // 新建一个map
  for(let i=0;i<nums.length;++i){  // 遍历目标数组
    if(map.has(target-nums[i])){     // 判断map里是否有我们需要找的值
      return [i,map.get(target-nums[i])]    // 返回找到的值的索引和当前遍历到的值的索引,即为答案
    }
    map.set(nums[i],i)  // 每次遍历后我们都需要把遍历的项和他的下标缓存起来
  }
  return []
};

有了上面这道简单的题目作为基础我们再来看下面这道中等难度的题:

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:
输入: s = "pwwkew"
输出: 3

解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
 
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

无重复字符的最长子串,首先想到了双指针,依然抛弃暴力解决法,我还是利用了Map数据结构做数据缓存。

/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
  let start=0,end=0,len=0  // 开始位置的指针,结束为止的指针,无重复字符串的最长长度
  const map = new Map()  // new一个map
  for(end;end<s.length;end++){   // 以结束指针为主动遍历,当结束指针到达字符串最后一位时结束遍历
    const current=s.charAt(end)   // 定义一个临时变量用来表示当前的字符s.charAt(end)
    if(map.has(current)){       // 如果map中已经存在这个字符就有可能存在重复字符了
      start=Math.max(map.get(current),start)   
      // 判断出现的字符下标是在start指针之前还是之后,如果是之前则不需要挪动开始指针的位置,
      // 出现在开始指针之后就要将开始指针的位置挪到当前字符的位置➕1的位置了,
      // 这里直接就是get的map中存的索引的位置是因为我在缓存的时候就直接给下标➕ 1处理了
    }
    map.set(current,end+1)  
    // 缓存我遍历的当前项和的下标值加1,
    // 为什么要加1呢,因为这个值后面已经出现了,所以我需要做的自然是将开始指针挪到重复字符的后一位
    len=Math.max(end-start+1,len) // 取最大长度
  }
  return len
};

还有进步空间,继续加油⛽️

image.png