LeetCode 双指针专题 java代码 思路 题解

前言:正式开始刷题啦

从今儿开始,会进入全新的刷题阶段,争取保持每天都刷题的状态,除此之外,会争取做一个完整的运用spring的项目,同时保证实验室的工作能正常做完。加油。

双指针专题的结题思路是使用两个指针来顺序遍历数组,从而实现目的,或同向,或反向,或块或慢,不同的使用方法会达到不同的效果。同时,对于不同的问题,可以采用递归或者其他方法,总体来说,双指针用起来还是十分之快的。当然,其劣势也明显,在解决一些问题时,双指针需要考虑数组越界的问题,这个比较麻烦,也是要放到第一位考虑的。

关于数组越界问题,可以这样统一来考虑:在进入循环之前,假如就已经越界如何处理?在进入循环之后,假如越界,该如何处理?每次在做题之前把握住这样的两个问题,对于数组越界来说,问题基本就不是太大了。

题解

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

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

解题思路:首先要注意是有序数组,这个很重要,其次是两数之和,那么,我们同时要有两个加数,则对于数组,也就是我们需要两个索引,即指针。这里处理的思路是,最小+最大,与目标数进行比较,若大于目标数,则说明加数要变小,则情况变成最小+第二大,此时和就变小了,若小于目标数,则说明加数要变大,则情况变成第二小+最大。如此进行迭代,就一定能在O(n)内找到一个(第一个)两个加数。

class Solution {
    public int[] twoSum(int[] numbers, int target){
        if(numbers==null) return null;

        int i = 0;
        int j = numbers.length-1;
        while(i<j){
            int sum = numbers[i]+numbers[j];
            if(sum==target){
                return new int[]{i+1,j+1};
            }
            else if(sum<target){
                i++;
            }
            else {
                j--;
            }
        }
        return null;
    }
}

该题主要在于双指针的思想。

633题 两平方数之和

题目详情:给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c。

解题思路:该题与上一题基本一致,本质上没有任何区别,唯二的区别就在于,首先没有数组,只给了个数,那么这里需要自己构建一个数组,其次关于初始索引的问题,就不是简单的0与n-1了,当然,这个也可,但是没必要,可以直接令为c的算术平方根的取整。然后直接套用上一题即可。

class Solution {
    public boolean judgeSquareSum(int c) {
        if(c<0) return false;
        int i = 0;
        int j = (int) Math.sqrt(c);
        while(i<=j){
            int sum = i*i + j*j;
            if(sum==c){
                return true;
            }
            else if(sum<c){
                i++;
            }
            else {
                j--;
            }
        }
        return false;

    }
}

345题 翻转字符串中的元音字母

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

示例:
输入:“hello”
输出:“holle”

输入:“leetcode”
输出:“leotcede”

解题思路:对于该题,题目解释的有点不清晰(最近理解能力有点欠佳),该题的意思实际是从头和尾分别开始往中间遍历,然后将分别找到的元音字母位置对调。所以,这里就又是完美契合第一题的思路,两个指针相向而行(是不是很像物理中两车相向而行的问题)。除此之外,麻烦事唯二,其一是这是字符串,没法按照索引来遍历,我如何一个字母一个字母的来遍历呢,这里我采用的方式是将其转换为char数组,其二,如何判断字母是元音,这里的办法是列出一个元音字典,然后每次在里面查就行。

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[] str = s.toCharArray();
        while (i <= j) {
            if (!vowels.contains(str[i])) {
                i++;
            } else if (!vowels.contains(str[j])) {
                j--;
            } else {
                char swap;
                swap = str[i];
                str[i]=str[j];
                str[j]=swap;
                i++;
                j--;
            }
        }
        return new String(str);
    }
}

680题 验证回文字符串II

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

示例1:
输入: “aba”
输出: True

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

