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 }
分析:
题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。
双指针专题完结撒花~