前端算法笔记-数组的应用

本文是关于前端算法的学习笔记,主要探讨了数组问题的解决策略,如利用Map解决两数之和问题,以及如何运用双指针法高效地合并有序数组和解决三数求和问题。通过剑指offer的题目实例,阐述了双指针法在求和、比大小类数组题目中的重要性,并强调了数组排序作为应用前提的重要性。

前言

准备根据修言老师的小册子结合leetcode上剑指offer的数组题学习这一部分。

 

Map的妙用

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

emmm首先这道题我做过,虽然第一反应肯定是两层循环。但是仔细一想,可以用哈希表。

剑指offer原题 和这题太一样。给的是一个递增排序,所以考点就变成了二分或者双指针(下面会讲到)。

这里修言老师语:大家以后做算法题的时候,要有这样的一种本能:当发现自己的代码里有两层循环时,先反思一下,能不能用空间换时间,把它优化成一层循环。

大家记住一个结论:几乎所有的求和问题,都可以转化为求差问题

 

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
const twoSum = function(nums, target) {
    // 这里我用对象来模拟 map 的能力
    const diffs = {}
    // 缓存数组长度
    const len = nums.length
    // 遍历数组
    for(let i=0;i<len;i++) {
        // 判断当前值对应的 target 差值是否存在(是否已遍历过)
        if(diffs[target-nums[i]]!==undefined) {
            // 若有对应差值,那么答案get!
            return [diffs[target - nums[i]], i]
        }
        // 若没有对应差值,则记录当前值
        diffs[nums[i]]=i
    }
};

然后说到我踩的坑。我用了forEach,对我这种js菜鸟来说下面的代码看着完全没问题嘛……循环到了就return呗。但是,下面这段代码的return永远是undefined。

//问题代码
//我这里是用hash表做了剑指57题,所以返回的不是下标
var twoSum = function(nums, target) {
    const diff={};
   nums.forEach((item)=>{
        console.log(item)
        if(diff[target-item]!==undefined){
            return [item,target-item];
        }else{
            diff[item]=target-item;
             console.log(diff)
        }
    })
 
};

forEach的坑点:

1,forEach中使用return无效

2.forEach不支持break

 

强大的双指针法

合并两个有序数组

真题描述:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

这里一看就是归并排序常用的操作啦~ 我的第一反应是创建一个(m+n)长度的新数组,但是题目说是合并到nums1中,所以要近似的原地排序,从后开始合并~

剑指原题:和这里不一样的是剑指25给的数据结构是链表,所以可以做到完全的原地排序!(这个题目讲到链表的应用的时候具体再写)

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]

说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

 

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
const merge = function(nums1, m, nums2, n) {
    // 初始化两个指针的指向,初始化 nums1 尾部索引k
    let i = m - 1, j = n - 1, k = m + n - 1
    // 当两个数组都没遍历完时,指针同步移动
    while(i >= 0 && j >= 0) {
        // 取较大的值,从末尾往前填补
        if(nums1[i] >= nums2[j]) {
            nums1[k] = nums1[i] 
            i-- 
            k--
        } else {
            nums1[k] = nums2[j] 
            j-- 
            k--
        }
    }
    
    // nums2 留下的情况,特殊处理一下 
    while(j>=0) {
        nums1[k] = nums2[j]  
        k-- 
        j--
    }
};

 

三数求和问题

双指针法能处理的问题多到你想不到。不信来瞅瞅两数求和它儿子——三数求和问题!
俗话说,青出于蓝而胜于蓝,三数求和虽然和两数求和只差了一个字,但是思路却完全不同。

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

注意:答案中不可以包含重复的三元组。

示例:给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]

三数之和延续两数之和的思路,我们可以把求和问题变成求差问题——固定其中一个数,在剩下的数中寻找是否有两个数和这个固定数相加是等于0的。

虽然乍一看似乎还是需要三层循环才能解决的样子,不过现在我们有了双指针法,定位效率将会被大大提升,从此告别过度循环~

(这里大家相信已经能察觉出来双指针法的使用场景了,一方面,它可以做到空间换时间;另一方面,它也可以帮我们降低问题的复杂度。)

双指针法用在涉及求和、比大小类的数组题目里时,大前提往往是:该数组必须有序。否则双指针根本无法帮助我们缩小定位的范围,压根没有意义。因此这道题的第一步是将数组排序:、

 nums = nums.sort((a,b)=>{
    return a-b
})

然后,对数组进行遍历,每次遍历到哪个数字,就固定哪个数字。然后把左指针指向该数字后面一个坑里的数字,把右指针指向数组末尾,让左右指针从起点开始,向中间前进:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
const threeSum = function(nums) {
    // 用于存放结果数组
    let res = [] 
    // 给 nums 排序
    nums = nums.sort((a,b)=>{
        return a-b
    })
    // 缓存数组长度
    const len = nums.length
    // 注意我们遍历到倒数第三个数就足够了,因为左右指针会遍历后面两个数
    for(let i=0;i<len-2;i++) {
        // 左指针 j
        let j=i+1 
        // 右指针k
        let k=len-1   
        // 如果遇到重复的数字,则跳过
        if(i>0&&nums[i]===nums[i-1]) {
            continue
        }
        while(j<k) {
            // 三数之和小于0,左指针前进
            if(nums[i]+nums[j]+nums[k]<0){
                j++
               // 处理左指针元素重复的情况
               while(j<k&&nums[j]===nums[j-1]) {
                    j++
                }
            } else if(nums[i]+nums[j]+nums[k]>0){
                // 三数之和大于0,右指针后退
                k--
               
               // 处理右指针元素重复的情况
               while(j<k&&nums[k]===nums[k+1]) {
                    k--
                }
            } else {
                // 得到目标数字组合,推入结果数组
                res.push([nums[i],nums[j],nums[k]])
                
                // 左右指针一起前进
                j++  
                k--
               
                // 若左指针元素重复,跳过
                while(j<k&&nums[j]===nums[j-1]) {
                    j++
                }  
               
               // 若右指针元素重复,跳过
               while(j<k&&nums[k]===nums[k+1]) {
                    k--
                }
            }
        }
    }
    
    // 返回结果数组
    return res
};

这章最后修言老师讲到了对撞指针法,也就是双指针~其实我觉得对双指针最好的理解就是快排。

剑指offer

留个坑,周末填。

总结

1. 几乎所有的求和问题,都可以转化为求差问题

 

 

 

 

 

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值