一、 双指针 (Two Pointers)
1. 算法原理
双指针的核心思想是利用数组的有序性或特定结构,通过两个指针的协同移动(通常是相向移动的头尾指针,或同向移动的快慢指针),将原本可能需要 复杂度的嵌套循环暴力解法,降维到 复杂度。
2. 适用场景
- 处理有序数组或链表中的对撞问题(如求和、比大小)。
- 字符串/数组的对称性判断(如回文串)。
- 原地修改数组(如移除元素)。
3. 经典示例与代码
示例 1:验证回文串 (头尾指针)
利用首尾两端的指针向中间靠拢,跳过非字母数字字符,对比字符是否相等。
Python 实现:
def is_palindrome(s: str) -> bool:
left, right = 0, len(s) - 1
while left < right:
while left < right and not s[left].isalnum(): left += 1
while left < right and not s[right].isalnum(): right -= 1
if s[left].lower() != s[right].lower(): return False
left += 1
right -= 1
return True
Java 实现:
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) return false;
left++; right--;
}
return true;
}
示例 2:两数之和 II - 输入有序数组
根据两数之和与目标值的大小关系,决定是左指针右移(增大和)还是右指针左移(减小和)。
Python 实现:
def two_num_sum(nums: list[int], target: int) -> tuple[int, int]:
l, r = 0, len(nums) - 1
while l < r:
s = nums[l] + nums[r]
if s == target: return (l, r)
elif s > target: r -= 1
else: l += 1
return (-1, -1)
二、 滑动窗口 (Sliding Window)
1. 算法原理
滑动窗口是双指针的“同向升级版”。它使用左右两个指针在数组或字符串上“同向滑动”,框定出一个“窗口”。
- 进窗口:右指针主动右移,扩大窗口并加入新元素。
- 出窗口:当窗口满足(或破坏)特定条件时,左指针右移,缩小窗口并移出老元素。
- 更新结果:在每次窗口变化时记录最优解。
2. 适用场景
- 解决连续子数组或子串的问题(如求最长/最短的满足条件的子数组)。
- 定长子区间的最优解问题。
3. 经典示例与代码
示例 1:长度最小的子数组 (变长窗口 / 弹簧模型)
寻找和大于等于 target 的最短连续子数组。
Python 实现:
def min_sub_array_len(target: int, nums: list[int]) -> int:
left, window_sum, min_len = 0, 0, float('inf')
for right in range(len(nums)):
window_sum += nums[right] # 进窗口
while window_sum >= target:
min_len = min(min_len, right - left + 1)
window_sum -= nums[left] # 出窗口
left += 1
return min_len if min_len != float('inf') else 0
Java 实现:
public int minSubArrayLen(int target, int[] nums) {
int left = 0, sum = 0, minLen = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left++];
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
示例 2:长度为 k 的最大子数组和 (定长窗口 / 尺子模型)
窗口大小固定,整体向右平移。
Python 实现:
def max_sum_fixed_window(nums: list[int], k: int) -> int:
current_sum = sum(nums[:k])
max_sum = current_sum
for i in range(k, len(nums)):
current_sum = current_sum + nums[i] - nums[i - k] # 加上新进来的,减去滑出去的
max_sum = max(max_sum, current_sum)
return max_sum
三、 工程实战扩展:滑动窗口限流器 (Rate Limiting)
1. 算法原理与场景
在服务端开发中,为了防止接口被瞬时高并发压垮,需要进行限流。相较于简单的“固定窗口”可能导致的边界突发流量问题,滑动窗口限流器按时间维度滑动,能更平滑、精确地控制流量。本质上是将空间维度的“数组下标”平移,转化为了时间维度的“时间戳”平移。
2. 代码示例 (Java 实现)
利用队列记录请求的时间戳,每次新请求到来时,剔除过期的时间戳,然后判断当前有效请求数是否超标。
Java 实现:
import java.util.LinkedList;
public class SlidingWindowRateLimiter {
private final long windowSizeMillis;
private final int maxRequestCount;
private final LinkedList<Long> window;
public SlidingWindowRateLimiter(long windowSizeMillis, int maxRequestCount) {
this.windowSizeMillis = windowSizeMillis;
this.maxRequestCount = maxRequestCount;
this.window = new LinkedList<>();
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
long boundaryTime = currentTime - windowSizeMillis;
// 1. 窗口平移:移除过期时间戳
while (!window.isEmpty() && window.peekFirst() <= boundaryTime) {
window.removeFirst();
}
// 2. 判断是否超限
if (window.size() < maxRequestCount) {
window.addLast(currentTime);
return true;
}
return false;
}
}