LeetCode题解-双指针

88.合并两个有序数组

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

该题需要特别注意的是,给定的 m 是 nums1 除过 0 元素的其它元素个数,如:数组 [0] ,m 就为 0 ,而不是 1 ;

从两个数组的尾开始遍历,而不是头,这样容易将 nums2 的元素 存储在 nums1 数组中;

在循环时,需要判断 nums1 的元素个数的角标是否越界;

    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m -1;
        int l1 = m+n-1;
        int l2 = n-1;
        while(i >= 0 || l2 >= 0){
            //判断 nums1 的有效元素是否已经越界,如果越界,直接将 nums2 数组剩余的元素直接存储在 nums1 中
            if(i < 0){
                nums1[l1--] = nums2[l2--];
                //判断 nums2 数组是否越界,如果越界,说明 nums1 数组还未遍历结束,则直接将 nums1 中的数组“原封不动”地存储在原地
            }else if(l2 < 0){
                nums1[i--] = nums1[l1--];
            }else if(nums2[l2] > nums1[i]){
               	 //如果 nums2 的元素大于 nums1 的元素,直接将 nums2 元素存储在 nums1 数组的最后面
                nums1[l1--] = nums2[l2--];
            }else if(nums2[l2] < nums1[i]){
                //如果 nums1 元素大于 nums2 元素,则先将 nums1 中元素向后挪一位,等待下次遍历将 nums2 元素存储在nums1 中
                nums1[l1--] = nums1[i--];
            }else{
                //相当于上一步的存储
                nums1[l1--] = nums2[l2--];
            }
        }
    }
}

278.第一个错误版本

假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例:

给定 n = 5,并且 version = 4 是第一个错误的版本。

调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true

所以,4 是第一个错误的版本。 

该题也属于二分查找,但是最重要的一点是,注意边界的情况,如果使用这种: mid = (right+left)/2 ,很可能会引起整数溢出的情况,因此,可以将上述的方法做一个变形: mid = (right-left)/2 + left ,先是做减法然后除以2,最后再加上 left ,这种方法在数学中和第一种其实结果一致;判断条件是 left < right ,只要不满足条件说明已经找到第一个错误的版本了;

public int firstBadVersion(int n) {
    int left = 1, right  = n;
    while (left < right ) {
        int mid = left + (right  - left) / 2;
        if (isBadVersion(mid)) {
            right  = mid;
        } else {
            left = mid + 1;
        }
    }
    return l;
}

345.反转字符串中的元音字符(简单)

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

示例 1:

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

示例 2:

输入: "leetcode"
输出: "leotcede"

该题使用双指针是很容易求解的,因为题目要求是让我们将 s 中的元音字母进行翻转,所以明显使用 left 和 right 指针从前后两端开始遍历寻找;如果 left 找到元音字母后,就不用继续向后寻找(保持当前位置等待交换),而如果 right 也找寻到后,就和等待的 left 进行交换,循环推出条件是 left <= right ;String check = "aAoOeEiIuU" 表示所有的元音字母;

public String reverseVowels(String s) {
        if(s == null || s.length() <= 1){
            return s;
        }
        String check = "aAoOeEiIuU";
        int l = 0;
        int r = s.length()-1;
        char[] res = new char[s.length()];        
        while(l <= r){
            char c1 = s.charAt(l);
            char c2 = s.charAt(r);
            if(!check.contains((c1+""))){
                res[l++] = c1;
            }else if(!check.contains((c2+""))){
                res[r--] = c2;
            }else{
                res[l++] = c2;
                res[r--] = c1;
            }
        }
        return new String(res);
    }

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

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

示例 1:

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

示例 2:

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

输出: 
"a"

该题只要去比较,列表 d 中的每个元素其中的每个字符在字符串 s 中存在的最多,即就可以看作是 s 字符串通过删除给定字符串的某些字符所得到的;

注意:如果答案不唯一时,返回的是“字典顺序最小的”那个字符串,如, a 和 c 都在 abpcplea 字符串中存在,但是 a 的 ACSII 值比 c 小,那么就返回字符串 a ,而不是 c ;

public String findLongestWord(String s, List<String> d) {
    String longestWord = "";
    for (String target : d) {
        int l1 = longestWord.length(), l2 = target.length();
        //判断特殊情况,如果 l1 大于 l2 ,说明之前的查到的字符串 longestWord 比 target 字符串更加合适,因此不需要再次比较
        //如果 l1 等于 l2 ,说明出现了两个之前查到的字符串 longestWord 和 接下来的 target 字符串长度相等的情况,那么就要去判断它们在“字典”中的顺序了
        if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {
            continue;
        }
        //判断目标字符串,即列表 d 中的元素 target ,是否是 s 字符串的子字符串,如果是,则说明该目标字符串 target 目前是我们要找寻的字符串
        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();
}

参考GitHub,CyC2018

540.有序数组的单一元素

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。

示例 1:

输入: [1,1,2,3,3,4,4,8,8]
输出: 2

示例 2:

输入: [3,3,7,7,10,11,11]
输出: 10

该题使用双指针解题是一个很不错的方法,也很简单;但在解题时,需要注意:nums数组的长度和特殊情况:

  • length 为1时;
  • length 为3时且最后一个数为单一元素时;
  • length 为2时;

只要将上述的特殊情况考虑清除,该题就会迎刃而解了。

当判断长度为1时直接返回第一个元素即可;否则,设置双指针从 nums 的起始位置开始遍历,不需要每次 +1 的遍历,由于该数组的每个元素都会出现两次,只有一个单一元素,可以直接一次 +2 的进行遍历,也保证时间复杂度的要求;只要 nums[i] == nums[j] 时,先让 i+=2 去判断是否越界,如果没有越界,则继续判断 j+=2 是否越界,如果越界说明最后一个数为单一元素,返回 nums[i] 即可;如果也没有越界,则 i 和 j 继续向后遍历,直到两者不相等为止。

class Solution {
    public int singleNonDuplicate(int[] nums) {
        if(nums.length == 1){
            return nums[0];
        }
        int i = 0; 
        int j = 1;
        while(j < nums.length){
            if(nums[i] == nums[j]){
                i += 2;
                if(i == nums.length){
                    return 0;
                }else if(j+2 == nums.length){
                    return nums[i];
                }
                j += 2;
            }else{
                return nums[i];
            }
        }
        return 0;
    }
}

你可能感兴趣的:(LeetCode,算法,JavaSE)