leetcode有关数组题目
在做题过程中参考了代码随想录的解题思路以及刷题顺序下面是代码随想录的网站2026大厂笔试真题整理腾讯阿里字节美团华为京东小红书算法笔试题含题解OJ判题 | 代码随想录-全网最全算法数据结构刷题学习路线|图文视频教程|免费开源目录索引基础入门二分查找 (LeetCode 704) —— 拿捏区间定义的艺术双指针初探移除元素 (LeetCode 27) —— 快慢指针的原地重塑双指针进阶有序数组的平方 (LeetCode 977) —— 两端向中间的夹击相遇终极压轴长度最小的子数组 (LeetCode 209) —— 滑动窗口的不回头哲学1. 二分查找 (LeetCode 704)思考到底写left right还是left right很多同学背二分查找的模板往往死在循环条件和边界更新上。其实根本原因取决于你对“查找区间”的定义。这里我们采用最符合直觉的左闭右闭[left, right]区间随着区间不断砍掉一半left和right离得越来越近最后它们指到了同一个位置即left right。此时区间变成了[left, left]。在数学上这个区间依然包含最后一个元素如果写while (left right)程序会进入循环去检查这最后一个数。如果写while (left right)循环直接终止这最后一个元素就被漏掉了如果它恰好是target就会直接返回-1造成 Bug。代码实现Javaclass Solution { public int search(int[] nums, int target) { // 剪枝优化如果 target 超出数组边界直接返回 -1 if (target nums[0] || target nums[nums.length - 1]) { return -1; } int left 0, right nums.length - 1; while (left right) { // 左闭右闭区间当 left right 时依旧有效 // 安全写法防止 (left right) 直接相加导致 int 溢出 int mid left (right - left) / 2; if (nums[mid] target) { right mid - 1; // target 在左区间所以更新右边界 } else if (nums[mid] target) { left mid 1; // target 在右区间所以更新左边界 } else { return mid; // 找到了直接返回下标 } } return -1; } }时间复杂度O ( log n ) O(\log n)O(logn)空间复杂度O ( 1 ) O(1)O(1)2. 移除元素 (LeetCode 27)思考如何在O ( 1 ) O(1)O(1)空间原地重塑数组由于数组在内存中是连续存储的我们不能真正地“删除”某个元素只能用后面的元素去“覆盖”。这里引入快慢指针Fast Slow Pointersfast快指针负责冲锋陷阵遍历原数组去寻找新数组所需要的元素即不等于val的元素。slow慢指针负责镇守大后方维护新数组的更新位置。代码实现Javaclass Solution { public int removeElement(int[] nums, int val) { int slow 0; // slow 指针用来维持不含 val 的新数组 for (int fast 0; fast nums.length; fast) { // 只要快指针发现不是 val 的元素就把它“搬”到慢指针的位置 if (nums[fast] ! val) { nums[slow] nums[fast]; slow; // 慢指针向前移动一位等待下一个新元素 } } return slow; // slow 的值正好就是新数组的长度 } }时间复杂度O ( n ) O(n)O(n)快指针一步到头。空间复杂度O ( 1 ) O(1)O(1)无任何额外空间开销。3. 有序数组的平方 (LeetCode 977)思考最大值藏在何处既然原数组是升序的并且含有负数那么平方后的最大值只可能出现在哪里答案要么在最左边绝对值很大的负数要么在最右边绝对值很大的正数。基于这个单调性我们可以使用左右双指针从两端向中间夹击比较left和right平方的大小谁的大谁就是当前剩余元素里的最大值。将最大值逆序放入新数组result的尾部从后往前填。代码实现Javaclass Solution { public int[] sortedSquares(int[] nums) { int left 0; int right nums.length - 1; int k nums.length - 1; // 新数组的指针从后往前填充 int[] result new int[nums.length]; while (left right) { // 比较左右两端平方大小 if (nums[left] * nums[left] nums[right] * nums[right]) { result[k--] nums[left] * nums[left]; left; // 左指针向右移 } else { result[k--] nums[right] * nums[right]; right--; // 右指针向左移 } } return result; } }时间复杂度O ( n ) O(n)O(n)相比于暴力平方后再排序的O ( n log n ) O(n \log n)O(nlogn)这是质的飞跃。空间复杂度O ( n ) O(n)O(n)用于存放结果数组。4. 长度最小的子数组 (LeetCode 209)思考滑动窗口到底比暴力好在哪里很多人觉得带break的暴力解法和滑动窗口差不多其实差远了。假设nums [1, 2, 3, 4, 5]target 10。暴力解法i0时累加123410满足条件记录并break。但当i1时暴力解法不得不回滚去重新连加234...。这些中间数字被大量重复计算最坏复杂度依然是O ( n 2 ) O(n^2)O(n2)。滑动窗口它利用了“连续子数组”的单调性。当j走到 4 满足123410后我们不需要回滚重加直接用减法sum - nums[left]把最左边的1吐出来变成[2,3,4]。接着只需要再加一个5就能继续判断。总结暴力解法是每搬完一车砖就要把车倒回起点重新装而滑动窗口是车子一直往前开前面装一块砖right后面扔一块砖left绝不倒车。代码实现Javaclass Solution { public int minSubArrayLen(int target, int[] nums) { int left 0; // 窗口起始位置 int result Integer.MAX_VALUE; // 初始化为极大值 int sum 0; // 维持当前滑动窗口内的数值之和 // right 代表窗口的终止位置 for (int right 0; right nums.length; right) { sum nums[right]; // 扩大窗口 // 注意这里必须用 while因为左边界可能需要连续右移缩小窗口 // 如果用 if会导致缩减不彻底漏掉更短的符合条件的子串 while (sum target) { int lens right - left 1; // 动态计算当前窗口长度 result result lens ? result : lens; // 更新极小值 sum - nums[left]; // 精髓缩小窗口并向右移动左指针 } } // 如果 result 没被改动过说明没有符合条件的子数组返回 0 return result Integer.MAX_VALUE ? 0 : result; } }为什么看似嵌套循环时间复杂度确是O ( n ) O(n)O(n)不要被for循环里嵌套while循环的外表给骗了我们看每一个元素的生命周期它最多被right指针扫描推入窗口1次最多被left指针弹出窗口1次。每个元素总共被操作了2次。因此总总移动次数不超过2 n 2n2n时间复杂度是不折不扣的O ( n ) O(n)O(n)。️ 总结与心法从二分查找的固定区间到快慢指针/左右指针的联动再到滑动窗口的动态伸缩你会发现双指针算法的本质就是利用题目中的单调性或边界规律剪掉不必要的盲目遍历。双指针适合用于“原地修改”或“两端有序”的场景。滑动窗口专门治各种“满足条件的连续子数组/子串”的疑难杂症。希望这篇博文能帮你彻底理清数组这一关的思路如果对你有帮助希望点赞支持感谢如果有任何疑问欢迎在评论区留言交流我们下期见