数组算法一般解题指南(前端JS)

本文主要针对 leetcode 中部分数组相关的简单、中等算法题进行总结分析,提供几个易于理解,便于记忆的解题范式,希望可以帮到大家。

其中涉及到的数组方法,以及相关数据结构,总结在前面,如有不熟悉,建议先看一遍这篇文章:前端常用数据结构一览

数组方法:sort()升降序、Array.from()

Map方法:has()、get()、set()

解题思路:求和转求差、双指针、快慢指针

1 两数之和

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

思路: 此类问题必然是需要遍历数组的,关键在于想到一次遍历即可解决问题的方法。常规思路不好解决的时候,可以将求和问题转化为求差问题,再结合 Map 能保存键值对的特性,有如下解法:

1.新建一个Map来记录已经遍历过的数值(key)及其对应的索引值(value)

2.遍历数组,确认Map里是否有值等于target - nums[i]

  • 是,则返回Mapkeytarget - nums[i]所对用的value以及此时的i
  • 否,则将数值和索引值添加到Mapset(num[i], i)
js 复制代码
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    const myMap = new Map()
    for(let i = 0; i < nums.length; i++){
        if(myMap.has(target - nums[i])){
            return [myMap.get(target - nums[i]), i]
        }else{
            myMap.set(nums[i],i)
        }
    }
};

2 三数之和

题目: 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

思路: 因为是三数,不能只是简单遍历,考虑双指针法,即固定其中一个数,在剩下的数中寻找是否存在两个数和固定数求和为0的,具体解法如下:

1.数组升序排序

2.数组遍历,初始化对撞指针:let l = i + 1, r = nums.length - 1

3.对撞指针循环移动:while(l < r),不断计算sum值大小

  • sum === 0,将三元组添加到res中,并且l++,r--
  • sum >= 0,说明右侧的数偏大了,r--
  • sum <= 0,说明左侧的数偏小了,l++

4.特殊情况处理:重复元素需要跳过

  • 数组遍历时,从第二个开始,与前一个数值比较,相等即跳过此次遍历
  • sum === 0时,循环判断左指针当前值与后一个位置数值是否相等(还需要确保l < r),相等即l++;循环判断右指针当前值与前一个位置数值是否相等,相等即r--
js 复制代码
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    const res = []
    if(nums == null || nums.length < 3) return res
    nums.sort((a,b) => a - b)
    for(let i = 0; i < nums.length; i++){
        if(nums[i] > 0) break
        if(i > 0 && nums[i] === nums[i - 1]) continue
        let l = i + 1, r = nums.length - 1
        while(l < r){
            const sum = nums[i] + nums[l] + nums[r]
            if(sum === 0){
                res.push([nums[i], nums[l], nums[r]])
                while(l < r && nums[l] == nums[l + 1]) l++
                while(l < r && nums[r] == nums[r - 1]) r--
                l++
                r--
            }else if(sum < 0){
                l++
            }else{
                r--
            }
        }
    }
    return res
};

3 合并两个有序数组

题目: 给你两个按非递减顺序排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你合并 nums2 到 nums1 中,使合并后的数组同样按非递减顺序排列。最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

思路: 数组有序,直接用双指针法即可

1.两指针分别指向nums1nums2最大值,依次比较,较大值赋给nums1的尾部

2.遍历条件为while(m>0 && n>0)

3.特殊情况处理:当m先为0的时,依次将nums2赋值给nums1即可

js 复制代码
/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    while(m > 0 && n > 0){
        if(nums1[m-1] >= nums2[n-1]){
            nums1[m+n-1] = nums1[m-1]
            m--
        }else{
            nums1[m+n-1] = nums2[n-1]
            n--
        }
    }
    while(n > 0){
        nums1[n-1] = nums2[n-1]
        n--
    }
};

4 最大子数组和

题目: 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。

思路: 因为要求连续,直接遍历累加即可,但当出现前i项和少于0即应舍弃掉这部分元素

1.遍历数组,更新前i项最大和sum

2.更新数组最大和res

3.特殊情况处理:数组元素有可能均为负数,因此res的初始值不能默认给0,而是数组的第一个元素

js 复制代码
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let res = nums[0]
    let sum = 0
    nums.forEach(item => {
        sum += item
        res = Math.max(sum,res) 
        if(sum < 0){
            sum = 0
        }
    })
    return res
};

5 最长连续序列

题目: 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

思路: 对时间复杂度有要求的往往都需要使用数组以外的数据结构,比如 Map。因为不要求序列元素在原数组连续,考虑用 key 存元素本身,value 存元素所在序列最大长度,解法如下:

1、遍历数组,判断 myMap 中是否已经有该元素,有则跳过,无则继续

2、新存元素时,分别查该元素左邻居和右邻居的 value(不存在时即为0),将其相加并 +1 即可得到该元素的 value。

3、还需要更新连续序列头尾数字的 value(解决了先存元素因为刚开始缺乏左右邻居导致的 value 偏小的问题),因为后续遍历找左右边时,只会找到现有连续序列的头或者尾

js 复制代码
/**
 * @param {number[]} nums
 * @return {number}
 */
