双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。

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字符。

注意:

  1. 字符串只包含从 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"

说明:

  1. 所有输入的字符串只包含小写字母。
  2. 字典的大小不会超过 1000。
  3. 所有输入的字符串长度不会超过 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();
    }