Java双指针技术

2025/04/09

1. 概述

在本教程中,我们将讨论使用双指针方法解决涉及数组和列表的问题,此技术是一种简单而有效的提高算法性能的方法

2. 技术描述

在许多涉及数组或列表的问题中,我们必须分析数组中每个元素与其他元素进行比较。

为了解决此类问题,我们通常从第一个索引开始,并根据我们的实现循环遍历数组一次或多次。有时,我们还必须根据问题的要求创建一个临时数组。

上述方法可能会给我们正确的结果,但它可能不会给我们提供最节省空间和时间的解决方案。

因此,考虑一下我们的问题是否可以通过双指针方法有效地解决通常是件好事。

在双指针方法中,指针指向数组的索引,通过使用指针,我们可以在每次循环中处理两个元素,而不是一个

双指针方法的常见模式包括:

  • 两个指针分别从起点和终点出发,直到它们相遇
  • 一个指针移动速度较慢,而另一个指针移动速度较快

上述两种模式都可以帮助我们减少问题的时间和空间复杂度,因为我们可以在更少的迭代中获得预期的结果,并且不需要使用太多额外的空间。

现在,让我们看几个例子,以帮助我们更好地理解这项技术。

3. 数组中存在和

问题:给定一个排序的整数数组,我们需要查看其中是否有两个数字,使得它们的和等于特定值。

例如,如果我们的输入数组是[1,1,2,3,4,6,8,9]并且目标值是11,那么我们的方法应该返回true。但是,如果目标值是20,它应该返回false。

首先我们来看一个简单的解决方案:

public boolean twoSumSlow(int[] input, int targetValue) {
    for (int i = 0; i < input.length; i++) {
        for (int j = 1; j < input.length; j++) {
            if (input[i] + input[j] == targetValue) {
                return true;
            }
        }
    }
    return false;
}

在上述解决方案中,我们循环遍历两次输入数组以获取所有可能的组合。我们将组合总和与目标值进行对比,如果匹配则返回true;此解决方案的时间复杂度为O(n^2)

现在让我们看看如何在这里应用双指针技术:

public boolean twoSum(int[] input, int targetValue) {
    int pointerOne = 0;
    int pointerTwo = input.length - 1;

    while (pointerOne < pointerTwo) {
        int sum = input[pointerOne] + input[pointerTwo];

        if (sum == targetValue) {
            return true;
        } else if (sum < targetValue) {
            pointerOne++;
        } else {
            pointerTwo--;
        }
    }

    return false;
}

由于数组已经排序,我们可以使用两个指针,一个指针从数组的开头开始,另一个指针从数组的末尾开始,然后我们将这两个指针上的值相加。如果值的总和小于目标值,我们就增加左指针,如果值的总和大于目标值,则减小右指针。

我们不断移动这些指针,直到得到与目标值匹配的和,或者到达数组中间,意味着没有找到任何组合。此解决方案的时间复杂度为O(n),空间复杂度为O(1),与我们的第一个实现相比有显著的改进。

4. 旋转数组k步

问题:给定一个数组,将数组向右旋转k步,其中k为非负数。例如,如果我们的输入数组是[1,2,3,4,5,6,7]且k为4,则输出应为[4,5,6,7,1,2,3]。

我们可以通过再次进行两个循环来解决这个问题,这将使时间复杂度变为O(n^2),或者通过使用一个额外的临时数组,但这将使空间复杂度变为O(n)。

让我们使用双指针技术来解决这个问题:

public void rotate(int[] input, int step) {
    step %= input.length;
    reverse(input, 0, input.length - 1);
    reverse(input, 0, step - 1);
    reverse(input, step, input.length - 1);
}

private void reverse(int[] input, int start, int end) {
    while (start < end) {
        int temp = input[start];
        input[start] = input[end];
        input[end] = temp;
        start++;
        end--;
    }
}

在上述方法中,我们对输入数组的各个部分进行多次原地反转,以获得所需的结果。为了反转各个部分,我们使用了双指针方法,即在数组部分的两端交换元素。

具体来说,我们首先反转数组的所有元素。然后,我们反转前k个元素,然后反转其余元素。此解决方案的时间复杂度为O(n),空间复杂度为O(1)

5. LinkedList中的中间元素

问题:给定一个单链表LinkedList,找到其中间元素。例如,如果我们的输入链表是1->2->3->4->5,那么输出应该是3。

我们也可以在类似于数组的其他数据结构(例如LinkedList)中使用双指针技术:

public <T> T findMiddle(MyNode<T> head) {
    MyNode<T> slowPointer = head;
    MyNode<T> fastPointer = head;

    while (fastPointer.next != null && fastPointer.next.next != null) {
        fastPointer = fastPointer.next.next;
        slowPointer = slowPointer.next;
    }
    return slowPointer.data;
}

在这个方法中,我们使用两个指针遍历链表,一个指针加1,另一个指针加2。当快指针到达链表末尾时,慢指针将位于链表中间。此解决方案的时间复杂度为O(n),空间复杂度为O(1)

6. 总结

在本文中,我们通过一些示例讨论了如何应用双指针技术,并研究了它如何提高算法的效率。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章