解题思路:对于这个问题,所谓回文字符串,就是首尾一样,如果是奇数,则除了最中间的,其余首尾都是对应相等的,而如果是偶数,则都是对应相等的,当然这个也很契合双指针的思想,首尾遍历,当然会出现一个问题,因为题目指明最多可以删除一个字符,所以可能会遇到不相等的情况,那么此时删去哪一个字符呢,是前面的还是后面的呢?这个无法确定,所以此时需要分别将两边都删除,来试一试,只要其中有一个可以就可。至于如何删除呢,这里我采取的方式是将索引号+1或者-1,就是代表着跳过这个字母,也就是相当于是删除了。
在代码层面,我这里写了两个方法,isPalindrome方法是通过字符数组和初始索引号来判断是否回文,只判断索引范围之内的字符串是否回文,而validPalindrome是用来判断整个字符串是否回文,其中允许一个字母不同。

class Solution {
    public boolean validPalindrome(String s){
        char[] str = s.toCharArray();
        int i =0,j=str.length-1;
        int flag = 0;
        //调用判断回文字符串的方法
        int[] index = isPalindrome(i,j,str);
        if(index[2]==1){
            return true;
        }
        else{
            if(isPalindrome(index[0]+1,index[1],str)[2]==1){
                return true;
            }
            else if(isPalindrome(index[0],index[1]-1,str)[2]==1){
                return true;
            }
        }
        return false;
    }
    //返回一个数组,分别为i,j和true or false
    public int[] isPalindrome(int i,int j,char[] str){
        while(i<j){
            if(str[i]==str[j]){
                i++;
                j--;
            }
            else return new int[]{i,j,0};
        }
        return new int[]{i,j,1};
    }
}

88题 合并两个有序数组

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

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

输出: [1,2,2,3,5,6]

解题思路:对于该题,就如同我在本文开头所讲过的,关于数组越界问题,可以这样统一来考虑:在进入循环之前,假如就已经越界如何处理?在进入循环之后,假如越界,该如何处理?每次在做题之前把握住这样的两个问题,对于数组越界来说,问题基本就不是太大了。
这里执行的思路,因为nums1是m+n大小,而nums2是n个,这里的因为nums1的大小足够装的下nums1和2中的元素,所以可以考虑将两个数组中的最大元素放到nums1的末尾去。
这里的麻烦之处在于,有三个指针,而不仅仅是两个指针,不过其中一个指针只是用于定位存放的位置(出现3个指针的原因在于,这里其实可以理解成三个数组,nums1,nums2两个数据数组,nums1用于存放的数组)。
首先考虑数组数组越界,对于nums1,越界则代表nums1中没有实际的元素,则直接全复制过去就完事了;而对于nums2,越界则代表nums2中没有需要复制到nums1中的,则直接结束就完事了。然后再进入循环,之后就可以在循环中进行越界的判断。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1_all = m+n-1; //nums1的整体指针
        int p1 = m -1;//nums1实际数据的指针
        int p2 = n-1;//nums2的指针
        if(p2<0) { //当nums2中没有数据,也就没有复制的必要了
            return;
        }
        while(p2>=0){
            if(p1<0){//当nums1中没有数据,则不用比较大小,直接将nums2复制过去就行
                nums1[p1_all]=nums2[p2];
                p1_all --;
                p2--;
            }
            //常规比较
            else{
                if(nums2[p2]>=nums1[p1]){
                    nums1[p1_all]=nums2[p2];
                    p1_all --;
                    p2--;
                }
                else{
                    nums1[p1_all] = nums1[p1];
                    nums1[p1]=0;
                    p1--;
                    p1_all--;
                }
            }

        }
    }
}

141 环形链表

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

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例:输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
在这里插入图片描述

这里的示例有点不清不楚的,实际上,示例的输入是给LeetCode后台看的,实际上我们的代码输入只是链表而已,而且是如下结构的链表。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

解题思路:
(1)比较简单的思路就是从头指针开始遍历,记录下走过的点(不能记录值,这里会出现不同点值相同的情况,有点小恶心),然后什么时候发现新到达的点之前已经走过了,则说明有圈,否则一直走下去,肯定会在某个位置停止。该思路比较简单,用个hashset就可以解决,同时,这一题之后的142题寻找环形列表的第一个节点,用这个方法也很简单的就能实现。当然,这个方法的效率没那么高。

public class Solution {
    public boolean hasCycle(ListNode head) {
        HashSet<ListNode> markedSet = new HashSet<ListNode>();
        ListNode p = head;
        while(p!=null){
            markedSet.add(p);
            p = p.next;
            if(markedSet.contains(p)){
                return true;
            }
        }
        return false;
    }
}

