LeetCode——双指针(两数之和\平方和、反转字符串中的元音字母、验证回文字符串、合并两个有序数组、环形链表、字匹配字典最长单词)

目录

一、167. 两数之和 II - 输入有序数组

1.1  题目描述

1.2 思路+代码

1.2.1 暴力枚举法 

1.2.2 双指针法

二、633. 两数平方和

2.1题目描述

 2.2 思路+代码

2.2.1 暴力枚举法

2.2.2 暴力枚举法——改进

2.2.3 双指针法

三、 345. 反转字符串中的元音字母

3.1 题目解析

3.2 思路+代码

3.3 补充

四、680. 验证回文字符串 Ⅱ

4.1 题目描述

4.2 思路+代码 

4.2.1 双指针法

五、88. 合并两个有序数组

5.1 题目描述

5.2 思路+代码 

5.2.1 先合并后排序

5.2.2 双指针、从后往前

六、141. 环形链表

6.1 题目描述

6.2 思路+代码 

6.2.1 哈希表

6.2.2 快慢指针

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

7.1 题目描述

7.2 思路+代码 

7.2.1 直接从字典中查找



双指针

一、167. 两数之和 II - 输入有序数组

1.1  题目描述

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

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

提示:

  • 2 <= numbers.length <= 3 * 104
  • -1000 <= numbers[i] <= 1000
  • numbers 按 递增顺序 排列
  • -1000 <= target <= 1000
  • 仅存在一个有效答案

示例 1:

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

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]

 

1.2 思路+代码

1.2.1 暴力枚举法 

package TwoPointer;

import java.util.Arrays;

/**
 * @author : lkw
 * @data : 2021/3/2
 * @description :167. 两数之和 II - 输入有序数组(easy)
 **/
public class Two_Sum {
    public static void main(String[] args) {
        int[] numbers={2,7,11,15};
        int target= 9;
        System.out.println(Arrays.toString(twoSum(numbers, target)));
//        for (int index:twoSum(numbers, target)) {
//            System.out.println(index);
//        }

    }

    public static int[] twoSum(int[] numbers, int target) {
        int[] answer = new int[2];
        for (int i = 0; i < numbers.length-1 ; i++) {
            for (int j = i+1; j < numbers.length ; j++) {
                if (numbers[i]+numbers[j]==target){
                    answer[0]=i+1;
                    answer[1]=j+1;
                    return answer;
                }
            }
        }
        return answer;
    }
}

运行结果: 

   

时间复杂度:使用了常数个局部变量,因此为O(1)

空间复杂度:需要循环的次数:(n-1)+(n-2)+...+1=n(n-1)/2  ,因此为O(n^2)

 

即i位置固定不动,若i位置的值加j位置的和以及超过target,则j没有必要向右挪了,因为数组是升序的,则j之后位置的值与i处和肯定超过target了。

同理:若j位置固定不动,若j位置的值加i位置的和小于target,则i没有必要向左挪了,因为i之前的位置的值和j处和肯定小于target。

 

1.2.2 双指针法

package TwoPointer;

import java.util.Arrays;

/**
 * @author : lkw
 * @data : 2021/3/2
 * @description :167. 两数之和 II - 输入有序数组(easy)
 *  * 双指针法
 **/
public class TwoSum_02 {
    public static void main(String[] args) {
        int[] numbers = {2, 7, 11, 15};
        int target = 9;
        System.out.println(Arrays.toString(TwoSum_02.twoSum(numbers,target)));

    }

    public static int[] twoSum(int[] numbers, int target) {
        int[] answer = new int[2];
        int left = 0;
        int right = numbers.length - 1;
        while (left < right) {
            if (numbers[left] + numbers[right] > target) {
                right--;
            } else if (numbers[left] + numbers[right] < target) {
                left++;
            } else {
                answer[0] = left + 1;
                answer[1] = right + 1;
                return answer;
                // return new int[]{left + 1, right + 1};  
            }

        }
        return answer;
    }

}

运行结果: 

  

时间复杂度,空间复杂度:

 

二、633. 两数平方和

2.1题目描述

633.两数平方和。给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。

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

示例 2:输入:c = 3
输出:false

示例 3:输入:c = 4
输出:true

示例 4:输入:c = 2
输出:true

示例 5:输入:c = 1
输出:true

 2.2 思路+代码

2.2.1 暴力枚举法

