大家好,我是14年菜鸟前端,在面试和日常刷题中总结了10 道超高频算法题,涵盖排序、LIS、双指针、哈希、前缀和、贪心等核心考点,每道题都附带清晰思路 + 极简最优代码 + JS/Dart 双语言实现,非常适合面试突击和博客学习。
全文结构统一:题目描述 → 思路分析 → 核心数据结构 / 算法 → 代码实现(JS + Dart) → 复杂度分析
1. 俄罗斯套娃信封问题(困难・面试必考)
题目描述
给你一个二维整数数组 envelopes,其中 envelopes[i] = [wi, hi],表示第 i 个信封的宽度和高度。当一个信封的宽度和高度都比另一个大时,才能嵌套。求最多能套多少层。
思路
- 排序:宽度升序,宽度相同时高度降序(避免同宽重复嵌套)
- 提取高度数组,求最长严格递增子序列 LIS
- LIS 使用二分优化到 O(n log n)
JS 实现
javascript
运行
var maxEnvelopes = function(envelopes) {
envelopes.sort((a, b) => {
if (a[0] !== b[0]) return a[0] - b[0]
return b[1] - a[1]
})
const heights = envelopes.map(e => e[1])
const tails = []
for (const h of heights) {
let l = 0, r = tails.length
while (l < r) {
const mid = (l + r) >> 1
if (tails[mid] >= h) r = mid
else l = mid + 1
}
l === tails.length ? tails.push(h) : (tails[l] = h)
}
return tails.length
}
Dart 实现
dart
int maxEnvelopes(List<List<int>> envelopes) {
envelopes.sort((a, b) {
if (a[0] != b[0]) return a[0] - b[0];
return b[1] - a[1];
});
final heights = envelopes.map((e) => e[1]).toList();
final tails = <int>[];
for (int h in heights) {
int l = 0, r = tails.length;
while (l < r) {
int mid = (l + r) ~/ 2;
if (tails[mid] >= h) {
r = mid;
} else {
l = mid + 1;
}
}
l == tails.length ? tails.add(h) : tails[l] = h;
}
return tails.length;
}
复杂度
- 时间:O(n log n)
- 空间:O(n)
2. 最长连续递增序列(简单・双指针)
题目描述
给定一个未排序数组,找出最长连续递增子序列的长度。
思路
- 双指针维护一个递增窗口
- 遇到下降就重置左指针
JS
javascript
运行
var findLengthOfLCIS = function(nums) {
if (!nums.length) return 0
let maxLen = 1, left = 0
for (let right = 1; right < nums.length; right++) {
if (nums[right] <= nums[right - 1]) left = right
maxLen = Math.max(maxLen, right - left + 1)
}
return maxLen
}
Dart
dart
int findLengthOfLCIS(List<int> nums) {
if (nums.isEmpty) return 0;
int maxLen = 1, left = 0;
for (int right = 1; right < nums.length; right++) {
if (nums[right] <= nums[right - 1]) left = right;
maxLen = max(maxLen, right - left + 1);
}
return maxLen;
}
复杂度
O(n) / O(1)
3. 最长连续序列(困难・哈希经典)
题目描述
给定未排序数组,找出数字连续最长序列的长度(要求 O (n))。
思路
- 用 Set 去重 + O (1) 查询
- 只从序列起点(num-1 不存在)开始统计
JS
javascript
运行
var longestConsecutive = function(nums) {
const set = new Set(nums)
let maxLen = 0
for (const num of set) {
if (!set.has(num - 1)) {
let cur = num, len = 1
while (set.has(cur + 1)) {
cur++
len++
}
maxLen = Math.max(maxLen, len)
}
}
return maxLen
}
Dart
dart
int longestConsecutive(List<int> nums) {
final set = nums.toSet();
int maxLen = 0;
for (int num in set) {
if (!set.contains(num - 1)) {
int cur = num, len = 1;
while (set.contains(cur + 1)) {
cur++;
len++;
}
maxLen = max(maxLen, len);
}
}
return maxLen;
}
复杂度
O(n) / O(n)
4. 盛最多水的容器(困难・面试高频)
题目描述
数组代表高度,选两条线,使它们与 x 轴容纳最多水。
思路
- 左右双指针
- 每次移动较短边(贪心)
JS
javascript
运行
var maxArea = function(height) {
let l = 0, r = height.length - 1, max = 0
while (l < r) {
const h = Math.min(height[l], height[r])
max = Math.max(max, h * (r - l))
height[l] < height[r] ? l++ : r--
}
return max
}
Dart
dart
int maxArea(List<int> height) {
int l = 0, r = height.length - 1, max = 0;
while (l < r) {
int h = min(height[l], height[r]);
max = max(max, h * (r - l));
height[l] < height[r] ? l++ : r--;
}
return max;
}
复杂度
O(n) / O(1)
5. 寻找两个正序数组的中位数(困难・双指针)
题目描述
两个正序数组,找出整体中位数,要求尽量最优。
思路
双指针遍历,只走到中位数位置即可,无需合并全部数组。
JS
javascript
运行
var findMedianSortedArrays = function(nums1, nums2) {
const len1 = nums1.length, len2 = nums2.length
const total = len1 + len2
let i = 0, j = 0, pre = 0, cur = 0
for (let k = 0; k <= total ~/ 2; k++) {
pre = cur
if (i < len1 && (j >= len2 || nums1[i] < nums2[j])) {
cur = nums1[i++]
} else {
cur = nums2[j++]
}
}
return total % 2 === 1 ? cur : (pre + cur) / 2
}
Dart
dart
double findMedianSortedArrays(List<int> nums1, List<int> nums2) {
int len1 = nums1.length, len2 = nums2.length;
int total = len1 + len2;
int i = 0, j = 0, pre = 0, cur = 0;
for (int k = 0; k <= total ~/ 2; k++) {
pre = cur;
if (i < len1 && (j >= len2 || nums1[i] < nums2[j])) {
cur = nums1[i++];
} else {
cur = nums2[j++];
}
}
return total.isOdd ? cur.toDouble() : (pre + cur) / 2.0;
}
复杂度
O(m+n) / O(1)
6. 删除有序数组中的重复项(简单・快慢指针)
题目描述
原地删除重复元素,返回新长度。
思路
- 慢指针:有效区末尾
- 快指针:遍历
JS
javascript
运行
var removeDuplicates = function(nums) {
if (!nums.length) return 0
let slow = 0
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) {
nums[++slow] = nums[fast]
}
}
return slow + 1
}
Dart
dart
int removeDuplicates(List<int> nums) {
if (nums.isEmpty) return 0;
int slow = 0;
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
nums[++slow] = nums[fast];
}
}
return slow + 1;
}
复杂度
O(n) / O(1)
7. 和为 K 的子数组(中等・前缀和 + 哈希)
题目描述
统计数组中和为 K 的连续子数组数量。
思路
- 前缀和
preSum - 哈希表存
preSum 出现次数 - 子数组和 = preSum [j] - preSum [i]
JS
javascript
运行
var subarraySum = function(nums, k) {
const map = new Map([[0, 1]])
let preSum = 0, count = 0
for (const n of nums) {
preSum += n
count += map.get(preSum - k) || 0
map.set(preSum, (map.get(preSum) || 0) + 1)
}
return count
}
Dart
dart
int subarraySum(List<int> nums, int k) {
final map = <int, int>{0: 1};
int preSum = 0, count = 0;
for (int n in nums) {
preSum += n;
count += map[preSum - k] ?? 0;
map[preSum] = (map[preSum] ?? 0) + 1;
}
return count;
}
复杂度
O(n) / O(n)
8. nSum 系列(简单・哈希 + 双指针)
这里放最常考的 2Sum + 3Sum
2Sum JS
javascript
运行
var twoSum = function(nums, target) {
const map = new Map()
for (let i = 0; i < nums.length; i++) {
const t = target - nums[i]
if (map.has(t)) return [map.get(t), i]
map.set(nums[i], i)
}
}
2Sum Dart
dart
List<int> twoSum(List<int> nums, int target) {
final map = <int, int>{};
for (int i = 0; i < nums.length; i++) {
int t = target - nums[i];
if (map.containsKey(t)) return [map[t]!, i];
map[nums[i]] = i;
}
return [];
}
3Sum JS
javascript
运行
var threeSum = function(nums) {
nums.sort((a, b) => a - b)
const res = []
for (let i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue
let l = i + 1, r = nums.length - 1
while (l < r) {
const sum = nums[i] + nums[l] + nums[r]
if (sum === 0) {
res.push([nums[i], nums[l], nums[r]])
while (l < r && nums[l] === nums[l + 1]) l++
while (l < r && nums[r] === nums[r - 1]) r--
l++; r--
} else sum < 0 ? l++ : r--
}
}
return res
}
3Sum Dart
dart
List<List<int>> threeSum(List<int> nums) {
nums.sort();
final res = <List<int>>[];
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int l = i + 1, r = nums.length - 1;
while (l < r) {
int sum = nums[i] + nums[l] + nums[r];
if (sum == 0) {
res.add([nums[i], nums[l], nums[r]]);
while (l < r && nums[l] == nums[l + 1]) l++;
while (l < r && nums[r] == nums[r - 1]) r--;
l++; r--;
} else {
sum < 0 ? l++ : r--;
}
}
}
return res;
}
9. 接雨水(困难・面试压轴)
题目描述
给定高度数组,计算能接多少雨水。
思路
预处理左右最大高度数组,每个位置存水量:min(leftMax[i], rightMax[i]) - height[i]
JS
javascript
运行
var trap = function(height) {
const n = height.length
if (n <= 2) return 0
const leftMax = new Array(n)
const rightMax = new Array(n)
leftMax[0] = height[0]
rightMax[n - 1] = height[n - 1]
for (let i = 1; i < n; i++) leftMax[i] = Math.max(leftMax[i - 1], height[i])
for (let i = n - 2; i >= 0; i--) rightMax[i] = Math.max(rightMax[i + 1], height[i])
let water = 0
for (let i = 0; i < n; i++) {
water += Math.min(leftMax[i], rightMax[i]) - height[i]
}
return water
}
Dart
dart
int trap(List<int> height) {
int n = height.length;
if (n <= 2) return 0;
final leftMax = List.filled(n, 0);
final rightMax = List.filled(n, 0);
leftMax[0] = height[0];
rightMax[n - 1] = height[n - 1];
for (int i = 1; i < n; i++) {
leftMax[i] = max(leftMax[i - 1], height[i]);
}
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = max(rightMax[i + 1], height[i]);
}
int water = 0;
for (int i = 0; i < n; i++) {
water += min(leftMax[i], rightMax[i]) - height[i];
}
return water;
}
复杂度
O(n) / O(n)
10. 跳跃游戏系列(中等・贪心)
包含 I(能否跳到终点)+ II(最小步数)
跳跃游戏 I JS
javascript
运行
var canJump = function(nums) {
let maxReach = 0
for (let i = 0; i < nums.length; i++) {
if (i > maxReach) return false
maxReach = Math.max(maxReach, i + nums[i])
if (maxReach >= nums.length - 1) return true
}
return true
}
跳跃游戏 II JS
javascript
运行
var jump = function(nums) {
let step = 0, end = 0, maxReach = 0
for (let i = 0; i < nums.length - 1; i++) {
maxReach = Math.max(maxReach, i + nums[i])
if (i === end) {
end = maxReach
step++
}
}
return step
}
Dart 双实现
dart
bool canJump(List<int> nums) {
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = max(maxReach, i + nums[i]);
if (maxReach >= nums.length - 1) return true;
}
return true;
}
int jump(List<int> nums) {
int step = 0, end = 0, maxReach = 0;
for (int i = 0; i < nums.length - 1; i++) {
maxReach = max(maxReach, i + nums[i]);
if (i == end) {
end = maxReach;
step++;
}
}
return step;
}