前端算法面试必刷题系列[1]

1,880 阅读5分钟

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

1. 两数之和 (two-sum)

标签

  • Array
  • Map
  • 简单

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

给定数组中找到 2 个数之等于给定值的数字结果返回 2 个数字在数组中的下标

基本知识

MDN: Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。

  • Map.prototype.get(key)

    • 返回键对应的值,如果不存在,则返回undefined。
  • Map.prototype.has(key)

    • 返回一个布尔值,表示Map实例是否包含键对应的值。
  • Map.prototype.set(key, value)

    • 设置Map对象中键的值。返回该Map对象。

JavaScript 标准内置对象 Map

基本思路

  1. 先建立一个map结构,利用map能很方便建立值和坐标的映射。
  2. 顺序扫描数组,对每一个元素,如果在 map 中找能组合给定值的另一半数字,找到了直接返回对这两数下标就行。如果找不到就把这个数字存入 map 中,等待扫到另一半数字的时候,再取出来返回结果。

写法实现

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  // 建立map数据结构
  const myMap = new Map()
  for (i = 0; i < nums.length; i++) {
    // (该数字的另一半)= (和 - 该数字)
    let reverse = target - nums[i]
    if (myMap.has(reverse)) {
      // 找到直接输出两数下标
      return [myMap.get(reverse), i]
    }
    // 没找到就先存入 map
    myMap.set(nums[i], i)
  }
  return null
};

2. 两数相加 (add-two-numbers)

标签

  • Array
  • Linklist
  • 中等

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

2 个逆序的链表,要求从低位开始相加,得出结果也逆序输出返回值是逆序结果链表的头结点

基本知识

  • 链表数据结构
  • 数学常识 进位,%10 取数位

基本思路

  1. 为了避免两链表直接为空,还需要特殊处理建立一个头结点的前置节点 preHead 指向真正的头结点。
  2. 由于 preHead 结点本身不能变,所以我们用一个指针 cur 来指向新链表的最后一个结点。
  3. 两链表顺序位相加,注意进位就行,然后每一位值取(和%10),最高位如果还有进位,就加一个最高位 1
  4. 最后返回前置节点preHead->next就ok了

写法实现

/**
 * 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) {
  // 为了避免2链表直接为空建立一个头结点的前置节点
  let preHead = new ListNode(-1),
  // 作为新链表的当前指针,现在指向这个前置节点,之后向后移动
  cur = preHead,
  sum = 0,   // 和
  carry = 0  // 进位
  // 这两个链表只要有一个不为空就需要相加,两链表为空,进位为1,最高位为1
  while (l1 || l2 || carry) {
    // 和就等于,两个数位的值相加,再加进位
    sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + carry
    // 进位就是大于等于 10 进一位
    carry = sum >= 10 ? 1 : 0
    // 新链表的下一位的值是这个(和 % 10) 比如这两位是 (7 + 5)% 10 这位值是 2 进一位 carry = 1
    cur.next = new ListNode(sum % 10)
    // 新链表指针后移,原始2列表指针也后移,当然不为空的情况下后移
    cur = cur.next
    l1 && (l1 = l1.next)
    l2 && (l2 = l2.next)
  }
  // 最后返回头结点,注意不是前置节点
  return preHead.next
};

3. 无重复字符的最长子串 (longest-substring-without-repeating-characters)

标签

  • Array
  • Map
  • 滑动窗口思想

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

在一个字符串重寻找没有重复字母的最长子串。

基本知识

  • Map操作,看上面第一个问题哈
  • 滑动窗口思想

滑动窗口算法思路

  1. 我们在字符串 S 中使用双指针中的左右指针技巧,初始化left = right = 0,把索引闭区间 [left, right]称为一个「窗口」。
  2. 我们先不断地增加right指针扩大窗口[left, right],直到窗口中的字符串符合要求(包含了T中的所有字符)。
  3. 此时,我们停止增加right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含T 中的所有字符了)。同时,每次增加left,我们都要更新一轮结果。
  4. 重复第 2和第3步,直到right到达字符串S 的尽头。

简单来说就是,第 2 步相当于在寻找一个可行解,然后第 3 步在优化这个可行解最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

拿本题举例就是:滑动窗口的右边界不断的右移,只要没有重复的字符,就持续向右扩大窗口边界。一旦出现了重复字符,就需要缩小左边界,直到重复的字符移出了左边界,然后继续移动滑动窗口的右边界。以此类推,每次移动需要计算当前长度,并判断是否需要更新最大长度,最终最大的值就是题目中的所求。

基本思路

  1. 用双指针维护一个滑动窗口,用来剪切子串。
  2. 不断移动右指针,直到遇到重复字符的时候把左指针移到重复字符的下一位。
  3. 移动指针过程中,记录窗口长度的最大值即为答案。

写法实现

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
  let len = s.length,
  maxNow = 0, // 临时最大值
  left = 0, // 定义左指针坐标
  map = new Map(); // 用map来作为滑动窗口,存放字符和对应下标
  // 右边指针后移遍历
  for (let right = 0; right < len; right++) {
    // 如果出现了重复字符,则把左指针移到重复字符的下一位。
    // 注意同时满足重复字符的索引大于左指针,表示在窗口内的重复
    if (map.has(s[right]) && map.get(s[right]) >= left) {
      // 左窗口右滑,直到没重复
      left = map.get(s[right]) + 1
    }
    // 此时长度是 左右两坐标差值 + 1 ,再跟当前最大比较,取最大值
    maxNow = Math.max(maxNow, right - left + 1);
    // 存下每个字符的下标
    map.set(s[right], right)
  }
  return maxNow
};

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the reiver monster,我看到就通过,暗号对不上不加哈