时间复杂度太高,未通过

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/2
 * @description :633. 平方数之和(中等)
 **/
public class judgeSquareSum_01 {
    public static void main(String[] args) {
        int c=0;
        System.out.println(judgeSquareSum(c));
    }
   
    public static boolean judgeSquareSum(int c) {
        for (int i = 0; i <= Math.sqrt(c);i++) {
            for (int j = 0; j <= Math.sqrt(c); j++) {
                if (i*i+j*j==c)
                    return true;
            }
        }
        return false;
    }
}

 

暴力枚举法复杂度分析:

  • 时间复杂度:O(sqrt(c)*sqrt(c))=O(c)

  • 空间复杂度:只使用了两个额外的变量i和j,因此空间复杂度为O(1)

 

2.2.2 暴力枚举法——改进

使用 sqrt 函数后判断剩余部分是否还是一个平方和 

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/2 16:07
 * @description :633.两数平方和
 * 先减去一个平方和,看剩下的是否还是一个平方和
 **/
public class judgeSquareSum_03 {
    public static void main(String[] args) {
        int c = 999999999;
        System.out.println(judgeSquareSum(c));
    }

    public static boolean judgeSquareSum(int c) {
        for (int i = 0; i <= Math.sqrt(c); i++) {
            int m = c-i*i;
            int s = (int)Math.sqrt(c-i*i);
            //如果剩余的部分也是平方和,则返回真
            if (s*s==m){
                return true;
            }

        }
        return false;
    }
}

复杂度分析:

  • 时间复杂度:O(sqrt(c))

  • 空间复杂度:O(1)

 

2.2.3 双指针法

本题和 167. 两数之和的题很类似,区别在于:一个是两数和为 target,一个是两数平方和为 target。因此本题也可以使用双指针来解决。

本题的重点是是右指针的初始化,设右指针为 m,左指针为 0,为了使 0^2 + m^2 的值尽可能接近 target,我们可以将 m 取为 sqrt(target),从而降低时间复杂度。

最多只需要遍历一次 0~sqrt(target),因此时间复杂度为 O(sqrt(target))。

并且只使用了两个额外的变量right和left,因此空间复杂度为 O(1)。

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/2 15:17
 * @description :两数平方和——双指针法
 **/
public class judgeSquareSum_02 {
    public static void main(String[] args) {
        int c=4;
        System.out.println(judgeSquareSum(c));
    }

    public static boolean judgeSquareSum(int c) {
        int left=0;
        int right= (int)Math.sqrt(c);
        while (left<=right){
            if (left*left+right*right>c){
                right--;
            }
            else if (left*left+right*right<c){
                left++;
            }
            else {
               return true;
            }
        }
        return false;
    }
}

双指针法复杂度分析:

  • 时间复杂度:O(sqrt(c))

  • 空间复杂度:O(1)


三、 345. 反转字符串中的元音字母

3.1 题目解析

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

示例 1:输入:"hello"
输出:"holle"

示例 2:输入:"leetcode"
输出:"leotcede"

提示:元音字母不包含字母 "y" 。

3.2 思路+代码

本题自己未写出来,看了讲解

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/4 10:47
 * @description :345. 反转字符串中的元音字母(简单)
 * String.toCharArray();此方法将字符串转换为字符数组。
 **/
public class reverseVowels_02 {
    public static void main(String[] args) {
        String str = "hello";
        System.out.println(reverseVowels(str));

    }

    public static String reverseVowels(String str) {
        char[] chars = str.toCharArray();
        int left = 0;
        int right = chars.length - 1;
        while (left < right) {
            if (isVowel(chars[left]) && isVowel(chars[right])) {
                swap(chars, left, right);
                left++;
                right--;
            } else if (!isVowel(chars[left]) && isVowel(chars[right])) {
                //左侧不是元音,右侧是
                left++;
                    } else if (isVowel(chars[left]) && !isVowel(chars[right])) {
                        right--;
                            } else {
                                left++;
                                right--;
                            }

        }
        return new String(chars);
    }


        private  static boolean isVowel(char ch){
            return ch=='a' ||ch=='e' ||ch=='i' ||ch=='o' ||ch=='u' ||ch=='A' ||ch=='E' ||
                    ch=='I' ||ch=='O' ||ch=='U' ;
        }
        private   static void swap(char[] chars,int a ,int b){
            char tem = chars[a];
            chars[a]= chars[b];
            chars[b] = tem;
            //return chars;
        }
}

