Leetcode题解-双指针
1、有序数组的Two Sum
167. 两数之和 II - 输入有序数组
题目描述:
给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
(1)暴力解法(太耗时,时间复杂度应该是O(n2))
class Solution { public int[] twoSum(int[] numbers, int target) { int len = numbers.length; for (int i = 0; i < len-1; i++){ for (int j = i+1; j<len; j++){ if(numbers[i]+numbers[j] == target){ return new int[]{i + 1, j + 1}; } } } return new int[]{-1, -1}; } }
(2)双指针
分析:
使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
- 如果 sum > target,移动较大的元素,使 sum 变小一些;
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
只遍历一遍数组,时间复杂度应该是O(n)
class Solution { public int[] twoSum(int[] numbers, int target) { int len = numbers.length; int start=0, last=len-1; while (start < last){ int sum = numbers[start] + numbers[last]; if (sum == target){ return new int[]{start+1, last+1}; }else if(sum < target){ start++; }else{ last--; } } return new int[]{-1, -1}; } }
2、两数平方和
633. 平方数之和
题目描述:给定一个非负整数 c
,你要判断是否存在两个整数 a
和 b
,使得 a2 + b2 = c
。
分析:
本题和 167. Two Sum II - Input array is sorted 类似,只有一个明显区别:一个是和为 target,一个是平方和为 target。本题同样可以使用双指针得到两个数,使其平方和为 target。
本题的关键是右指针的初始化,实现剪枝,从而降低时间复杂度。设右指针为 x,左指针固定为 0,为了使 02 + x2 的值尽可能接近 target,我们可以将 x 取为 sqrt(target)。
因为最多只需要遍历一次 0~sqrt(target),所以时间复杂度为 O(sqrt(target))。又因为只使用了两个额外的变量,因此空间复杂度为 O(1)。
class Solution { public boolean judgeSquareSum(int c) { int i = 0, j = (int)Math.sqrt(c); while(i <= j){ int target = i*i + j*j; if(target == c){ System.out.println(i+" "+j); return true; }else if(target < c){ i++; }else{ j--; } } return false; } }
3、反转字符串中的元音字符
345. 反转字符串中的元音字母
题目描述:
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
分析:String不可变,因此需要重新new一个char型数组char[] result = new char[len]; 去盛放反转后的字符,然后把反转后的字符数组转化为String类型去返回return new String(result);。
class Solution { public String reverseVowels(String s) { int len = s.length(); int left = 0, right = len-1; char[] result = new char[len]; while (left <= right){ char c1 = s.charAt(left); char c2 = s.charAt(right); if(!findChar(c1)){ result[left++] = c1; }else if(!findChar(c2)){ result[right--] = c2; }else{ result[left++] = c2; result[right--] = c1; } } // System.out.println(new String(result)); return new String(result); } public boolean findChar(char c){ char[] c1 = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'}; for (int i=0; i<10; i++){ if (c1[i] == c){ return true; } } return false; } }
4、回文字符串
680. 验证回文字符串 Ⅱ
题目描述:
给定一个非空字符串 s
,最多删除一个字符。判断是否能成为回文字符串。
分析:
所谓的回文字符串,是指具有左右对称特点的字符串,例如 "abcba" 就是一个回文字符串。
使用双指针可以很容易判断一个字符串是否是回文字符串:令一个指针从左到右遍历,一个指针从右到左遍历,这两个指针同时移动一个位置,每次都判断两个指针指向的字符是否相同,如果都相同,字符串才是具有左右对称性质的回文字符串。
本题的关键是处理删除一个字符。在使用双指针遍历字符串时,如果出现两个指针指向的字符不相等的情况,我们就试着删除一个字符,再判断删除完之后的字符串是否是回文字符串。
在判断是否为回文字符串时,我们不需要判断整个字符串,因为左指针左边和右指针右边的字符之前已经判断过具有对称性质,所以只需要判断中间的子字符串即可。
在试着删除字符时,我们既可以删除左指针指向的字符,也可以删除右指针指向的字符。
class Solution { public boolean validPalindrome(String s) { int len = s.length(); int left = 0, right = len - 1; while(left<=right){ if (s.charAt(left) != s.charAt(right)){ return isvalid(s, left+1, right) || isvalid(s, left, right-1); } left++; right--; } return true; } public boolean isvalid(String s, int left, int right){ while(left <= right){ if(s.charAt(left) == s.charAt(right)){ left++; right--; }else{ return false; } } return true; } }
5、归并两个有序数组
88. 合并两个有序数组
题目描述:
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
分析:需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值。
// 我首先想到的是构建一个新的int型数组(m+n),然后从头到位归并完成后把新数组的值赋到nums1中,
// 这样空间复杂度应该是O(N) class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int n1 = 0, n2 = 0, n3 = 0; int[] nums = new int[m+n]; while(n1 < m && n2 < n){ if(nums1[n1]<nums2[n2]){ nums[n3++] = nums1[n1++]; // n1++; }else{ nums[n3++] = nums2[n2++]; // n2++; } } if(n1 == m){ while(n2 < n){ nums[n3++] = nums2[n2++]; } }else if(n2 == n){ while(n1 < m){ nums[n3++] = nums1[n1++]; } } for(int i = 0; i < m+n; i++){ nums1[i] = nums[i]; } } }
// 尝试从尾到头直接在nums1上进行归并,这样不需要新建数组,空间复杂度更小 class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int n1 = m-1, n2 = n-1; int n3 = m+n-1; // int[] nums = new int[m+n]; while(n1 >= 0 && n2 >= 0){ if(nums1[n1]<nums2[n2]){ nums1[n3--] = nums2[n2--]; }else{ nums1[n3--] = nums1[n1--]; } } if(n1 >= 0){ while(n3 >= 0){ nums1[n3--] = nums1[n1--]; } }else if(n2 >= 0){ while(n3 >= 0){ nums1[n3--] = nums2[n2--]; } } // for(int i = 0; i < m+n; i++){ // nums1[i] = nums[i]; // } } }
6、判断链表是否有环
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 l1 = head; ListNode l2 = head.next; while(l1 != null && l2 != null && l2.next != null){ if(l1 != l2){ l1 = l1.next; l2 = l2.next.next; }else{ return true; } } return false; } }
7、最长子序列
524. 通过删除字母匹配到字典里最长单词
题目描述:
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
分析:我第一遍做的时候问题就出在这个地方,这个地方的解决方法: 字符串的compareTo()方法,按照字典顺序比较字符串的大小,如果在字典中,str.compareTo(target)的str顺序比target前则返回一个负整数,之后则为正整数,相同则为0
class Solution { public String findLongestWord(String s, List<String> dictionary) { List<String> result = new ArrayList(); result.add(""); int max = 0; for(String list:dictionary){ int l = jianYan(s, list); // int l2 = list.length(); if(result.size() >= 1 && l>0){ if (l>max){ max = l; //System.out.println(list); result.add(list); }else if(l==max && result.get(result.size() - 1).compareTo(list)>0){ //System.out.println(list); result.add(list); } }else if(l>0){ max = l; //System.out.println(list); result.add(list); } } System.out.println(result.toString()); return result.get(result.size() - 1); } public int jianYan(String s1, String s2){ int l1 = s1.length(); int l2 = s2.length(); int i=0, j=0; while(i<l1 && j<l2){ if(s1.charAt(i) == s2.charAt(j)){ i++; j++; }else{ i++; } } if(j == l2){ return j; } return -1; } }