基础题
移除元素、移动零
两道题的基本思路基本一致,但移除元素比移动零少了需要按原有顺序排列的限制,因此可以进行优化。
方法一:快慢指针
双指针,快指针用于找到所有非val的值,慢指针用于记录。
快慢指针同时往右走,用快指针指向的值替换慢指针指向的值。如果数组内没有val,意味着快慢指针始终指向同一个值直到遍历结束。一旦出现一个val,快指针会继续遍历,找到第一个不为val的值,但慢指针还是指向为val的值。此时用快指针的值替换慢指针的值,快慢指针继续遍历,直至快指针指向num.length。此时快指针已经处理过所有的值,慢指针指向第一个为val的值,即非val数组的长度是慢指针此时的索引值。
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
let slow = 0, fast = 0
while (fast < nums.length) {
if(nums[fast] === val) {
fast++
continue
}
const o_s = nums[slow], o_f = nums[fast]
nums[slow++] = o_f
nums[fast++] = o_s
}
return slow
};
方法二:双指针优化
即leetcode官方优化版,思路一致,写法不太一样。核心都是把右边的非val值和左边的val值替换,当两个指针相遇时右指针右侧都是val值,左指针左侧都是非val值。区别和二分法中左/右边界的法一法二一致。right取nums.length - 1和nums.length的差别导致跳出时的条件和返回的值不一样。
由于移动元素不需要保证移动后数组元素的相对顺序,可以考虑分别从两边遍历数组,左边的指针如果指向非val值直接继续遍历,如果指向val值则和右边第一个非val值调换后继续遍历。
需要注意的点是最后的返回值取什么。虽然最后一次循环无法判断是走入了哪一个分支,可以假设当right===left时指向的是val值,right--后跳出,指向最后一个非val值,返回right+1(或left)。反之如果指向的是非val值,仍返回right+1(或left)。
分析:最后返回的值对应的是数组调整后第一个指向val值的索引,最后跳出时一定有left > right,而right右边的值都是val,left左边的值都是非val,因而left跳出时指向的一定是第一个非val值。
var removeElement = function(nums, val) {
let left = 0, right = nums.length - 1
while (left <= right) {
if(nums[right] === val) {
right--
continue
}
if(nums[left] === val) {
nums[left] = nums[right--]
}
left += 1
}
return left // 或right + 1
};
代码优化
var removeElement = function(nums, val) {
let left = 0, right = nums.length - 1
while (left <= right) {
if(nums[right] !== val) {
nums[left++] = nums[right]
}
right--
}
return left // 或right + 1
};
其他基础题
题目 | 难度 | 备注 |
---|---|---|
反转字符串 | 简单 | |
应用题
乘最多水的容器
方法一:暴力解法(超时)
function maxArea(height) {
// 暴力
let area = 0
for (let left = 0; left < height.length - 1; left++) {
const leftHeight = height[left]
for (let right = left + 1; right < height.length; right++) {
const rightHeight = height[right]
area = Math.max(area, (right - left) * Math.min(leftHeight, rightHeight))
}
}
return area
}
方法二:双指针
*参考反转字符串
可以分析暴力解法中面积的计算方式
right-left最大为nums.length-1,此时左右指针分别在0, nums.length - 1,如果希望获得更大的面积可以发现只能移动height[left], height[right]中短的那一个。因为根据公式Math.min(height[left], height[right])可以发现面积只与短边有关,因此在不断向中心移动左右指针的过程中长(right-left)越小,每次移动短边获得可能的更大的面积。
// 由于算面积时取左右height值中低的那一个,因此如果向中心移动高值对应的坐标,面积只会更低。如果向中心移动低值对应的坐标,如果新的值大于低值,则面积可能会变大。
let left = 0, right = height.length - 1, area = 0
while (left < right) {
area = Math.max(area, (right - left) * Math.min(height[left], height[right]))
if (height[left] > height[right]) {
right -= 1
} else {
left += 1
}
}
return area
}
三数和
经典题了,核心思路是将数组排序,把题目转化为 第二个数和第三个数的和是负的第一个数
**代码中有些细节点要注意(比如不需要专门用一个值存重复的nums[start]和nums[second],既然排序,那重复的值必然相邻,判断nums[start++]和nums[start]是否相等就行)
function threeSum(nums) {
// 等同于找三个数 -nums[i] = nums[j] + nums[k]
// 当nums[i]值固定时,如果nums[j]的值也固定,那nums[k]的值也唯一了。
// 可以把题目简化为找到[i+1, nums.length)中和为-nums[i]的两个数
// 此时把数组排序可以避免很多的重复处理,1.nums[i]的值重复 2.nums[j]的值重复
if (nums.length < 3) return []
nums = nums.sort((a, b) => a - b)
const res = []
let sum = Number.MAX_SAFE_INTEGER
for (let i = 0; i < nums.length - 2; i++) {
if (sum + nums[i] === 0) {
continue
}
sum = -nums[i]
let left = i + 1, right = nums.length - 1
let cur_left = Number.MAX_SAFE_INTEGER
while (left < right) {
const cur_sum = nums[left] + nums[right]
if (cur_sum < sum) {
left += 1
} else if (cur_sum === sum) {
if (nums[left] === nums[cur_left]) {
left += 1
right -= 1
} else {
res.push([nums[i], nums[left], nums[right--]])
cur_left = left++
}
} else if (cur_sum > sum) {
right -= 1
}
}
}
return res
}
代码优化
function threeSum(nums) {
if (nums.length < 3) return []
nums = nums.sort((a, b) => a - b)
const res = []
let sum = Number.MAX_SAFE_INTEGER
for (let i = 0; i < nums.length - 2; i++) {
// 同一个nums[i]值只用处理一遍,但注意i>0
if (i > 0 && nums[i] === nums[i - 1]) {
continue
}
sum = -nums[i]
let left = i + 1, right = nums.length - 1
while (left < right) {
const cur_sum = nums[left] + nums[right]
if (cur_sum < sum) {
left += 1
} else if (cur_sum === sum) {
// 和一样,nums[second]一样时nums[third]一定一样
if (left > i + 1 && nums[left] === nums[left - 1]) {
left += 1
right -= 1
} else {
res.push([nums[i], nums[left++], nums[right--]])
}
} else if (cur_sum > sum) {
right -= 1
}
}
}
return res
}