On this page
javascript
求数组中最大的两个值
求数组中最大的两个值
考察点分析
这道题主要考察以下几个方面:
- 数组遍历和比较操作
- 时间复杂度优化
- 边界情况处理能力
- 算法思维
技术解析
原理剖析
求数组中最大的两个值,有多种解决方案:
- 排序法:将数组排序后取最后两个元素
- 两次遍历法:第一次找最大值,第二次找第二大值
- 单次遍历法:维护两个变量,分别记录最大值和第二大值
其中,单次遍历法的时间复杂度最优,为O(n)。
常见误区
- 忽略数组长度检查:当数组长度小于2时,无法返回两个最大值
- 重复计算最大值:在两次遍历法中,第二次遍历时没有排除第一次找到的最大值
- 未考虑相等元素:当有多个元素值相同且为最大值时的处理
- 未考虑负数情况:初始化第二大值为0可能导致负数数组出错
问题解答
1. 排序法
function findTwoLargest(arr) {
if (!Array.isArray(arr) || arr.length < 2) {
return "数组长度不足";
}
// 对数组进行降序排序
const sortedArr = [...arr].sort((a, b) => b - a);
// 返回最大的两个值
return [sortedArr[0], sortedArr[1]];
}
时间复杂度:O(n log n),主要受排序算法影响
2. 两次遍历法
function findTwoLargest(arr) {
if (!Array.isArray(arr) || arr.length < 2) {
return "数组长度不足";
}
// 第一次遍历找最大值
let max = -Infinity;
let maxIndex = -1;
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
maxIndex = i;
}
}
// 第二次遍历找第二大值
let secondMax = -Infinity;
for (let i = 0; i < arr.length; i++) {
if (i !== maxIndex && arr[i] > secondMax) {
secondMax = arr[i];
}
}
return [max, secondMax];
}
时间复杂度:O(n),只需要遍历数组两次
3. 单次遍历法
function findTwoLargest(arr) {
if (!Array.isArray(arr) || arr.length < 2) {
return "数组长度不足";
}
let max = -Infinity;
let secondMax = -Infinity;
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max) {
// 当前值大于最大值,更新最大值和第二大值
secondMax = max;
max = arr[i];
} else if (arr[i] > secondMax) {
// 当前值大于第二大值但小于最大值,更新第二大值
secondMax = arr[i];
}
}
return [max, secondMax];
}
时间复杂度:O(n),只需要遍历数组一次
4. 测试用例
// 测试
console.log(findTwoLargest([1, 2, 3, 4, 5])); // [5, 4]
console.log(findTwoLargest([5, 5, 4, 3, 2])); // [5, 5]
console.log(findTwoLargest([-1, -2, -3, -4])); // [-1, -2]
console.log(findTwoLargest([10])); // "数组长度不足"
深度追问
1. 如何处理有多个相同的最大值的情况?
如果我们严格定义"两个最大的值"为不同的值,当有多个相同的最大值时,我们需要找出最大值和严格小于最大值的第二大值。
function findTwoDistinctLargest(arr) {
if (!Array.isArray(arr) || arr.length < 2) {
return "数组长度不足";
}
// 先排序去重
const uniqueSorted = [...new Set(arr)].sort((a, b) => b - a);
// 如果去重后长度不足2,说明数组中所有元素都相同
if (uniqueSorted.length < 2) {
return "没有两个不同的值";
}
// 返回最大的两个不同值
return [uniqueSorted[0], uniqueSorted[1]];
}
// 测试
console.log(findTwoDistinctLargest([5, 5, 5, 3, 2])); // [5, 3]
console.log(findTwoDistinctLargest([5, 5, 5, 5])); // "没有两个不同的值"
另一种方法是在单次遍历中处理:
function findTwoDistinctLargest(arr) {
if (!Array.isArray(arr) || arr.length < 2) {
return "数组长度不足";
}
let max = -Infinity;
let secondMax = -Infinity;
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max) {
secondMax = max;
max = arr[i];
} else if (arr[i] < max && arr[i] > secondMax) {
// 只有当元素严格小于max且大于secondMax时才更新secondMax
secondMax = arr[i];
}
}
// 检查是否找到了第二大的不同值
if (secondMax === -Infinity) {
return "没有两个不同的值";
}
return [max, secondMax];
}
2. 如何优化空间复杂度?
排序法 :空间复杂度为O(n),因为创建了新数组
// 优化空间复杂度的排序法 - 原地排序 function findTwoLargestOptimized(arr) { if (!Array.isArray(arr) || arr.length < 2) { return "数组长度不足"; } // 直接对原数组排序,不创建新数组 arr.sort((a, b) => b - a); return [arr[0], arr[1]]; }
注意:这种方法会修改原数组,如果不希望修改原数组,就无法避免O(n)的空间复杂度。
两次遍历法和单次遍历法 :空间复杂度均为O(1),只使用了常数个变量
// 单次遍历法的空间复杂度已经是最优O(1) function findTwoLargest(arr) { if (!Array.isArray(arr) || arr.length < 2) { return "数组长度不足"; } let max = arr[0]; let secondMax = arr[1]; // 确保max是最大的,secondMax是第二大的 if (secondMax > max) { [max, secondMax] = [secondMax, max]; } // 从第三个元素开始遍历 for (let i = 2; i < arr.length; i++) { if (arr[i] > max) { secondMax = max; max = arr[i]; } else if (arr[i] > secondMax) { secondMax = arr[i]; } } return [max, secondMax]; }
总结:对于这个问题,单次遍历法已经达到了O(1)的最优空间复杂度,无需进一步优化。如果不希望修改原数组,排序法的O(n)空间复杂度是无法避免的。
3. 如何处理大数据量的情况?
对于非常大的数组,可以考虑分治法或并行处理:
function findTwoLargestParallel(arr) {
if (!Array.isArray(arr) || arr.length < 2) {
return "数组长度不足";
}
// 将数组分成多个块
const chunkSize = 10000; // 根据实际情况调整
const chunks = [];
for (let i = 0; i < arr.length; i += chunkSize) {
chunks.push(arr.slice(i, i + chunkSize));
}
// 对每个块找出最大的两个值
const results = chunks.map(chunk => {
let max = -Infinity;
let secondMax = -Infinity;
for (let i = 0; i < chunk.length; i++) {
if (chunk[i] > max) {
secondMax = max;
max = chunk[i];
} else if (chunk[i] > secondMax) {
secondMax = chunk[i];
}
}
return [max, secondMax];
});
// 合并结果
let finalMax = -Infinity;
let finalSecondMax = -Infinity;
for (let i = 0; i < results.length; i++) {
const [max, secondMax] = results[i];
if (max > finalMax) {
finalSecondMax = Math.max(finalMax, secondMax);
finalMax = max;
} else if (max > finalSecondMax) {
finalSecondMax = max;
} else if (secondMax > finalSecondMax) {
finalSecondMax = secondMax;
}
}
return [finalMax, finalSecondMax];
}
// 在实际环境中,可以使用Web Workers或Node.js的Worker Threads实现真正的并行处理
在JavaScript中,可以使用Web Workers或Node.js的Worker Threads来实现真正的并行计算,进一步提高大数据量下的处理效率。
4. 如何扩展到求前K大的元素?
求数组中前K大的元素,可以使用最小堆(优先队列)实现:
class MinHeap {
constructor() {
this.heap = [];
}
getParentIndex(i) {
return Math.floor((i - 1) / 2);
}
getLeftChildIndex(i) {
return 2 * i + 1;
}
getRightChildIndex(i) {
return 2 * i + 2;
}
swap(i1, i2) {
[this.heap[i1], this.heap[i2]] = [this.heap[i2], this.heap[i1]];
}
peek() {
return this.heap[0];
}
size() {
return this.heap.length;
}
insert(value) {
this.heap.push(value);
this.siftUp(this.heap.length - 1);
}
siftUp(index) {
let parent = this.getParentIndex(index);
while (index > 0 && this.heap[parent] > this.heap[index]) {
this.swap(parent, index);
index = parent;
parent = this.getParentIndex(index);
}
}
extractMin() {
if (this.heap.length === 0) return null;
const min = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = last;
this.siftDown(0);
}
return min;
}
siftDown(index) {
let minIndex = index;
const left = this.getLeftChildIndex(index);
const right = this.getRightChildIndex(index);
const size = this.heap.length;
if (left < size && this.heap[left] < this.heap[minIndex]) {
minIndex = left;
}
if (right < size && this.heap[right] < this.heap[minIndex]) {
minIndex = right;
}
if (index !== minIndex) {
this.swap(index, minIndex);
this.siftDown(minIndex);
}
}
}
function findKLargest(arr, k) {
if (!Array.isArray(arr) || arr.length < k || k <= 0) {
return "参数不合法";
}
// 创建一个最小堆
const minHeap = new MinHeap();
// 先将前k个元素加入堆
for (let i = 0; i < k; i++) {
minHeap.insert(arr[i]);
}
// 遍历剩余元素
for (let i = k; i < arr.length; i++) {
// 如果当前元素大于堆顶,则替换堆顶并重新调整堆
if (arr[i] > minHeap.peek()) {
minHeap.extractMin();
minHeap.insert(arr[i]);
}
}
// 从堆中提取所有元素(从小到大)
const result = [];
while (minHeap.size() > 0) {
result.unshift(minHeap.extractMin());
}
return result;
}
// 测试
console.log(findKLargest([3, 1, 5, 7, 2, 4, 9, 6], 3)); // [7, 9, 6]
时间复杂度:O(n log k),其中n是数组长度,k是要找的最大元素个数。空间复杂度:O(k),用于存储堆。 这种方法对于大数据量且k远小于n的情况特别高效。
Last updated 18 Mar 2025, 10:40 +0800 .