Leetcode双指针系列(刷题记录)

参考链接:Leetcode 题解 - 双指针 | CS-Notes

167.两数之和

力扣

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

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

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

题目描述:在有序数组中找出两个数,使它们的和为 target。

查看题解后 我重新写的代码如下
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        if(numbers==null) return null;//先讨论特殊情况
        int i=0,j=numbers.length-1;//双指针一头一尾
        int sum=0;
        while(true){
            sum=numbers[i]+numbers[j];
            if(sum<target) i++;//和小于预期,则小的指针往右移动
            if(sum>target) j--;//和大于预期,则大的指针往左移动
            if(sum==target) return new int[]{i+1,j+1};
            if(i==j) return null;
        }

    }
}
题解如下
public int[] twoSum(int[] numbers, int target) {
    if (numbers == null) return null;
    int i = 0, j = numbers.length - 1;
    while (i < j) {//此处循环条件采用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 。

题目描述:判断一个非负整数是否为两个整数的平方和。

class Solution {
    public boolean judgeSquareSum(int c) {
        if (c<0) return false;//首先确定条件
        long a=0,b=(long)Math.sqrt(c);
        long sum=0;
        while(a<=b){
            sum=a*a+b*b;
            if(sum==c) return true;
            else if(sum<c) a++;
            else if(sum>c) b--;
        }
        return false;
    }
}

此题需要注意,使用long类型的数据。用int的话,较大的测试数据会溢出。


345. 反转字符串中的元音字符 

力扣

给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。

元音字母包括 'a''e''i''o''u',且可能以大小写两种形式出现。

使用双指针,一个指针从头向尾遍历,一个指针从尾到头遍历,当两个指针都遍历到元音字符时,交换这两个元音字符。

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

class Solution {
    //首先用一个哈希表把元音字母放进去
    private final static HashSet<Character> vowels = new HashSet<>(Arrays.asList('a','e','i','o','u','A','E','I','O','U'));
    //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) {
        int i=0,j=s.length()-1;
        if(s==null) return null;
        //创建一个新字符串,每次读到字符就放进去。意思是:并不是在这个原始的字符串中进行操作。
        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;
            if(!vowels.contains(cj)) result[j--]=cj;
            else {
                result[i++]=cj;
                result[j--]=ci;
            }
        }//end of while
        return new String(result);//注意不能直接返回char[]
    }
}

本题需要注意的问题:

1. 变量作用域问题:我之前是在while循环体里面创建的字符数组result,然后在循环体外面return了这个数组,就提示我没有这个数组。

2. if和else if的区别:循环体内判断字母是否在HashSet内,需要ci、cj两个都在才能交换。所以要用else if。

3. char[]和String的格式问题:这两个格式并不完全等同,需要用字符数组的内容去创建一个String。如new String (result);


680. 回文字符串

力扣

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

class Solution {
    public static void main(String[] args) {
        String s = "deeee";
        System.out.println(new Solution().validPalindrome(s));
    }
    static int flag=0;//加入一个标志,看是第一次进入validPalindrome方法还是第二次。如果第二次还不是回文序列的话,就return false。

    public boolean validPalindrome(String s) {
        if(s==null) return false;
        int i=0,j=s.length()-1;
        //int flag=0;//加入一个标志,看是第一次进入validPalindrome方法还是第二次。如果第二次还不是回文序列的话,就return false。
        while(i<=j){
            char ci=s.charAt(i);
            char cj=s.charAt(j);
            if(ci==cj){
                i++;
                j--;
            }
            else {
                flag++;
                if(flag>1) return false;
                return validPalindrome(s.substring(i+1,j+1))||validPalindrome(s.substring(i,j));
            }

        }//end of while
        //if(flag==0||flag==1) return true;
        return true;
    }
}

本题需要注意的问题:

1. 递归的思想:仍然使用validPalindrome方法判断去掉一个字母的两种不同子串是否为回文序列。

2. flag:增加一个变量观察调用了几次validPalindrome方法。如果flag大于1(即调用了两次以上),就说明去掉一个字母仍然不为回文序列,就可以直接返回false了。官方题解是重载validPalindrome方法,在多个参数的里面返回false。

3. 字符串函数:s.substring(a,b)的返回规则是有头无尾。即[a,b)。使用时一定要注意。我之前以为是头尾都有,就一直错,然后debug的时候才看出来。

