跳到主要内容数组算法总结:二分查找、快慢指针、双指针与滑动窗口 | 极客日志编程语言java算法
数组算法总结:二分查找、快慢指针、双指针与滑动窗口
综述由AI生成总结了四种常见的数组算法:二分查找、快慢指针、双指针和滑动窗口。通过 LeetCode 经典例题,详细讲解了每种算法的核心思路及 Python、Java、C++、Go 多语言实现方案。重点分析了时间复杂度优化技巧,如二分查找的 O(log n) 要求、快慢指针的原地修改策略以及滑动窗口的动态收缩方法,旨在帮助读者掌握数组处理的核心面试考点。
ApiHolic21 浏览 一、二分查找(LeetCode 702)
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1。
你必须编写一个具有 O(log n) 时间复杂度的算法。
二分查找是一个非常经典的操作数组的算法,其使用的条件是必须是一个有序的数组(升序或降序),其核心是根据数组的有序性来将 target 与 nums[mid] 比较,若 target > nums[mid],则 target 在 mid 的右边区间,否则在左半区间。搞清楚这个问题后,代码就非常容易写出来了。
Python
def search(nums: list[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Java
public class BinarySearch {
public static int {
;
nums.length - ;
(left <= right) {
left + (right - left) / ;
(nums[mid] == target) {
mid;
} (nums[mid] < target) {
left = mid + ;
} {
right = mid - ;
}
}
-;
}
}
search
(int[] nums, int target)
int
left
=
0
int
right
=
1
while
int
mid
=
2
if
return
else
if
1
else
1
return
1
#include <iostream>
#include <vector>
using namespace std;
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
package main
import "fmt"
func search(nums []int, target int) int {
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right - left) / 2
if nums[mid] == target {
return mid
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
由于该题目的区间是闭区间,因此中间的值是可以取到的。在一般的实际情况中,最常见的是左闭右开区间即 [left, right),其中间值则无法取到,因此循环条件会发生改变,即只需要改成 while(left<right) 即可,因为 left==right 无意义。将 left=mid+1 改为 left=mid+1, right=mid-1 即可,代码不再赘述。
二、快慢指针(LeetCode 27)
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改
nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
- 返回
k。
这道题如果使用暴力解法直接遍历每个区间,时间复杂度为 O(n*n),其在 LeetCode 上是可以通过的,但是此题还有一个更为高效的方法,那就是快慢指针。
核心解题思路(快慢指针法)
这是本题的最优解法(时间复杂度 O(n),空间复杂度 O(1)),核心是用两个指针分工:
- 慢指针(slow):记录「非 val 元素」的存放位置,初始为 0;
- 快指针(fast):遍历整个数组,逐个检查元素,初始为 0;
- 遍历过程中,若快指针指向的元素 ≠ val,则将该元素赋值给慢指针位置,慢指针右移;若等于 val,则快指针直接跳过;
- 遍历结束后,慢指针的位置(索引)就是非 val 元素的数量 k。
这种方法无需删除元素,仅通过「覆盖」实现原地修改,完全满足题目要求。
def removeElement(nums: list[int], val: int) -> int:
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
public class RemoveElement {
public static int removeElement(int[] nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
}
#include <iostream>
#include <vector>
using namespace std;
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
package main
import "fmt"
func removeElement(nums []int, val int) int {
slow := 0
for fast := 0; fast < len(nums); fast++ {
if nums[fast] != val {
nums[slow] = nums[fast]
slow++
}
}
return slow
}
三、双指针(LeetCode 997)
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
这道题最容易想到的思路就是先将每个数组中的元素进行平方,然后再进行排序,这种思路是最简单的。下面介绍一个更高效的方法:双指针。
核心解题思路(双指针法)
原数组是非递减的,但包含负数,平方后最大的值一定出现在数组的两端(如 -5 平方是 25,比 3 平方的 9 大)。因此:
- 初始化左指针
left 指向数组开头,右指针 right 指向数组末尾;
- 初始化结果数组
res,长度和原数组一致,用一个指针 k 从末尾向前填充(先放最大的平方值);
- 比较
nums[left] 和 nums[right] 的绝对值:
- 若
|nums[left]| > |nums[right]|,则将 nums[left]² 放入 res[k],左指针右移;
- 否则将
nums[right]² 放入 res[k],右指针左移;
k 向前移动一位,直到左右指针相遇,最终 res 就是非递减的平方数组。
def sortedSquares(nums: list[int]) -> list[int]:
n = len(nums)
res = [0] * n
left, right = 0, n - 1
k = n - 1
while left <= right:
left_sq = nums[left] ** 2
right_sq = nums[right] ** 2
if left_sq > right_sq:
res[k] = left_sq
left += 1
else:
res[k] = right_sq
right -= 1
k -= 1
return res
public class SortedSquares {
public static int[] sortedSquares(int[] nums) {
int n = nums.length;
int[] res = new int[n];
int left = 0, right = n - 1;
int k = n - 1;
while (left <= right) {
int leftSq = nums[left] * nums[left];
int rightSq = nums[right] * nums[right];
if (leftSq > rightSq) {
res[k] = leftSq;
left++;
} else {
res[k] = rightSq;
right--;
}
k--;
}
return res;
}
}
#include <iostream>
#include <vector>
using namespace std;
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> res(n);
int left = 0, right = n - 1;
int k = n - 1;
while (left <= right) {
int leftSq = nums[left] * nums[left];
int rightSq = nums[right] * nums[right];
if (leftSq > rightSq) {
res[k] = leftSq;
left++;
} else {
res[k] = rightSq;
right--;
}
k--;
}
return res;
}
package main
import "fmt"
func sortedSquares(nums []int) []int {
n := len(nums)
res := make([]int, n)
left, right := 0, n-1
k := n - 1
for left <= right {
leftSq := nums[left] * nums[left]
rightSq := nums[right] * nums[right]
if leftSq > rightSq {
res[k] = leftSq
left++
} else {
res[k] = rightSq
right--
}
k--
}
return res
}
四、滑动窗口(LeetCode 209)
核心解题思路(滑动窗口 / 双指针)
滑动窗口的核心是用两个指针维护一个「动态窗口」,通过扩张和收缩窗口找到满足条件的最小长度:
- 右指针(right):扩张窗口,逐个将元素加入窗口,累加窗口内元素和;
- 左指针(left):当窗口和≥target 时,收缩窗口(左指针右移),并更新最小窗口长度;
- 遍历结束后,若找到有效窗口则返回最小长度,否则返回 0。
def minSubArrayLen(target: int, nums: list[int]) -> int:
n = len(nums)
min_len = float('inf')
left = 0
current_sum = 0
for right in range(n):
current_sum += nums[right]
while current_sum >= target:
min_len = min(min_len, right - left + 1)
current_sum -= nums[left]
left += 1
return min_len if min_len != float('inf') else 0
public class MinSubArrayLen {
public static int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
int minLen = Integer.MAX_VALUE;
int left = 0;
int currentSum = 0;
for (int right = 0; right < n; right++) {
currentSum += nums[right];
while (currentSum >= target) {
minLen = Math.min(minLen, right - left + 1);
currentSum -= nums[left];
left++;
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
}
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int minLen = INT_MAX;
int left = 0;
int currentSum = 0;
for (int right = 0; right < n; right++) {
currentSum += nums[right];
while (currentSum >= target) {
minLen = min(minLen, right - left + 1);
currentSum -= nums[left];
left++;
}
}
return minLen == INT_MAX ? 0 : minLen;
}
package main
import (
"fmt"
"math"
)
func minSubArrayLen(target int, nums []int) int {
n := len(nums)
minLen := math.MaxInt32
left := 0
currentSum := 0
for right := 0; right < n; right++ {
currentSum += nums[right]
for currentSum >= target {
if right-left+1 < minLen {
minLen = right - left + 1
}
currentSum -= nums[left]
left++
}
}
if minLen == math.MaxInt32 {
return 0
}
return minLen
}
五、总结
这四种算法是操作数组常用的算法,也是面试和笔试中常考的算法,能应用于大部分操作数组的情景。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online