leetcode题解整理——双指针

484 阅读5分钟

1. 167 两数之和——输入有序数组

1. 题目描述:

给定一个已按照升序排列的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target

示例输出:

输入:numbers = [2,7,11,15], target = 9 输出:[1,2] 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

2. 题解:

使用双指针,left和right,分别指向数组的开头和结尾,left指针从左往右移动,right指针从右往左遍历。(因为输入数组是有序数组)那么:

  • 如果left指针和right指针指向的数字之和sum等于target,就得到了结果;
  • 如果sum > target,那么就需要将sum的值减小,也就是右移right指针;
  • 如果sum < target,那么就需要增大sum值,也就是右移left指针。 数组最多遍历1次,时间复杂度为O(n)。只是用了两个额外空间,空间复杂度为O(1)。

3. 代码:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        if(numbers.length == 0) return null;
        int left = 0;
        int right = numbers.length - 1;
        while(left < right){
            int sum = numbers[left] + numbers[right];
            if(sum == target) return new int[]{left + 1, right + 1};
            if(sum > target) right--;
            if(sum < target) left++;
        }
        return null;
    }
}

2. 平方数之和

1. 题目描述:

给定一个非负整数c,你要判断是否存在两个整数a和b,使得 a^2 + b^2 = c

示例输出:

输入:c = 5 输出:true 解释:1 * 1 + 2 * 2 = 5

2. 题解:

这道题可以看成是在0~target之间找到两个数字,使得它们的平方和等于target,如果能找到,返回true,就表示target是两个数字的平方和。

这道题的关键点在于双指针方法中的右指针初始值,我们可以取right = sqrt(target),左指针固定为0。

因为最多只需要遍历一次 0~sqrt(target),所以时间复杂度为 O(sqrt(target))。又因为只使用了两个额外的变量,因此空间复杂度为 O(1)。

3. 代码:

class Solution {
    public boolean judgeSquareSum(int c) {
        if(c < 0) return false;//非负整数
        int left = 0;//左指针初值
        int right = (int) Math.sqrt(c);//右指针初值
        while(left <= right){//注意
            int sum = left * left + right * right;
            if(sum == c) return true;
            if(sum > c) right--;
            if(sum < c) left++;
        }
        return false;
    }
}

3. 反转字符串中的元音字母

1. 题目描述:

以字符串作为输入,反转该字符串中的元音字母。

示例输出:

输入:"hello" 输出:"holle"

2. 题解:

使用双指针的方法,一个指针从左往右遍历字符串,另一个从右往左遍历。当两个指针都遍历到元音字母时,交换二者。

为了快速判断一个字符是不是元音字符,我们将全部元音字符添加到集合 HashSet 中,从而以 O(1) 的时间复杂度进行该操作。

3. 代码:

class Solution {
    //元音字母集合
    private final static HashSet<Character> vowels = new HashSet<>(
        Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));

    public String reverseVowels(String s) {
        if (s == null) return null;
        int i = 0, j = s.length() - 1;
        char[] result = new char[s.length()];
        while (i <= j) {
            char ci = s.charAt(i);
            char cj = s.charAt(j);
            if (!vowels.contains(ci)) {
                result[i++] = ci;
            } else if (!vowels.contains(cj)) {
                result[j--] = cj;
            } else {
                result[i++] = cj;
                result[j--] = ci;
            }
        }
        return new String(result);
    }
}

4. 680 验证回文字符串

1. 题目描述:

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

示例 1: 输入: "aba" 输出: True

示例 2: 输入: "abca" 输出: True 解释: 你可以删除c字符。

2. 题解:

双指针可以很容易判断一个字符串是否是回文字符串:令一个指针从左到右遍历,一个指针从右到左遍历,这两个指针同时移动一个位置,每次都判断两个指针指向的字符是否相同,如果都相同,字符串才是具有左右对称性质的回文字符串。

本题的关键是处理删除一个字符。在使用双指针遍历字符串时,如果出现两个指针指向的字符不相等的情况,我们就试着删除一个字符,再判断删除完之后的字符串是否是回文字符串。

在判断是否为回文字符串时,我们不需要判断整个字符串,因为左指针左边和右指针右边的字符之前已经判断过具有对称性质,所以只需要判断中间的子字符串即可。

在试着删除字符时,我们既可以删除左指针指向的字符,也可以删除右指针指向的字符。

3. 代码:

class Solution {
    public boolean validPalindrome(String s) {
        for (int i = 0, j = s.length() - 1; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);
            }
        }
        return true;
    }

    private boolean isPalindrome(String s, int i, int j) {
        while (i < j) {
            if (s.charAt(i++) != s.charAt(j--)) {
                return false;
            }
       }
        return true;
    }
}

5. 88合并两个有序数组

1. 题目描述:

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

初始化 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]

2. 题解:

从数组的尾部开始遍历,i = m - 1,j = n - 1, k = m + n - 1,将最大的数放到num1的最后面,依次向前遍历,知道得到最终结果。

3. 代码:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m - 1;//遍历num1
        int j = n - 1;//遍历num2
        int k = m + n - 1;//存放结果位置
        while(i >= 0 || j >= 0){
            if(i < 0){
                nums1[k] = nums2[j];
                k--;
                j--;
            }else if(j < 0){
                nums1[k] = nums1[i];
                k--;
                i--;
            }else if(nums1[i] > nums2[j]){
                nums1[k] = nums1[i];
                k--;
                i--;
            }else{
                nums1[k] = nums2[j];
                k--;
                j--;
            }
        }
    }
}

6. 141环形链表

1. 题目描述:

给定一个链表,判断链表中是否有环。

2. 题解:

使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。

3. 代码:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null)
            return false;
        ListNode slow = head;
        ListNode fast = head.next;

        while(slow != null && fast != null && fast.next != null){
            if(slow == fast) {
                return true;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;
    }
}

7. 524. 通过删除字母匹配到字典里最长单词

1. 题目描述:

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。

示例输出:

输入: s = "abpcplea", d = ["ale","apple","monkey","plea"] 输出: "apple"

输入: s = "abpcplea", d = ["a","b","c"] 输出: "a"

2. 题解:

通过删除字符串 s 中的一个字符能得到字符串 t,可以认为 t 是 s 的子序列,我们可以使用双指针来判断一个字符串是否为另一个字符串的子序列。

3. 代码:

class Solution {
    public String findLongestWord(String s, List<String> d) {
    String longestWord = "";
    for (String target : d) {
        int l1 = longestWord.length(), l2 = target.length();
        if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {
            continue;
        }
        if (isSubstr(s, target)) {
            longestWord = target;
        }
    }
    return longestWord;
}
    //双指针判断一个字符串是否为另一个字符串的子序列
    private boolean isSubstr(String s, String target) {
        int i = 0, j = 0;
        while (i < s.length() && j < target.length()) {
            if (s.charAt(i) == target.charAt(j)) {
                j++;
            }
            i++;
        }
        return j == target.length();
    }
}