前言
准备根据修言老师的小册子结合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. 几乎所有的求和问题,都可以转化为求差问题。
本文是关于前端算法的学习笔记,主要探讨了数组问题的解决策略,如利用Map解决两数之和问题,以及如何运用双指针法高效地合并有序数组和解决三数求和问题。通过剑指offer的题目实例,阐述了双指针法在求和、比大小类数组题目中的重要性,并强调了数组排序作为应用前提的重要性。

1278

被折叠的 条评论
为什么被折叠?