var longestConsecutive = function(nums) {
    let res = 0
    const myMap = new Map()
    nums.forEach(num => {
        if(!myMap.has(num)){
            const left = myMap.get(num-1) || 0
            const right = myMap.get(num+1) || 0
            const cur = left + right + 1
            res = Math.max(cur, res)
            myMap.set(num, cur)
            myMap.set(num - left, cur)
            myMap.set(num + right, cur)
        }
    })
    return res
};

6 长度最小的子数组

题目: 给定一个含有 n 个正整数的数组和一个正整数 target,找出该数组中满足其和 ≥ target 的长度最小的连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度 。如果不存在符合条件的子数组,返回 0 。

思路: 因为要求是连续子数组,且均为正整数,考虑快慢指针,解法如下:

1.快指针遍历数组,累加元素,当满足条件后,进入while循环,遍历慢指针直至不满足条件,得到此时最短子数组

2.快指针继续遍历,重复步骤 1 直至遍历结束

3.返回步骤 1 中得到的最小值

4.特殊情况处理:若步骤 1 一次也未能满足条件,则返回 0

js 复制代码
/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    let res = nums.length + 1
    let sum = 0
    let slow = 0
    for(let i = 0; i < nums.length; i++){
        sum += nums[i]
        if(sum >= target){
            while(sum >= target){
                sum -= nums[slow]
                slow++
            }
            res = Math.min(res, i - slow + 2)
        }
    }
    return res === nums.length + 1 ? 0 : res
};

7 买卖股票的最佳时机

题目: 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

思路: 遍历时与前i项最小值做差即可以得到最大利润,解法如下:

1.遍历数组,更新前i项最小值min

2.更新最大利润res

js 复制代码
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    let min = prices[0]
    let res = 0
    prices.forEach(price => {
        min = Math.min(min, price)
        res = Math.max(res, price - min)
    })
    return res
};

8 螺旋矩阵

题目: 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。3 行 4 列的例子:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 思路: 依次以矩阵的四个角作为起点进行单向遍历,包括起点,但不包含终点,得到一个 m-1 行 n-1 列的矩阵,直至剩余单行或者单列,再单独遍历即可,解法如下:

1.确定四角

  • 左边界 left : 0
  • 上边界 top : 0
  • 右边界 right : matrix[0].length - 1
  • 下边界 bottom : matrix.length - 1

2.循环成环条件:top < bottom && left < right

3.每循环一次,需要收缩边界left++、right--、top++、bottom--

4.特殊情况处理:

  • 只剩单行:top === bottom && left <= right
  • 只剩单列:left == right && top <= bottom
js 复制代码
/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function(matrix) {
    const res = []
    let left = 0;
    let top = 0;
    let right = matrix[0].length - 1;
    let bottom = matrix.length - 1;
    while(left < right && top < bottom){
        for(let i = left; i < right ; i++) {
            res.push(matrix[top][i])
        };
        for(let i = top; i < bottom; i++){
            res.push(matrix[i][right])
        };
        for(let i = right; i > left; i--){
            res.push(matrix[bottom][i])
        }
        for(let i = bottom; i > top; i--){
            res.push(matrix[i][left])
        }
        left++
        top++
        right--
        bottom--
    }
    while(left === right && top <= bottom){
        res.push(matrix[top][left])
        top++
    }
    while(left <= right && top === bottom){
        res.push(matrix[top][left])
        left++
    }
    return res
};

9 合并区间

题目: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

  • 示例输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
  • 示例输出: [[1,6],[8,10],[15,18]]

思路: 数组排序后,采用快慢指针遍历数组,有交集即合并,解法如下:

1.以子数组左边界升序排列

2.初始化慢指针为第一个子数组pre

3.快指针从第二个子数组cur开始遍历

4.判断是否有重叠区间pre[1] >= cur[0]

  • 是,合并区间并更新慢指针pre: pre[1] = Math.max(pre[1], cur[1])
  • 否,将当前pre添加到res,并更新慢指针指向当前快指针pre = cur

5.将最后一个子数组,此时的慢指针pre添加到res

js 复制代码
/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
    intervals.sort((a,b) => a[0]-b[0])
    const res = []
    let pre = intervals[0]
    for(let i = 1; i < intervals.length; i++){
        const cur = intervals[i]
        if(pre[1] >= cur[0]){
            pre[1] = Math.max(pre[1],cur[1])
        }else{
            res.push(pre)
            pre = cur
        }
    }
    res.push(pre)
    return res
};

10 字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

  • 示例输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

  • 示例输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

思考: 字符串转数组排序后再转回字符串,存为 Map 的 key,即可作为字母异位词判断依据,value 存符合条件的数组。

1.遍历数组

2.将元素转数组排序后重新转为字符串,作为 key 存入,元素作为数组的子元素存入 value

3.重复 2 步骤,遇到 Map 已存在的 key,即取出 value,push 后再重新存入

4.将 Map 的 value 转为数组

js 复制代码
/**
 * @param {string[]} strs
 * @return {string[][]}
 */
var groupAnagrams = function(strs) {
    const myMap = new Map()
    strs.forEach(str => {
        const arr = str.split('')
        arr.sort()
        let key = arr.toString()
        const list = myMap.get(key) || new Array()
        list.push(str)
        myMap.set(key,list)
    })
    return Array.from(myMap.values())
};
相关推荐
tinker在coding11 分钟前
Coding Caprice - Linked-List 1
算法·leetcode
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
XH华5 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_5 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
落魄君子5 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡5 小时前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin6 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin