LeetCode日记——【算法】双指针专题

  题1:两数之和 II - 输入有序数组(Two Sum II - Input array is sorted)

Leetcode题号:167

难度:Easy

链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/

题目描述:

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

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

说明:

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

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

代码:

 1 class Solution {
 2     public int[] twoSum(int[] numbers, int target) {
 3         if(numbers==null) return null;
 4         int i=0,j=numbers.length-1;
 5         while(i<j){
 6             int sum = numbers[i]+numbers[j];
 7             if(sum==target) {
 8                 return new int[]{i + 1, j + 1};
 9             }else if(sum<target){
10                 i++;
11             }else{
12                 j--;
13              }
14         }
15         return null;
16     }
17 }

分析:

我们使用两个指针,初始分别位于第一个元素和最后一个元素位置,比较这两个元素之和与目标值的大小。如果和等于目标值,我们发现了这个唯一解。如果比目标值小,我们将较小元素指针增加一。如果比目标值大,我们将较大指针减小一。移动指针后重复上述比较知道找到答案。
写代码的时候尽量简略,如new int [ ] {i+1,j+1}。
 

  题2:两数平方和(Sum of Square Numbers)

Leetcode题号:633

难度:Easy

链接:https://leetcode-cn.com/problems/sum-of-square-numbers/description/

题目描述:

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

例1:

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

示例2:

输入: 3
输出: False

代码:

 1 class Solution {
 2     public boolean judgeSquareSum(int c) {
 3         if(c<0) return false;
 4         int i = 0, j = (int) Math.sqrt(c);
 5         while(i<=j){
 6             int sum = i*i+j*j;
 7             if(sum==c) {
 8                 return true;
 9             }else if(sum<c){
10                 i++;  
11             }else{
12                 j--;
13             }  
14         }
15         return false;
16     }
17 }

分析:

与第一道思路相同。

需要注意的地方:j的取值从(int)Math.sqrt(c)开始。while()条件中要取到等号,不然2=1*1+1*1就会被判断为false了。

 

  题3:反转字符串中的元音字符(Reverse Vowels of a String)

Leetcode题号:345

难度:Easy

链接:https://leetcode-cn.com/problems/reverse-vowels-of-a-string/description/

题目描述:

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

示例 1:

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

输入: "leetcode"
输出: "leotcede"
说明:
元音字母不包含字母"y"。

代码:

 1 class Solution {
 2     private final static HashSet <Character> vowels = new HashSet<>(
 3         Arrays.asList('a','e','i','o','u','A','E','I','O','U'));
 4     public String reverseVowels(String s) {
 5         if(s==null) return null;
 6         //定义双指针,i为头索引,j为尾索引
 7         int i=0,j=s.length()-1;
 8         //定义一个数据类型为char类型的数组,容量与字符串s长度相同
 9         char[] result = new char[s.length()];
10         while(i<=j){
11             //把原字符串s中i与j索引位置的字母取出来存放到ci,cj中
12             char ci=s.charAt(i);
13             char cj=s.charAt(j);
14             //如果头指针指向的不是元音字符,则i索引后移一位
15             if (!vowels.contains(ci)) {
16                  result[i++] = ci;
17             //如果尾指针指向的不是元音字符,则j索引前移一位
18             } else if (!vowels.contains(cj)) {
19                  result[j--] = cj;
20             //若i与j索引都指向元音字符,则i索引位置存放cj,j索引位置存放ci,并同时移动ij索引
21             } else {
22                 result[i++] = cj;
23                 result[j--] = ci;
24             }
25         }
26         //最后将result数组转成字符串并返回
27          return new String(result);
28     }
29 }

分析:

总体思路:把所有元音字符存放在一个HashSet中。定义头尾两个指针。定义一个容量为s字符串长度,char类型的数组,用来存放处理后的字符。在循环体中,如果i指向的不是元音字符,则i后移一位;若j指向的不是元音字符,则前移一位。当i,j均指向元音字符,则将对应字符存放到新定义的数组中(注意要交换位置存放),并同时移动两个指针。最后,把新定义的数组转成字符串并返回。

注意点:

1.循环条件一定要取等号。否则,若j,j同时移动到中间的字符,即i=j,就会退出循环,那么新定义的这个数组的对应位置就为null了。

2.复习几种容器的特点:

set系列:无序,不可重复(在这道题中我们选择HashSet)

map系列:存储键值对,键不可重复

list系列,:有序,可重复

3.记住字符串相关的方法:

求字符串的长度 length(),定义一个数据类型为char的数组 char [ ] 数组名 = new char (数组长度),取出字符串中的字符 char 字符名称 = 字符串名称.charAt(索引),字符串是否包含指定元素contanis(变量名)

 

  题4:回文字符串(Valid Palindrome II)

Leetcode题号:680

难度:Easy

链接:https://leetcode-cn.com/problems/valid-palindrome-ii/description/

题目描述:

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

示例 1:

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

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

代码:

 1 class Solution {
 2     public boolean validPalindrome(String s) {
 3         int i=0,j=s.length()-1;
 4         while(i<j){
 5             //遇到不一样的情况,有一次删除的机会
 6             if(s.charAt(i)!=s.charAt(j)){
 7                 //尝试删除左端或右端然后再判断剩下的部分是否为回文串,任何一边成功就算成功
 8                 return isPalindrome(s,i+1,j)||isPalindrome(s,i,j-1);
 9             }
10             //两边字符一样,移动两端指针验证下一组字符
11             i++;
12             j--;
13         }
14         return true;
15     }
16     public boolean isPalindrome(String s,int i,int j) {
17         //该方法用来验证一个字符串是否为回文字符串,无删除机会
18         while(i<j){
19             if(s.charAt(i++)!=s.charAt(j--)) return false;
20         }
21         return true;
22     }
23 }