运行结果: 

   

  • 时间复杂度:O(n).   最坏情况下,whlie循环次数是字符串长度.
  • 空间复杂度:O(n),将字符串转为了数组格式。

 

3.3 补充

令我疑惑的是swap中不return,原数组chars中的元素竟发生了变化。我将代码return后,运行正确,但提交时报错,信息如下: 

原因为何?

在程序中,调用方法并且把数组的名称作为参数传递到方法中。本质上是传递数组的地址值。既然传递的是数组的地址,那么方法就可以通过数组的地址改变内存中数组的内容。

 传递int类型,不return就不修改原来的值,举例:

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/5 10:35
 * @description :传递int型,不return就不改变值
 **/
public class pra {
    public static void main(String[] args) {
        int a = 3;
        System.out.println(a);//3
        test(a);
        System.out.println(a);//3
        System.out.println(test1(a));//10
    }
    private static void test(int a){
        a = 10;

    }

    private static int test1(int a){
        a = 10;
        return a;

    }
}

运行结果: 

 

四、680. 验证回文字符串 Ⅱ

4.1 题目描述

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

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

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

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

“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。

 

4.2 思路+代码 

4.2.1 双指针法

以"abbbac"这个串为例,此时i指向'a',j指向'c',发现不对了。但是有一次删除的机会,若此时子串范围为[i+1, j]或[ i, j-1]的俩子串只要有任意一个是回文串,则结果就是回文串,否则就不是。

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/9 16:38
 * @description :680. 验证回文字符串 Ⅱ (easy)
 **/


public class validPalindrome_03 {
    public static void main(String[] args) {
        String s = "abbbac";
        validPalindrome(s);

    }

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

    public static boolean isValid(String s, int i, int j){
        while(i < j){
        //如果不相等,直接return false,结束isValid()
        //若相等,则i++,j--,判断后面元素,继续while循环判断
            if(s.charAt(i) != s.charAt(j)){
                return false;
            }
            i++;
            j--;
        }
        return true;
    }
}

 两个方法类似: 

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/6 15:03
 * @description :680. 验证回文字符串 Ⅱ (easy)
 * 
 **/
 class validPalindrome_02 {

    public static void main(String[] args) {
        String s = "abbbac";
        System.out.println(validPalindrome(s));

    }

    private static 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+1,j)||isPalindrome(s,i,j-1) ;
            }
        } return true;
    }

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

 

 

五、88. 合并两个有序数组

5.1 题目描述

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

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。

示例 1:输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]

示例 2:输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]

提示:

nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[i] <= 109

 

5.2 思路+代码 

5.2.1 先合并后排序


    public static void arraycopy(Object src,
                                 int srcPos,
                                 Object dest,
                                 int destPos,
                                 int length)
//    参数
//    src - 源数组。
//    srcPos - 源数组中的起始位置。
//    dest - 目标数组。
//    destPos - 目的地数据中的起始位置。
//    length - 要复制的数组元素的数量。

 

package TwoPointer;

import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * @author : lkw
 * @data : 2021/3/10 11:53
 * @description :先放后排序
 **/

public class merge_02 {
    public static void merge(int[] nums1, int m, int[] nums2, int n) {
        System.arraycopy(nums2,0,nums1,m,n);
        Arrays.sort(nums1);
    }
}

时间复杂度:O((n+m)log(n+m))

空间复杂度:O(1)

 

这种方法没有利用两个数组本身已经有序这一点。

 

5.2.2 双指针、从后往前

package TwoPointer;

/**
 * @author : lkw
 * @data : 2021/3/10 12:36
 * @description :88. 合并两个有序数组(easy)
 * 思路:倒放——>从nums1的后面插入
 * 从两个数字后面比较,大的值放入nums1的最后方
 **/
public class merge_01 {
    public static void merge(int[] nums1, int m, int[] nums2, int n) {
        int p=(m--)+(n--)-1;
        while (m>=0 && n>=0){
            nums1[p--]= nums1[m]>nums2[n] ? nums1[m--]: nums2[n--];
        }

        while (n>=0){
            nums1[p--] = nums2[n--];
        }
    }
}

时间复杂度: O(m+n)

空间复杂度:O(1)

 

 

六、141. 环形链表

6.1 题目描述

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

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

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

进阶:你能用 O(1)(即常量)内存解决此问题吗?

       

 

6.2 思路+代码 

