数据结构和算法(JS题解):1. 数组、链表

119 阅读6分钟

📚 数组、链表

  1. 数组

    分配一块连续的内存,每个元素只需要通过索引就可以访问,效率为*O(1)*.数组进行增删操作时,会涉及大量片段的复制群移元素,效率为O(n)

  2. 链表

    这时候用链表可以省去这些操作,它的移动修改操作效率为*O(1)。同时也是因为这样的结构,要访问链表里的元素变得不那么简单,效率为O(n)*。 单向链表:链表 Linked List,每个元素有valuenext,next 指向下一个元素。最后一个元素的 next 指向空 None 循环列表:头指针为 Head, 尾指针为 Tail, Tail 的 next 指向 Head 双向链表:有两个指针 prevnext可以往前走,也可以往后走

  3. 跳表 跳表对标的是平衡树和二分查找,是一种插入/删除/搜索都是O(log n)的数据结构。


🤔 解题思路

  1. 暴力循环
  2. 双指针 双指针,是指用两个变量在线性结构上遍历而解决的问题。双指针算法是基于暴力解法的优化。
  • 对于数组,指两个变量在数组上相向移动解决的问题
  • 对于链表,指两个变量在链表上同向移动解决的问题,也称为「快慢指针」问题
  • 左右指针夹逼

左右指针分别指向左右两端,根据情况向中间移动。适用于两数之和三数之和四数之和, 盛最多水的容器这样的 LeetCode 题目,先对数组进行排序,然后左右夹逼求值。

  • 快慢指针

不断维护一维数组的双指针来做一些事情 3. 滑动窗口


🏫 经典题目

283.移动零(双指针 - 快慢指针)

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。必须在不复制数组的情况下原地对数组进行操作。 输入:[0,1,0,3,12] 输出: [1,3,12,0,0] 思路:for 循环双指针,i 指针指向遍历的元素,j 指针 0 元素的索引。nums[i] !== 0 时,nums[j] = nums[i],同时让 nums[i] = 0, j++。 套路:不断维护一维数组的双指针来做一些事情 时间复杂度:n

var moveZeroes = function (nums) {
  let j = 0;
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] !== 0) {
      nums[j] = nums[i];
      if (i !== j) {
        nums[i] = 0;
      }
      j++;
    }
  }
  return nums;
};

11. 盛水最多的容器(双指针 - 左右夹击)

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 输入:[1,8,6,2,5,4,8,3,7] 输出:49 题解:左右指针夹逼,每次移动短的指针,因为移动长的指针,容器的高度不会增加,宽度会减少,所以面积只会减小。

const maxArea = (height) => {
  let l = 0;
  let r = height.length - 1;
  let maxArea = 0;
  while (l < r) {
    const area = Math.min(height[l], height[r]) * (r - l);
    maxArea = Math.max(maxArea, area);
    if (height[l] < height[r]) {
      l++;
    } else {
      r--;
    }
  }
  return maxArea;
};

15. 三数之和 (双指针 - 左右夹击)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 题解:数组先排序,三指针,定住一个指针,剩下两个指针左右指针夹逼。

var threeSum = function (nums) {
  let arr = [];
  nums.sort((a, b) => a - b);
  for (let k = 0; k < nums.length - 2; k++) {
    if (nums[k] > 0) break;
    if (k > 0 && nums[k] == nums[k - 1]) continue; // 跳过重复的k值
    let i = k + 1;
    let j = nums.length - 1;

    while (i < j) {
      const sum = nums[k] + nums[i] + nums[j];
      if (sum === 0) {
        arr.push([nums[k], nums[i], nums[j]]);
        while (i < j && nums[i] === nums[++i]) {}
        while (i < j && nums[j] === nums[--j]) {}
      } else if (sum < 0) {
        while (i < j && nums[i] === nums[++i]) {}
      } else {
        while (i < j && nums[j] === nums[--j]) {}
      }
    }
  }

  return arr;
};

239. 滑动窗口的最大值(滑动窗口)

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7

const maxSlidingWindow = (nums, k) => {
  const q = []; // 存放优先队列的元素下标,为了取值方便
  const result = []; // 结果数组

  for (let i = 0; i < nums.length; i++) {
    // 如果队列不为空,当前元素大于队列里的其他元素,则弹出
    while (q.length && nums[i] >= nums[q[q.length - 1]]) {
      q.pop();
    }

    // 当前元素下标入栈
    q.push(i);

    // 判断当前最大值是否在窗口中,若不在则让其出队
    while (q[0] <= i - k) {
      q.shift();
    }
    // 达到窗口大小时,就向结果添加数据
    if (i >= k - 1) result.push(nums[q[0]]);
  }
  return result;
};

3. 无重复字符的最长子串(滑动窗口)

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

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

滑动窗口 维护一个滑动窗口,窗口内都是没有重复的字符,尽可能去扩大窗口的大小

  1. 如果当前遍历到的字符从未出现,直接扩大右边界
  2. 如果当前遍历到的字符出现过,则缩小窗口(左边索引向右移动),继续观察
  3. 重复 1,2,知道左边索引无法再移动
  4. 维护 res,更新无重复字符的最长子串
const lengthOfLongestSubstring = (s) => {
  const dict = new Map();
  let minIndex = 0, // 滑动窗口的左指针
    maxLength = 0,
    curIndex; // 表示和滑动窗口的右指针元素相同的最新的索引

  for (let i = 0; i < s.length; i++) {
    // i是滑动窗口的右指针
    curIndex = dict.get(s[i]);
    if (curIndex >= minIndex) {
      // 元素重复,将左指针移动上次出现这个字符的索引的下一位
      minIndex = curIndex + 1;
    }
    dict.set(s[i], i); // 记录字符的最新索引
    maxLength = Math.max(i + 1 - minIndex, maxLength);
  }
  return maxLength;
};