双指针算法是解决数组和链表问题的利器,其核心在于通过两个指针的协同移动来降低时间复杂度。在Java中实现双指针时,通常有以下几种典型模式:
以LeetCode 283题为例,我们使用快慢指针实现O(n)时间复杂度的零元素移动:
java复制// 快指针j遍历数组,慢指针i记录非零元素位置
int i = 0;
for(int j = 0; j < nums.length; j++){
if(nums[j] != 0){
nums[i++] = nums[j]; // 非零元素前移
}
}
// 剩余位置补零
while(i < nums.length){
nums[i++] = 0;
}
关键技巧:当遇到需要保持元素原始顺序的问题时,快指针的扫描顺序必须与原始顺序一致,这是保证稳定性的关键。
LeetCode 11题看似简单,实则蕴含着经典的双指针优化思想。其核心公式为:
code复制面积 = 距离 × min(左高度, 右高度)
java复制public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int max = 0;
while(left < right){
int current = (right - left) * Math.min(height[left], height[right]);
max = Math.max(max, current);
// 关键决策:移动较矮的一侧
if(height[left] < height[right]){
left++;
}else{
right--;
}
}
return max;
}
为什么移动较矮的指针是正确的?假设height[left] < height[right]:
因此移动较矮指针保留了找到更大面积的可能性。
LeetCode 15题是双指针的经典应用,需要处理去重和边界条件等复杂情况。
java复制public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int i = 0; i < nums.length - 2; i++){
// 去重处理
if(i > 0 && nums[i] == nums[i-1]) continue;
int left = i + 1, right = nums.length - 1;
while(left < right){
int sum = nums[i] + nums[left] + nums[right];
if(sum == 0){
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 跳过重复元素
while(left < right && nums[left] == nums[left+1]) left++;
while(left < right && nums[right] == nums[right-1]) right--;
left++;
right--;
}else if(sum < 0){
left++;
}else{
right--;
}
}
}
return res;
}
LeetCode 42题有多种解法,其中双指针方案最为高效。理解其物理意义至关重要。
java复制public int trap(int[] height) {
int n = height.length;
if(n == 0) return 0;
// 从左向右扫描的最大高度
int[] leftMax = new int[n];
leftMax[0] = height[0];
for(int i = 1; i < n; i++){
leftMax[i] = Math.max(height[i], leftMax[i-1]);
}
// 从右向左扫描的最大高度
int[] rightMax = new int[n];
rightMax[n-1] = height[n-1];
for(int i = n-2; i >= 0; i--){
rightMax[i] = Math.max(height[i], rightMax[i+1]);
}
// 计算每个位置的积水量
int ans = 0;
for(int i = 0; i < n; i++){
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
空间复杂度可从O(n)优化到O(1):
java复制public int trap(int[] height) {
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
int ans = 0;
while(left < right){
if(height[left] < height[right]){
if(height[left] >= leftMax){
leftMax = height[left];
}else{
ans += leftMax - height[left];
}
left++;
}else{
if(height[right] >= rightMax){
rightMax = height[right];
}else{
ans += rightMax - height[right];
}
right--;
}
}
return ans;
}
物理意义:每个位置的积水量由左右两侧最高柱子的较小值决定,这解释了为什么可以用Math.min(leftMax, rightMax)计算。
在实际面试中,建议先明确说明双指针的移动策略和终止条件,再开始编码。例如解决盛水容器问题时可以这样表述:"我们将维护左右两个指针,每次移动高度较小的指针,因为这样才有可能获得更大的面积。当指针相遇时算法终止。"