什么是双指针
双指针算法是一种常用的算法策略,通常用于处理有序数组或链表,能够高效地解决许多问题。其核心思想是通过维护两个指针在数组或链表中移动,从而达到减少时间复杂度的目的。我们将通过三个示例代码来深入了解双指针算法的应用。
示例一:求满足 a[i] + b[j] = x 的(i,j)数对
代码示例一
package 双指针;
import java.io.BufferedInputStream;
import java.util.Scanner;
/**
* 求满足 a[i] + b[j] = x 的(i,j)数对
*/
public class test1 {
private static final int N = 100000;
private static int[] a = new int[N];
private static int[] b = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt(); // a数组长度
int m = sc.nextInt(); // b数组长度
int x = sc.nextInt(); // 目标值
for (int i = 0; i < n; i++) a[i] = sc.nextInt();
for (int i = 0; i < m; i++) b[i] = sc.nextInt();
int left = 0, right = m - 1;
String result = " ";
while (left < n && right >= 0) {
int sum = a[left] + b[right];
if (sum == x) {
result = left + " " +right;
break; // 找到唯一解后退出循环
} else if (sum < x) {
left++;
} else {
right--;
}
}
// 输出结果
System.out.println(result);
}
}
核心逻辑解释
在这个示例中,我们的目标是找到两个数组中的一对下标(i,j),使得它们对应的元素之和等于给定的目标值 x。
int left = 0, right = m - 1;
while (left < n && right >= 0) {
int sum = a[left] + b[right];
if (sum == x) {
result = left + " " + right;
break; // 找到唯一解后退出循环
} else if (sum < x) {
left++;
} else {
right--;
}
}
// 输出结果
System.out.println(result);
- 初始化两个指针
left
和right
,分别指向数组 a 的开始位置和数组 b 的结束位置。- 在每次循环中计算
a[left] + b[right]
的值。
- 如果和等于 x,说明找到了一对符合条件的元素,存储结果并退出循环。
- 如果和小于 x,将
left
右移,以尝试增大和的值。- 如果和大于 x,将
right
左移,以尝试减小和的值。- 该方法的时间复杂度为 O(n + m),而不是 O(n * m),从而提高了效率
示例二:判断子序列
代码示例二
package 双指针;
import java.io.BufferedInputStream;
import java.util.Scanner;
/**
* 判断子序列
*/
public class test2 {
private static final int N = 100010;
private static int[] a = new int[N];
private static int[] b = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 0; i < n; i++) a[i] = sc.nextInt();
for (int i = 0; i < m; i++) b[i] = sc.nextInt();
int i = 0, j = 0;
while (i < n && j < m) {
if (a[i] == b[j]) i++;
j++;
}
if (i == n) System.out.println("Yes");
else System.out.println("No");
}
}
核心逻辑解释
在这个示例中,我们需要判断数组 a 是否为数组 b 的子序列。
int i = 0, j = 0;
while (i < n && j < m) {
if (a[i] == b[j]) i++;
j++;
}
if (i == n) System.out.println("Yes");
else System.out.println("No");
- 使用两个指针
i
和j
分别遍历数组 a 和 b。- 当
a[i]
等于b[j]
时,表示找到了 a 中的一个元素,指针i
向前移动,表示继续寻找 a 的下一个元素。- 指针
j
无论如何都会移动,用于扫描 b 中的所有元素。- 如果最终
i
达到了数组 a 的长度,表示数组 a 的所有元素都已在 b 中找到,此时输出 "Yes";否则输出 "No"。- 这种方法也具有 O(n + m) 的时间复杂度,充分利用了双指针的优势。
示例三:求数组中最长的不重复子序列
代码示例三
package 双指针;
import java.io.BufferedInputStream;
import java.util.Scanner;
import static java.lang.Math.max;
/**
* 求数组中最长的不重复子序列
*/
public class test3 {
private static final int N = 100010;
private static int[] a = new int[N];//原始数组
private static int[] b = new int[N];//记录每个数出现的次数
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
for (int i = 0; i < n; i++) a[i] = sc.nextInt();
int res = 0;
for (int i = 0, j = 0; j < n; j++) {
b[a[j]]++;
while (b[a[j]] > 1) {
b[a[i]]--;
i++;
}
res = max(res, j - i + 1);
}
System.out.println(res);
}
}
核心逻辑解释
for (int i = 0, j = 0; j < n; j++) {
b[a[j]]++;
while (b[a[j]] > 1) {
b[a[i]]--;
i++;
}
res = max(res, j - i + 1);
}
- 通过维护两个指针
i
和j
,j
用于遍历数组,而i
用于调整不重复元素的区间。- 对于每个
j
,首先增加b[a[j]]
的计数,表示该元素出现了一次。- 当某个元素出现次数超过 1 时,通过移动
i
指针并减少b[a[i]]
的计数,直到所有元素都是唯一的。res
用于跟踪当前最长的不重复子序列的长度。- 最终输出
res
。
总结
双指针算法是处理数组和链表问题中非常高效的一种策略。通过上述示例,我们可以看到双指针的灵活运用可以有效解决问题,尤其是在减少时间复杂度方面优势明显。在开发过程中,熟悉双指针的应用场景将对我们解决实际问题有很大帮助。
希望这些示例和逻辑解释能够帮助你更好地理解双指针算法。