我之前在idea中写的时候,flag前面加了static,本地测试可以通过,但是一上传就错误。上网查阅资料后发现,LeetCode上不要使用static变量或方法,否则容易出错。把static去掉就好了。


88. 归并两个有序数组

力扣

题目描述:把排序后的结果存到第一个数组上。(第一个数组 nums1 的初始长度为 m + n

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        //if(nums1==null||nums2==null) return null;
        int[] result = new int[m+n];
        if(m==0){
            for(int k=0;k<n;k++){
                result[k]=nums2[k];
        }
        for(int k=0;k<m+n;k++){
            nums1[k]=result[k];
        }
            return;
        }

        if(n==0){
            for(int k=0;k<m;k++){
                result[k]=nums1[k];
            }
        for(int k=0;k<m+n;k++){
            nums1[k]=result[k];
        }
            return;
        }
        int i=0,j=0;//i在nums1中,j在nums2中。
        int count=0;//count在数组result中。
        while(count<m+n){
            if(nums1[i]<=nums2[j]) result[count++]=nums1[i++];
            else if(nums1[i]>nums2[j]) result[count++]=nums2[j++];
            if(i==m){
                for(int k=i+j;k<m+n;k++){
                    result[k]=nums2[j++];
                    count++;
                }
            }//end of if(i==m)
            else if(j==n){
                for(int k=i+j;k<m+n;k++){
                    result[k]=nums1[i++];
                    count++;
                }
            }

        }
        for(int k=0;k<m+n;k++){
            nums1[k]=result[k];
        }
    }
}

本题需要注意的问题:

1. 我一开始是从while部分开始写的,因为没有注意到一些边界情况。然后在LeetCode上提交时,看到有数组长度为0的测试用例,我才补上m==0和n==0的情况。

2. 我这样还是有点繁琐的,可以从尾部开始归并,这样就不需要再用一个result数组去存储结果。因为nums1的尾部是空的,可以直接放东西。


 141. 环形链表

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

使用双指针(快慢指针),如果最后两个指针能碰上,就说明存在环。

/**
 * 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,fast=head.next;//慢指针每次移一步,快指针每次移两步
        while(fast!=null&&fast.next!=null){
            if(slow==fast) return true;
            slow=slow.next;
            fast=fast.next.next;
        }
        return false;
    }
}

本题需要注意的问题:

1. 链表节点定义部分:我之前看半天没看懂,因为自动带入了C语言里的结构,后来才发现是Java里的类。

2. while循环中的条件:要防止指针越界,因为fast每次移动两步,所以要保证fast.next也不为空。

3. 非数值类型使用==,不是说内容相等,而是指向同一片内存区域。


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

给你一个字符串 s 和一个字符串数组 dictionary 作为字典,找出并返回字典中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。

如果答案不止一个,返回长度最长且字典序最小的字符串。如果答案不存在,则返回空字符串。

题目分析:逆向思维,应该反过来思考字典里的单词是否为给定字符串的子序列(非传统意义上的子序列,只要在里面找到一种出现方式即可)

public class Solution {
    public String findLongestWord(String s, List<String> dictionary) {
        String result = "";
        for (String temp : dictionary){
            if(result.length()>temp.length()||(result.length()==temp.length()&&result.compareTo(temp)<0)) continue;
            if(isSubstring(s,temp)){
                result = new String(temp);
            }
        }
        return result;
    }

    public boolean isSubstring(String s,String temp){//判断temp是不是s的子串
        int i=0,j=0;//i作为s的下标,j作为temp的下标
        while(i<s.length()&&j<temp.length()){
            if(s.charAt(i)==temp.charAt(j)){
                j++;//字符一样,temp中的指针才向后。否则一直在s中寻找temp中下标为j的字符。
            }
            i++;
        }
        return j == temp.length();//idea帮我化简过了。就是通过看j有没有移动到最后,判断temp是不是s的子串。
    }
}

在idea中测试的主函数附上:

    public static void main(String[] args) {
        String s = "abpcplea";
        List<String> dictionary = Arrays.asList("ale","apple","monkey","plea");
        System.out.println(new Solution().findLongestWord(s,dictionary));
    }

本题需要注意的问题:

1. 本地测试时,主函数中需要定义一个List类型的变量。初始化的时候我们再次用到了Arrays.aslist。之前T345反转元音字母时,通过此方法把元音字母加入了HashSet。

2. 在isSubstring方法中使用双指针,下标 i 在s中后移,匹配上了才移动temp中的下标 j 。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值