Skip to main content

数组的应用

Map 的妙用——两数求和问题

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

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

淳朴的做法

两层for循环:两层循环来遍历同一个数组;第一层循环遍历的值记为 a,第二层循环时遍历的值记为 b;若 a+b = 目标值,那么 a 和 b 对应的数组下标就是我们想要的答案。

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

因为两层循环很多情况下都意味着 O(n^2) 的复杂度,这个复杂度非常容易导致你的算法超时。

空间换时间,Map 来帮忙

大家记住一个结论:几乎所有的求和问题,都可以转化为求差问题。 这道题就是一个典型的例子,通过把求和问题转化为求差问题,事情会变得更加简单。

我们可以在遍历数组的过程中,增加一个 Map 来记录已经遍历过的数字及其对应的索引值。然后每遍历到一个新数字的时候,都回到 Map 里去查询 targetNum 与该数的差值是否已经在前面的数字中出现过了。若出现过,那么答案已然显现,我们就不必再往下走了。

function twoSum(nums, target) {
const len = nums.length;
if (len < 2) return [];
for (let i = 0; i < len; i++) {
const num = target - nums[i];
const index = nums.indexOf(num);
if (index !== -1 && index !== i) {
return [i, index];
}
}
return [];
}

// !! 推荐
function twoSum(nums, target) {
const map = new Map();
const len = nums.length;
if (len < 2) return [];
for (let i = 0; i < len; i++) {
const num = target - nums[i];
if (map.has(num)) {
return [map.get(num), i]
}
map.set(nums[i], i)
}
return [];
}

强大的双指针法

合并两个有序数组

合并两个有序数组 -> 归并排序/双指针

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

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

// 双指针
function mergeTwoList(nums1, nums2, m, n) {
const result = [];
let p1 = 0;
let p2 = 0;
let cur;
while (p1 < m || p2 < n) {
if (p1 === m) {
cur = nums2[p2++];
} else if (p2 === n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
result.push(cur);
}

for (let i = 0; i < m + n; i++) {
nums1[i] = result[i]
}
}

// 倒双指针
// !!推荐,因为不需要开辟额外的空间
function mergeTwoList(nums1, nums2, m, n) {
let p1 = m - 1;
let p2 = n - 1;
let tail = m + n - 1;
let cur;
while (p1 > -1 || p2 > -1) {
if (p1 === -1) {
cur = nums2[p2--];
} else if (p2 === -1) {
cur = nums1[p1--];
} else if (nums1[p1] > nums2[p2]) {
cur = nums1[p1--];
} else {
cur = nums2[p2--];
}
nums1[tail--] = cur;
}
}

三数之和

固定一个,然后双指针

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

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

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

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

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

function threeSum(nums) {
const len = nums.length;
if (len < 3) return [];
nums.sort((a, b) => a - b);
const result = [];
for (let i = 0; i < len - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue;
let l = i + 1;
let r = len - 1;
while (l < r) {
const sum = nums[i] + nums[l] + nums[r];
if (sum > 0) {
r--;
} else if (sum < 0) {
l++;
} else {
result.push([nums[i], nums[l], nums[r]]);
while (nums[l] === nums[l + 1]) l++;
while (nums[r] === nums[r - 1]) r--;
l++;
r--;
}
}
}
return result;
}

双指针法中的“对撞指针”法

在上面这道题中,左右指针一起从两边往中间位置相互迫近,这样的特殊双指针形态,被称为“对撞指针”。

什么时候你需要联想到对撞指针?

这里我给大家两个关键字——“有序”和“数组”。

没错,见到这两个关键字,立刻把双指针法调度进你的大脑内存。普通双指针走不通,立刻想对撞指针!

即便数组题目中并没有直接给出“有序”这个关键条件,我们在发觉普通思路走不下去的时候,也应该及时地尝试手动对其进行排序试试看有没有新的切入点