(2)用块慢指针,两个指针都从链表头出发,一个指针每次走两个单位,一个走一个单位,则若有圈,则两个指针一定会相遇,设前面的非环部分长z,环形部分长n,则设走了k步两指针相遇,2k-k=n。这里关于快慢指针,不仅可以用于判断成环,还有其他应用,具体给出参考的博客链接:链表中快慢指针的妙用

public class Solution {
    public boolean hasCycle(ListNode head) {
        //运用快慢指针
        ListNode fast = head;
        ListNode low = head;
        //快指针比慢指针多走一个
        while(fast!=null&&low!=null){
            if(fast.next!=null){
                fast = fast.next.next;
            }
            else {
                return false;
            }
            low = low.next;
            if (fast==low){
                return true;
            }
        }
        return false;
    }
}

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

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

示例:
输入:
s = “abpcplea”, d = [“ale”,“apple”,“monkey”,“plea”]

输出:
“apple”

解题思路:该问题就是将字符串与字典中进行对比,挨个字符进行对比,只要能把字典中的某个子字符串全部匹配完即可,例如s与apple进行匹配,a对上了a,往后走,b没对上p,没事,往后走,s的b后面是p,与p对上了,然后c又对不上,再往后走,下一个p对上了,如此进行下去,最终apple都对上了。
从上面的描述就可以获得进行匹配的规则了:即双指针,两个指针分别在两个char数组上遍历,然后进行对比,如果一致,则s和d的指针都往后移动,否则,只移动s。
因为要找到最长的单词,所以需要比较匹配到的单词的长度,这里我采取的方式是用一个单独的数组进行记录匹配结果,数组中,-1为无,其余则为包含在内的长度。数组和字典d进行一一对应。等全部匹配完,在结果数组中去找最大就好。
当然也可以像比较最大值一样,每次将前一次匹配单词长度与本次进行比较,然后保留长的,这样也可,而且更快,只是,如果我想让你找第二长第三长的怎么办,是不是就没办法了?

然后,对于这里,若长度一致,则按照字典序返回第一个单词,所以我这里先对d进行了一次排序。

class Solution {
    public String findLongestWord(String s, List<String> d) {
        //想一下,这是双指针
        //emmmmmm两个指针分别在两个char数组上遍历,然后进行对比
        //如果一致,则s和d的指针都往后移动,否则,只移动s
        //每次对比完,要记录下是否成功且长度如何,所以,用一个数组来存储结果
        //数组中,-1为无,其余则为包含在内的长度
        //这里写的复杂了,其实可以存储一下之前的匹配到的字符串,与前一个比较就行,不过应该存数组的话,可能要更快一些?
        //而且,这里把结果存下来,可以不光找到最大,也可以找到第二个,第三个这种,挺好
        char[] str_original = s.toCharArray();
        if(d.size()==0){
            return "";
        }
        Collections.sort(d);
        int[] result = new int[d.size()];
        int count = 0; //用于计数
        for (String di : d) {
            char[] str_child = di.toCharArray();
            int i = 0, j = 0;
            while (i < str_original.length && j < str_child.length) {
                if (str_original[i] == str_child[j]) {
                    i++;
                    j++;
                } else {
                    i++;
                }
            }
            result[count] = j == str_child.length ? j : -1; //-1时说明没有匹配上
            count++;
        }
        //获取结果数组中最大值的索引
        int maxIndex = getMaxIndex(result);
        if(maxIndex==-1){//如果最大值也是-1,说明都没匹配上
            return "";
        }
        return d.get(maxIndex);

    }

    /**
     * 获取数组中最大值的索引
     * @param arr 输入数组
     * @return 最大值索引,如果都是-1,则返回-1
     */
    public int getMaxIndex(int[] arr){
        int maxIndex = 0;
        int max = arr[0];
        for(int i=0;i<arr.length;i++){
            if(max<arr[i]){
                max = arr[i];
                maxIndex=i;
            }
        }
        if (max==-1){
            return -1;
        }
        return maxIndex;
    }
}

参考资料

https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E7%9B%AE%E5%BD%95.md
我是跟着这个清单上刷题的,他也会介绍解题思路,不过我这里的思路基本都是自己想+看评论和题解的,和他的会有较大出入

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值