LeetCode刷题之双指针

288 阅读4分钟

167. 两数之和 II - 输入有序数组(Easy)

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

  • 返回的下标值(index1 和 index2)不是从零开始的。
  • 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

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

双指针

准备两个指针headtailhead指向数组头部,向尾部遍历;tail指向尾部,向头部遍历,两者之和为sum

  • sum < tragethead右移,让sum变小
  • sum > targettail左移,让sum变大
public int[] twoSum(int[] numbers, int target) {
    int head = 0;
    int tail = numbers.length - 1;
    int[] res = new int[2];

    while (head < tail) {
        int sum = numbers[head] + numbers[tail];
        if (sum == target) {
            res[0] = head + 1;
            res[1] = tail + 1;
            break;
        } else if (sum < target) {
            head++;
        } else {
            tail--;
        }
    }

    return res;
}

633. 平方数之和(Medium)

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

示例 1:

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

示例 2:

输入:c = 3
输出:false

双指针

这道题和上题类似,不同的是和变成了平方和,可以将b初始化为c的平方根来减少不必要的运算,因为b最大的值是c的平方根。

public boolean judgeSquareSum(int c) {
    int a = 0;
    int b = (int) Math.sqrt(c);

    while (a <= b) {
        int powSum = a * a + b * b;
        if (powSum == c) {
            return true;
        } else if (powSum > c) {
            b--;
        } else {
            a++;
        }
    }

    return false;
}

345. 反转字符串中的元音字母(Easy)

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。

Example 1:

Input: "hello"
Output: "holle"

Example 2:

Input: "leetcode"
Output: "leotcede"

提示:

  • 元音字母不包含字母 "y" 。

双指针:

元音包括大小写一共有10(a,e,i,o,u,A,E,I,O,U),可以写个判断是否为元音的方法,遍历字符串,当headtail都为元音时交换,如果head是元音,则tail--,如果tail是元音,head++。

public class Solution {
    public String reverseVowels(String s) {
        int head = 0;
        int tail = s.length() - 1;
        char[] word = s.toCharArray();
        HashSet<Character> chars = new HashSet<Character>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));

        while (head < tail) {
            // head,tail 所在的字母为元音
            if (chars.contains(word[head]) && chars.contains(word[tail])) {
                swap(word, head, tail);
                tail--;
                head++;
            } else if (chars.contains(word[head])){
                // tail 所在字母不为元音
                tail--;
            } else {
                head++;
            }
        }
        return new String(word);
    }

    // 交换字母
    private void swap(char[] word, int head, int tail) {
        char tmp = word[head];
        word[head] = word[tail];
        word[tail] = tmp;
    }
}

680. 验证回文字符串 Ⅱ(Easy)

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

示例 1:

输入: "aba"
输出: True

示例 2:

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

注意:

  • 字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。

双指针

题意是判断字符串是否为回文字结构,并且可以删除一个字符,那么就可以用两个指针start、end从头和尾一起开始判断,遇到相同的就直接跳过,遇到不同时就删除,关键在于删除可以删start或者end对应的字符,就是说有两种情况,那么两种情况都尝试。

class Solution {
    public boolean validPalindrome(String s) {
        // 定义两个指针
        int start = 0;
        int end = s.length() - 1;
        for(start = 0, end = s.length() - 1; start <= end; start++, end--){
            if(s.charAt(start) != s.charAt(end)){
                // 删除左边字符或者右边字符
                return valid(s,start + 1, end) || valid(s,start, end - 1);
                // 这种使用方法是错的,因为调用valid函数之后,start和end的值已经改变,第二遍调用的时候就不是原来的值,(0,2)-->(0,1)-->(1,1)
                // return valid(s, start, --end) || valid(s, ++start, end);
            }
            
        }
        return true;
    }
    
    private boolean valid(String s, int start, int end){
        while(start <= end){
            if(s.charAt(start++) != s.charAt(end--)){
                return false;
            }
        }
        return true;
    }
}

88. 合并两个有序数组(Easy)

给你两个有序整数数组 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]

提示:

  • -10^9 <= nums1[i], nums2[i] <= 10^9
  • nums1.length == m + n
  • nums2.length == n

双指针

把归并结果存到第一个数组上。如果从nums1头部开始归并,会覆盖掉后面未排序的元素,所以采用从后面归并的方式。

class Solution {
        public void merge(int[] nums1, int m, int[] nums2, int n) {
        int index1 = m - 1;
        int index2 = n - 1;
        int mergeIndex = m + n - 1;
        while (index1 >= 0 || index2 >= 0) {
            // 如果 nums1 先归并完,将 nums2 填充到nums1
            if (index1 < 0) {
                nums1[mergeIndex--] = nums2[index2--];
            } else if (index2 < 0) {
            	// 反之 nums1 归并到nums1
                nums1[mergeIndex--] = nums1[index1--];
            } else if (nums1[index1] > nums2[index2]) {
                nums1[mergeIndex--] = nums1[index1--];
            } else {
                nums1[mergeIndex--] = nums2[index2--];
            }
        }
    }
}

141. 环形链表(Easy)

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false

双指针

题意大概是给你一个单链表,然后让你判断这个链表是否有环,首先可以想到的第一种解法是准备一个set,遍历到一个就存储一个,然后检查set里面有无重复的。第二种方法是用快慢指针,快指针一次走一=两步,慢指针一次走一步,如果慢指针走到最后都没相遇,那么就无环,否则有环

/**
 * 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;
        }
        // fast 指针初始化为 head.next,是因为如果两个都为head,那么第一个while循环就会进入 if 判断,从而退出循环
        ListNode fast = head.next;
        ListNode slow = head;
        while(fast != null && slow != null && fast.next != null){
            if(fast == slow){
                return true;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        return false;
    }
}

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

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

示例 1:

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

输出: 
"apple"

示例 2:

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

输出: 
"a"

说明:

  • 所有输入的字符串只包含小写字母。
  • 字典的大小不会超过 1000。
  • 所有输入的字符串长度不会超过 1000。

双指针

首先要认识到,子序列的字符在字符串中是的相对位置是相同的,通过删除字符串 s 中的一个字符能得到字符串t是s的子序列,我们可以使用双指针来判断一个字符串是否为另一个字符串的子序列。

class Solution {
    public String findLongestWord(String s, List<String> d) {
        String result = "";
        // 遍历给定字符串数组
        for(String w : d){
            int len1 = result.length();
            int len2 = w.length();
            // 如果result的长度大于w,或者长度相等,但是result字典序比较小,不更新result
            if(len1 > len2 || (len1 == len2 && result.compareTo(w) < 0)){
                continue;
            }
            if(isSub(s, w)){
                result = w;
            }
        }
        return result;
    }
    
    private boolean isSub(String s, String w){
        int i = 0; // s的指针
        int j = 0; // w的指针
        while (i < s.length() && j < target.length()) {
            if(s.charAt(i) == w.charAt(j)){
                j++;
            }
            i++;
        }
        return j == w.length();
    }