6.2.1 哈希表

使用哈希表来存储已经访问过的节点。如果下一个待访问的节点,已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表。

package TwoPointer;

import java.util.HashSet;

/**
 * @author : lkw
 * @data : 2021/3/10 13:26
 * @description :141. 环形链表(easy)
 * 方法一:哈希表
 **/
class ListNode {
      int val;
      ListNode next;
      ListNode(int x) {
          val = x;
          next = null;
   }
}
public class hasCycle_01 {
    public boolean hasCycle(ListNode head) {
        HashSet<ListNode> set = new HashSet<>();
        //若此元素存在于哈希表中,则返回false
        while(head!=null) {
            if(!set.add(head))
                return true;
            head=head.next;
        }
        return false;
    }

}

时间复杂度:O(n),最坏情况下我们需要遍历每个节点一次。

空间复杂度: O(n),为哈希表的开销,最坏情况下我们需要将每个节点插入到哈希表中。

 

6.2.2 快慢指针

本方法需要读者对「Floyd 判圈算法」(又称龟兔赛跑算法)有所了解。

假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。

我们可以根据上述思路来解决本题。具体地,我们定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。

package TwoPointer;


/**
 * @author : lkw
 * @data : 2021/3/10 14:01
 * @description :141. 环形链表(easy)
 * 方法二:快慢指针
 **/
public class hasCycle_02 {
    public boolean hasCycle(ListNode head) {
        if (head==null || head.next==null ){
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while(slow!=fast){
            if (fast==null || fast.next==null){
                return false;
            }
            slow=slow.next;
            fast=fast.next.next;
        }
        return true;
    }

}

时间复杂度:O(N),其中 N 是链表中的节点数。

  • 当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。
  • 当链表中存在环时,每一轮移动后,快慢指针的距离将减小1。而初始距离为环的长度,因此至多移动 N 轮。

空间复杂度:O(1),我们只使用了两个指针的额外空间。

 

为什么慢指针每次只移动一步,而快指针每次移动两步而不是其他步?

如果有一个循环圈为3,而快指针移动为4,则快每次在循环里前进1,慢指针也前进1,永远不会相遇。

代码试验了一下,换为跳三步,时间超了。

 

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

7.1 题目描述

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

示例 1:

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

输出: "apple"

示例 2:

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

输出: "a"

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

查了一下,字典排序是一种对于随机变量形成序列的排序方法,其方法是按照字母排列顺序,或数字顺序由小到大形成的的序列。

因此此处的字典顺序最小的字符串,代表的含义是ASCII码最小

srt.compareTo(antherstr), 即先比较对应字符的大小(ASCII码顺序),遇到第一个不等的字符,结束比较,返回他们之间的ASCII差值 。

如果此字符串小于字符串参数,则返回一个小于 0 的值;

 

7.2 思路+代码 

7.2.1 直接从字典中查找

思路:利用两个指针i,j,i指向字符串的首字母,j指向字典中第一个元素的首字母。每次比较完之后i++,若两字母相等,则j++。若移到字典第一个元素的最后一个字母,则比较现找到的字典元素是否是最优的(长度最长|| ASCII最小),若不是,则修改str。

package TwoPointer;

import java.util.List;

/**
 * @author : lkw
 * @data : 2021/3/10 19:36
 * @description :524. 通过删除字母匹配到字典里最长单词
 **/
public class findLongestWord_01 {
    public String findLongestWord(String s, List<String> dictionary) {
        String str = "";
        for (String dstring : dictionary) {
            for (int i = 0, j = 0; i < s.length() && j < dstring.length(); i++) {
                if (s.charAt(i) == dstring.charAt(j)) j++;
                if (j == dstring.length()) {
                    //compareTo先比较对应字符的大小(ASCII码顺序),遇到第一个不等的字符,结束比较,返回他们之间的长度差值
                    //如果此字符串小于字符串参数,则返回一个小于 0 的值;
                    //(str.length()<dstring.length() && str.compareTo(dstring)>0)的含义是若长度相等返回字典顺序最小的字符串
                    if (str.length() < dstring.length() || (str.length() == dstring.length() && str.compareTo(dstring) > 0))
                        str = dstring;
                }
            }
        }
        return str;
    }
}

时间复杂度:O(n*x),这里 n是列表 dictionary 中字符串的数目,x是字符串平均长度。

空间复杂度:O(str),运行时字典中最佳字符串值。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值