双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
1. 有序数组的 Two Sum
Leetcode-167. 两数之和 II - 输入有序数组
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
- 返回的下标值(index1 和 index2)不是从零开始的。
- 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
解法:
- Java
使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
如果两个指针指向元素的和 sum == target,那么得到要求的结果;
- 如果 sum > target,移动较大的元素,使 sum 变小一些;
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
数组中的元素最多遍历一次,时间复杂度为 O(N)。只使用了两个额外变量,空间复杂度为 O(1)。
class Solution {
public int[] twoSum(int[] numbers, int target) {
int l = 0, h = numbers.length-1;
while (l < h) {
int sum = numbers[l] + numbers[h];
if (sum==target) return new int[]{l+1, h+1};
else if (sum<target) l++;
else h--;
}
return null;
}
}
2. 两数平方和
Leetcode-633. 平方数之和
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c。
示例1:
输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5
示例2:
输入: 3
输出: False
解法:
- Java
class Solution {
public boolean judgeSquareSum(int c) {
int l = 0, h = (int)Math.sqrt(c);
while (l<=h) {
int sum = l*l + h*h;
if (sum==c) return true;
else if (sum<c) l++;
else h--;
}
return false;
}
}
3. 反转字符串中的元音字符
Leetcode-345. 反转字符串中的元音字母
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 1:
输入: "hello"
输出: "holle"
示例 2:
输入: "leetcode"
输出: "leotcede"
说明:
元音字母不包含字母"y"。
解法:
- Java
class Solution {
public String reverseVowels(String s) {
Set<Character> set = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
int l = 0, h = s.length()-1;
char[] res = new char[s.length()];
while (l <= h) {
char left = s.charAt(l);
char right = s.charAt(h);
if (set.contains(left) && set.contains(right)) {
res[l++] = right;
res[h--] = left;
}
else if (set.contains(left)) {
res[h--] = right;
}
else if(set.contains(right)) {
res[l++] = left;
}
else {
res[l++] = left;
res[h--] = right;
}
}
return new String(res);
}
}
4. 回文字符串
Leetcode-680. 验证回文字符串 Ⅱ
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: "aba"
输出: True
示例 2:
输入: "abca"
输出: True
解释: 你可以删除c字符。
注意:
- 字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
解法:
- Java
class Solution {
public boolean validPalindrome(String s) {
for (int l=0, h=s.length()-1; l<h; l++, h--) {
if (s.charAt(l) != s.charAt(h)) return isPalindrome(s, l+1, h) || isPalindrome(s, l, h-1);
}
return true;
}
private boolean isPalindrome(String s, int l, int h) {
while (l<h) {
if (s.charAt(l++)!=s.charAt(h--)) return false;
}
return true;
}
}
5. 归并两个有序数组
Leetcode-88. 合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
解法:
- Java
需要倒序
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int r1 = m-1, r2 = n-1, put = m+n-1;
while (r1>=0 || r2>=0) {
if (r1 < 0) nums1[put--] = nums2[r2--];
else if (r2 < 0) break; // 剩下的nums1本来就有序
else if (nums1[r1] > nums2[r2]) nums1[put--] = nums1[r1--];
else nums1[put--] = nums2[r2--];
}
}
}
6. 判断链表是否存在环
Leetcode-141. 环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
解法:
使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
- Java
public class Solution {
public boolean hasCycle(ListNode head) {
if (head==null) return false;
ListNode l1 = head, l2 = l1.next;
while (l1!=null && l2!=null && l2.next!=null) {
if (l1 == l2) return true;
l1 = l1.next;
l2 = l2.next.next;
}
return false;
}
}
7. 最长子序列
Leetcode-524. 通过删除字母匹配到字典里最长单词
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
输出:
"apple"
示例 2:
输入:
s = "abpcplea", d = ["a","b","c"]
输出:
"a"
说明:
- 所有输入的字符串只包含小写字母。
- 字典的大小不会超过 1000。
- 所有输入的字符串长度不会超过 1000。
解法:
- Java
class Solution {
public String findLongestWord(String s, List<String> d) {
int l = 0, maxCount = 0;
String res = "";
for (String str: d) {
int count = 0;
if (isSubstring(s, str)) {
count = str.length();
if (count>maxCount) {
maxCount = count;
res = str;
}
if (count==maxCount) res = smallerString(res, str);
}
}
return res;
}
private boolean isSubstring(String str1, String str2) {
if (str1.length() < str2.length()) return false;
int l1 = 0, l2 = 0;
while (l1<str1.length() && l2<str2.length()) {
if (str1.charAt(l1) == str2.charAt(l2)) {
l2++;
}
l1++;
}
return l2==str2.length();
}
private String smallerString(String str1, String str2) {
if (str1.length()<1) return str1;
if (str1.charAt(0) == str2.charAt(0)) return str1.charAt(0) + smallerString(str1.substring(1), str2.substring(1));
else if (str1.charAt(0) > str2.charAt(0)) return str2;
else return str1;
}
}
或者使用compareTo(更方便)
class Solution {
public String findLongestWord(String s, List<String> d) {
int l = 0, maxCount = 0;
String res = "";
for (String str: d) {
int count = 0;
if (isSubstring(s, str)) {
count = str.length();
if (count>maxCount) {
maxCount = count;
res = str;
}
if (count==maxCount && res.compareTo(str)>0) res = str;
}
}
return res;
}
private boolean isSubstring(String str1, String str2) {
if (str1.length() < str2.length()) return false;
int l1 = 0, l2 = 0;
while (l1<str1.length() && l2<str2.length()) {
if (str1.charAt(l1) == str2.charAt(l2)) {
l2++;
}
l1++;
}
return l2==str2.length();
}