Python学习第66天: 数据分析概述
2026/6/6 18:26:41
对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎
给定一个整数数组nums和一个整数k,有一个大小为k的滑动窗口从数组的最左侧移动到最右侧。你只可以看到在滑动窗口内的k个数字,滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7这个问题看似简单,实则暗藏玄机:
考虑以下前端应用场景:
遍历每个滑动窗口,在每个窗口内找到最大值。
时间复杂度: O(n×k)
空间复杂度: O(1) 或 O(n-k+1) 用于存储结果
评价: 简单直观,但效率低下,不适用于大规模数据
维护一个单调递减的双端队列,存储元素的索引而非值:
将数组分成大小为k的块,预处理每个块的前缀最大值和后缀最大值。
时间复杂度: O(n)
空间复杂度: O(n)
优点: 实现相对简单,适合理解分治思想
/** * 暴力解法 - 直观但低效 * 时间复杂度: O(n×k) * 空间复杂度: O(n-k+1) */functionmaxSlidingWindowBruteForce(nums,k){if(!nums.length||k===0)return[];constresult=[];constn=nums.length;for(leti=0;i<=n-k;i++){letmax=-Infinity;for(letj=i;j<i+k;j++){max=Math.max(max,nums[j]);}result.push(max);}returnresult;}/** * 双端队列法 - 最优解 * 时间复杂度: O(n) * 空间复杂度: O(k) */functionmaxSlidingWindowDeque(nums,k){if(!nums.length||k===0)return[];constresult=[];constdeque=[];// 存储索引的单调递减队列for(leti=0;i<nums.length;i++){// 1. 移除队列中不在窗口范围内的索引while(deque.length>0&&deque[0]<=i-k){deque.shift();}// 2. 移除队列尾部所有小于当前元素的索引// 保持队列单调递减(对应值)while(deque.length>0&&nums[deque[deque.length-1]]<nums[i]){deque.pop();}// 3. 将当前索引加入队列deque.push(i);// 4. 当窗口形成时,将队列头部(最大值)加入结果if(i>=k-1){result.push(nums[deque[0]]);}}returnresult;}// 优化版本:使用双指针减少数组操作functionmaxSlidingWindowOptimized(nums,k){if(!nums.length||k===0)return[];constresult=[];constdeque=newArray(k);// 预分配数组空间letfront=0,rear=0;// 双指针模拟双端队列for(leti=0;i<nums.length;i++){// 移除不在窗口内的元素while(front<rear&&deque[front]<=i-k){front++;}// 维护单调递减性while(front<rear&&nums[deque[rear-1]]<nums[i]){rear--;}// 添加当前索引deque[rear++]=i;// 收集结果if(i>=k-1){result.push(nums[deque[front]]);}}returnresult;}/** * 分块预处理法 * 时间复杂度: O(n) * 空间复杂度: O(n) */functionmaxSlidingWindowBlock(nums,k){if(!nums.length||k===0)return[];constn=nums.length;constprefixMax=newArray(n);constsuffixMax=newArray(n);constresult=newArray(n-k+1);// 计算前缀最大值for(leti=0;i<n;i++){if(i%k===0){prefixMax[i]=nums[i];}else{prefixMax[i]=Math.max(prefixMax[i-1],nums[i]);}}// 计算后缀最大值for(leti=n-1;i>=0;i--){if(i===n-1||(i+1)%k===0){suffixMax[i]=nums[i];}else{suffixMax[i]=Math.max(suffixMax[i+1],nums[i]);}}// 计算每个窗口的最大值for(leti=0;i<=n-k;i++){constj=i+k-1;result[i]=Math.max(suffixMax[i],prefixMax[j]);}returnresult;}| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 | 前端应用场景 |
|---|---|---|---|---|---|
| 暴力解法 | O(n×k) | O(n-k+1) | 1. 实现简单 2. 代码直观易懂 | 1. 效率低下 2. 不适用大数据 | 快速原型开发,数据量小的场景 |
| 双端队列法 | O(n) | O(k) | 1. 时间复杂度最优 2. 空间效率高 3. 实时性好 | 1. 实现相对复杂 2. 需要理解单调队列 | 实时监控系统、大数据可视化 |
| 分块预处理法 | O(n) | O(n) | 1. 实现相对简单 2. 可并行计算 | 1. 空间消耗大 2. 需要预计算 | 静态数据分析、批量处理 |
// 测试不同数据规模下的性能consttestCases=[{n:1000,k:10,desc:'小数据量'},{n:100000,k:100,desc:'中等数据量'},{n:1000000,k:1000,desc:'大数据量'}];// 预期结果:// 1. 小数据量:三种方法都可接受// 2. 中等数据量:暴力法开始吃力// 3. 大数据量:只有双端队列法高效// 实时监控最近N个帧率的最高值classPerformanceMonitor{constructor(windowSize){this.windowSize=windowSize;this.fpsQueue=[];// 双端队列存储时间戳this.maxFPS=0;}recordFrame(timestamp){// 移除旧帧while(this.fpsQueue.length>0&×tamp-this.fpsQueue[0]>this.windowSize){this.fpsQueue.shift();}// 添加新帧this.fpsQueue.push(timestamp);// 计算当前FPSconstcurrentFPS=this.fpsQueue.length/(this.windowSize/1000);this.maxFPS=Math.max(this.maxFPS,currentFPS);return{current:currentFPS,max:this.maxFPS};}}// 虚拟滚动中计算可见区域的最大元素高度classVirtualScrollOptimizer{constructor(containerHeight,itemHeights){this.itemHeights=itemHeights;this.containerHeight=containerHeight;}// 使用滑动窗口计算渲染区域calculateRenderRange(scrollTop){// 确定可见区域conststartIdx=Math.floor(scrollTop/100);constendIdx=Math.min(startIdx+Math.ceil(this.containerHeight/100)+5,this.itemHeights.length);// 使用滑动窗口最大值算法优化渲染constmaxHeightInViewport=this.getMaxHeightInRange(startIdx,endIdx);return{startIdx,endIdx,maxHeight:maxHeightInViewport,buffer:5// 预渲染的缓冲项};}}// 股票价格实时图表 - 计算最近N分钟最高价classStockChart{constructor(timeWindow){this.timeWindow=timeWindow;// 时间窗口(分钟)this.priceQueue=[];// {timestamp, price}this.maxPriceDeque=[];// 单调递减队列}addPrice(price,timestamp){// 清理过期数据while(this.priceQueue.length>0&×tamp-this.priceQueue[0].timestamp>this.timeWindow*60000){this.priceQueue.shift();}// 更新价格队列this.priceQueue.push({price,timestamp});// 更新单调队列while(this.maxPriceDeque.length>0&&this.maxPriceDeque[this.maxPriceDeque.length-1].price<price){this.maxPriceDeque.pop();}this.maxPriceDeque.push({price,timestamp});// 清理单调队列中的过期数据while(this.maxPriceDeque.length>0&×tamp-this.maxPriceDeque[0].timestamp>this.timeWindow*60000){this.maxPriceDeque.shift();}return{currentPrice:price,maxInWindow:this.maxPriceDeque[0].price,prices:this.priceQueue.map(p=>p.price)};}}