数组与双指针
11 盛最多水的容器
双指针:每次移动较短的那根
题干:给定 n 条垂直线段,找两条线使得围成的水最多。
- 关键:无论怎么移动长板,面积都不可能更大(宽变小,高受限于短板)→ 所以移动短板
var maxArea = function(height) {
let max = 0
let i = 0
let j = height.length - 1
while (i < j) {
let lh = height[i]
let rh = height[j]
if (lh < rh) {
max = Math.max(max, lh * (j - i))
i++ // 移动短板
} else {
max = Math.max(max, rh * (j - i))
j--
}
}
return max
}
15 三数之和
犹豫不决先排序,步步逼近双指针
题干:找出数组中所有和为 0 的三元组,不能重复。
- 先排序,固定 k,双指针 i/j 从两端逼近
- 去重:跳过相同值的 k、i、j
- 当
nums[k] > 0时直接 break(后面不可能为 0)
var threeSum = function(nums) {
if (nums.length < 3) return []
const ans = []
nums.sort((a, b) => a - b)
let k = 0
while (k < nums.length - 2) {
let nk = nums[k]
if (nk > 0) break // 剪枝
let i = k + 1
let j = nums.length - 1
while (i < j) {
let ni = nums[i], nj = nums[j]
if (nk + ni + nj < 0) {
i++
while (nums[i] === nums[i - 1]) i++ // 跳重复
} else if (nk + ni + nj > 0) {
j--
while (nums[j] === nums[j + 1]) j--
} else {
ans.push([nk, ni, nj])
i++
while (nums[i] === nums[i - 1]) i++
j--
while (nums[j] === nums[j + 1]) j--
}
}
k++
while (nums[k] === nums[k - 1]) k++ // 跳重复
}
return ans
}
26 删除有序数组中的重复项
双指针原地去重
题干:原地删除有序数组中的重复元素,返回新长度。
// 心里想的数组:[1,2,2,2,2,3,4]
var removeDuplicates = function(nums) {
let i = 0 // 指向已排好的末尾
let j = 1
while (j < nums.length) {
if (nums[j] !== nums[i]) {
i++
nums[i] = nums[j] // 不同就往前覆盖
}
j++
}
return i + 1
}
33 搜索旋转排序数组
二分查找变种:判断哪半边有序
题干:旋转排序数组(如 [4,5,6,7,0,1,2])中搜索 target,要求 O(log n)。
- 每次二分,左半或右半至少有一边是有序的
- 判断 target 在有序的那半边还是无序的那半边
var search = function (nums, target) {
let l = 0, r = nums.length - 1
let mid = Math.floor(r / 2)
while (l <= r) {
if (nums[mid] === target) return mid
// 左边有序
if (l <= mid - 1 && nums[l] <= nums[mid - 1]) {
if (target >= nums[l] && target <= nums[mid - 1]) {
r = mid - 1 // target 在有序左半边
} else {
l = mid + 1
}
} else { // 右边有序
if (target >= nums[mid + 1] && target <= nums[r]) {
l = mid + 1
} else {
r = mid - 1
}
}
mid = Math.floor((l + r) / 2)
}
return -1
}
48 旋转图像
原地旋转:递推公式 (x,y) → (y, n-1-x)
题干:将 n×n 矩阵顺时针旋转 90 度,原地修改。
- 递推公式:
(x,y) → (y, n-1-x) - 分区旋转,奇偶两种情况统一处理
var rotate = function(matrix) {
const n = matrix.length
for (let i = 0; i < Math.floor(n / 2); i++) {
for (let j = 0; j < Math.floor((n + 1) / 2); j++) {
// 四个位置一轮换
const temp = matrix[i][j]
matrix[i][j] = matrix[n - j - 1][i]
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1]
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1]
matrix[j][n - i - 1] = temp
}
}
}
56 合并区间
按左端点排序,然后逐个合并
题干:给定区间数组,合并所有重叠的区间。
- 先按左区间排序
- 比较前一个右端点和后一个左端点:能合并就取右端点大者,不能合并就推入结果
var merge = function(intervals) {
intervals.sort((a, b) => a[0] - b[0])
let compareArr = []
let ansArr = []
for (let i = 0; i < intervals.length; i++) {
let item = intervals[i]
if (!compareArr.length) {
compareArr = item.slice()
} else if (compareArr[1] < item[0]) { // 无法合并
ansArr.push(compareArr.slice())
compareArr = item.slice()
} else { // 可以合并
compareArr = [compareArr[0], Math.max(compareArr[1], item[1])]
}
}
if (compareArr.length) ansArr.push(compareArr.slice())
return ansArr
}
75 颜色分类
刷油漆法:i 追踪 0 边界,j 追踪 1 边界
题干:只含 0/1/2 的数组,原地排序(荷兰国旗问题)。
var sortColors = function(nums) {
let i = 0 // 0 的边界
let j = 0 // 1 的边界
for (let k = 0; k < nums.length; k++) {
let num = nums[k]
if (num === 0) {
nums[j] = 1 // 先刷 1
nums[i] = 0 // 再刷 0(可能覆盖刚刷的 1)
i++; j++
} else if (num === 1) {
nums[j] = 1
j++
}
}
// 剩余位置刷 2
for (let k = j; k < nums.length; k++) nums[k] = 2
}
88 合并两个有序数组
逆向双指针:从后往前填,不需要额外空间
题干:nums1 长度 m+n(后 n 个为 0),合并 nums2 进 nums1,保持有序。
var merge = function (nums1, m, nums2, n) {
let i = m - 1
let j = n - 1
let k = m + n - 1 // 从后往前填
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j]) {
nums1[k] = nums1[i]
i--
} else {
nums1[k] = nums2[j]
j--
}
k--
}
// nums2 还有剩余就补上
while (j >= 0) {
nums1[k] = nums2[j]
j--; k--
}
}