1. 数组算法精解:从二分查找到螺旋矩阵
作为Java开发者,掌握数组操作是基本功中的基本功。今天我想分享几个在面试和实际开发中频繁出现的数组算法问题,以及它们的Java实现方案。这些算法不仅考察基础编码能力,更能体现解决问题的思维模式。
1.1 二分查找的边界艺术
二分查找看似简单,但边界条件处理不当就会掉坑。核心在于理解循环终止条件和指针移动规则:
java复制class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) { // 包含等于的情况
int mid = left + (right - left) / 2; // 防止溢出
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1; // 明确排除mid位置
} else {
right = mid - 1; // 明确排除mid位置
}
}
return -1;
}
}
关键细节:使用
left <= right而不是left < right可以确保检查到所有元素。mid计算采用left + (right - left)/2的写法可以避免整数溢出问题。
1.2 双指针移除元素的两种范式
移除元素问题展示了双指针的典型应用场景。根据是否要求保持元素顺序,有两种实现方式:
不保持顺序的交换法(时间复杂度O(n)):
java复制class Solution {
public int removeElement(int[] nums, int val) {
int left = 0, right = nums.length;
while (left < right) {
if (nums[left] == val) {
nums[left] = nums[right - 1]; // 用末尾元素覆盖
right--; // 缩小右边界
} else {
left++; // 仅当元素不等于val时移动左指针
}
}
return left;
}
}
保持顺序的移位法(时间复杂度O(n^2)):
java复制class Solution {
public 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]; // 非val元素前移
}
}
return slow;
}
}
1.3 滑动窗口求最小子数组
滑动窗口是处理子数组/子串问题的利器。209题要求找到和≥target的长度最小子数组:
java复制class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left = 0, sum = 0;
int 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;
}
}
窗口滑动技巧:右指针负责扩展窗口,左指针负责收缩窗口。时间复杂度O(n)比暴力解法O(n^2)高效得多。
1.4 螺旋矩阵的层次填充法
生成螺旋矩阵考察的是对二维数组索引的精确控制。采用分层填充策略:
java复制class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int num = 1;
int left = 0, right = n - 1, top = 0, bottom = n - 1;
while (left <= right && top <= bottom) {
// 从左到右填充上边
for (int i = left; i <= right; i++) {
matrix[top][i] = num++;
}
top++;
// 从上到下填充右边
for (int i = top; i <= bottom; i++) {
matrix[i][right] = num++;
}
right--;
// 从右到左填充下边
for (int i = right; i >= left; i--) {
matrix[bottom][i] = num++;
}
bottom--;
// 从下到上填充左边
for (int i = bottom; i >= top; i--) {
matrix[i][left] = num++;
}
left++;
}
return matrix;
}
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 链表操作实战:从基础到进阶
链表问题往往考察指针操作和边界条件处理能力。以下是几个经典问题的深度解析。
2.1 虚拟头节点的妙用
203题移除链表元素展示了虚拟头节点的实用性:
java复制class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(0, head); // 虚拟头节点
ListNode curr = dummy;
while (curr.next != null) {
if (curr.next.val == val) {
curr.next = curr.next.next; // 跳过目标节点
} else {