分析:

先单独写一个isPalindrome()方法来判断当前字符串从指定的两端索引开始判断是否为回文串。

在主方法validPalindrome()中,我们定义双指针来遍历该字符串,当碰到字符不一致时,我们调用isPalindrome()方法分别判断删除左边和删除右边后是否满足回文条件。任意一边满足就可以返回true。当然,无论有没有遇到字符不一致,我们都在每一次循环后移动两边的指针。

从字符串中取出一个字符的方法:字符串名称.charAt(索引)

 

  题5:归并两个有序数组(Merge Sorted Array)

Leetcode题号:88

难度:Easy

链接:https://leetcode-cn.com/problems/merge-sorted-array/description/

题目描述:

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

代码:

 1 class Solution {
 2     public void merge(int[] nums1, int m, int[] nums2, int n) {
 3         int i=m-1, j=n-1, index=m+n-1;
 4         while(i>=0||j>=0){
 5             if(i<0){
 6                 nums1[index--]=nums2[j--];
 7             }else if(j<0){
 8                 nums1[index--]=nums1[i--];
 9             }else if(nums1[i]>nums2[j]){
10                 nums1[index--]=nums1[i--];
11             }else{
12                 nums1[index--]=nums2[j--];
13             }
14         }
15     }
16 }

分析:

我们定义3个指针,从两个数组的尾部(nums1有数的部分的尾部)开始遍历,把较大的数从nums1的尾部开始一次往前放,并设置了一个index指针来指示nums1当前放置的位置,总共分为4种情况:

情况1:nums1当前位置的数比nums当前位置的数大,那么我们把nums1当前位置的这个数放到nums1[index],同时i指针左移,index指针左移。

情况2:nums1当前位置的数比nums当前位置的数小,那么我们把nums2当前位置的这个数放到nums1[index],同时j指针左移,index指针左移。

情况3:nums1所有的数都放完了,即i<0,那么我们把nums2当前位置的这个数放到nums1[index],同时i指针左移,index指针左移。

情况4:nums2所有的数都放完了,即j<0,那么我们把nums1当前位置的这个数放到nums1[index],同时j指针左移,index指针左移。

特别注意:要先写情况3和4,再写情况1和2。为什么?

 

  题6:判断链表是否存在环(Linked List Cycle)

Leetcode题号:141

难度:Easy

链接:https://leetcode-cn.com/problems/linked-list-cycle/description/

题目描述:

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

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

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) {
 7  *         val = x;
 8  *         next = null;
 9  *     }
10  * }
11  */
12 public class Solution {
13     public boolean hasCycle(ListNode head) {
14         if(head==null) return false;
15         //定义一个快指针,每次走2步,一个慢指针,每次走1步
16         ListNode slow=head;
17         ListNode fast=head.next;
18         while(slow!=fast){
19             if(fast==null||fast.next==null) return false;
20             slow=slow.next;
21             fast=fast.next.next;
22         }
23         return true;
24     }
25 }

分析:

定义两个指针,一个一次跑一步,一个一次跑两步,如果存在环,它们就一定会跑到同一个节点上。

 

  题7:最长子序列(Longest Word in Dictionary through Deleting)

Leetcode题号:524

难度:Medium

链接:https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting/description/

题目描述:

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

示例 1:

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

输出:
"apple"
示例 2:

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

输出:
"a"
说明:

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

代码:

 1 class Solution {
 2     public String findLongestWord(String s, List<String> d) {
 3     String longestWord = "";
 4     //target指字典里的每个单词
 5     for (String target : d) {
 6         //l1为longestWord的长度,l2为target的长度
 7         int l1 = longestWord.length(), l2 = target.length();
 8         //下一个单词比上一个符合条件的短,直接跳过(因为就算符合条件也不是最长的)
 9         //下一个单词和上一个符合条件的一样长,但是上一个单词字典顺序较小,直接跳过
10         //(因为就算符合条件字典顺序也不是最小的)
11         if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {
12             continue;
13         }
14         //下一个单词有可能更长,且字母顺序更靠前,则我们判断它是不是s的子串
15         //如果target是给定字符串s的子串,就把target赋给longestWord
16         if (isSubstr(s, target)) {
17             longestWord = target;
18         }
19     }
20     return longestWord;
21 }
22 //该方法用来判断target是否是s的子串
23 private boolean isSubstr(String s, String target) {
24     int i = 0, j = 0;
25     while (i < s.length() && j < target.length()) {
26         if (s.charAt(i) == target.charAt(j)) {
27             j++;
28         }
29         i++;
30     }
31     //target中的字符,s里按顺序都有,就返回true
32     return j == target.length();
33 }
34 }

分析:

首先我们单独写一个方法来判断target字符串是否为s字符串的子串:用双指针来实现。每次循环都会移动s的指针i,若比较到一样的字符,移动target指针j。如果target中的字符,s里按顺序都有,那么指针j就会走到target的末尾,即j=target字符串的长度。这样就代表target是s的子串。

然后我们来写主方法。思路为:定义一个中间字符串target。第一次找到,是s的子串的单词target,就将其赋给longestWord。之后在遍历字典d时,先判断当前target是否比前一个要更长或者字典顺序更靠前,如果不满足条件就直接pass。如果满足条件,再去判断它是否是s的子串。最终longestWord必然是最长,字典顺序最靠前的同时满足是s子串的那个target。

 

双指针专题完结撒花~

 

 

 

 

 

 

 

 

 

 

posted @ 2020-05-18 21:48  菅兮徽音  阅读(170)  评论(0编辑  收藏  举报