文章目录
LeetCode精选题之双指针
参考资料:CyC2018的LeetCode题解
1 两数之和 II - 输入有序数组–LeetCode167
给定一个已按照 升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
- 返回的下标值(index1 和 index2)不是从零开始的。
- 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
思路:因为数组是有序的,所以可以用两个指针left
和right
,left
指向开头,从前往后,right
指向结尾,从后往前。计算两个指针指向的元素之和twoSum
,根据twoSum
与target
的大小关系,来移动两个指针,最终确定所求的下标值。
class Solution {
public int[] twoSum(int[] numbers, int target) {
int[] res = new int[2];
int len = numbers.length;
if (numbers == null || len == 0) {
return res;
}
int left = 0, right = len-1;
while (left < right) {
int twoSum = numbers[left]+numbers[right];
if (twoSum == target) {
res[0] = left+1;
res[1] = right+1;
return res;
}else if (twoSum > target) {
right--;
}else {
left++;
}
}
return res;
}
}
2 平方数之和–LeetCode633
给定一个非负整数 c
,你要判断是否存在两个整数 a
和 b
,使得 a2 + b2 = c。
示例1:
输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5
示例2:
输入: 3
输出: False
思路:本题和 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 left = 0;
int right = (int)Math.sqrt(c);
while (left <= right) {
int squSum = left*left + right*right;
if (squSum == c) {
return true;
}else if (squSum > c) {
right--;
}else {
left++;
}
}
return false;
}
}
3 反转字符串中的元音字母–LeetCode
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 1:
输入: "hello"
输出: "holle"
示例 2:
输入: "leetcode"
输出: "leotcede"
说明:元音字母不包含字母"y"。
思路:首先了解元音字母,元音字母有5个,分别是:a、e、i、o、u。
class Solution {
public String reverseVowels(String s) {
HashSet<Character> vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
char[] charArr = s.toCharArray();
int len = charArr.length;
int left = 0, right = len-1;
while (left < right) {
while (left < len && !vowels.contains(charArr[left])) {
left++;
}
while (right >= 0 && !vowels.contains(charArr[right])) {
right--;
}
if (left >= right) {
break;
}
// 交换
char temp = charArr[left];
charArr[left] = charArr[right];
charArr[right] = temp;
// 注意:交换完了之后一定要移动left right
left++;
right--;
}
return new String(charArr);
}
}
4 验证回文字符串 Ⅱ–LeetCode680
给定一个非空字符串 s
,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: "aba"
输出: True
示例 2:
输入: "abca"
输出: True
解释: 你可以删除c字符。
注意:字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
思路:
所谓的回文字符串,是指具有左右对称特点的字符串,例如 “abcba” 就是一个回文字符串。
使用双指针可以很容易判断一个字符串是否是回文字符串:令一个指针从左到右遍历,一个指针从右到左遍历,这两个指针同时移动一个位置,每次都判断两个指针指向的字符是否相同,如果都相同,字符串才是具有左右对称性质的回文字符串。
本题的关键是处理删除一个字符。在使用双指针遍历字符串时,如果出现两个指针指向的字符不相等的情况,我们就试着删除一个字符,再判断删除完之后的字符串是否是回文字符串。
在判断是否为回文字符串时,我们不需要判断整个字符串,因为左指针左边和右指针右边的字符之前已经判断过具有对称性质,所以只需要判断中间的子字符串即可。
在试着删除字符时,我们既可以删除左指针指向的字符,也可以删除右指针指向的字符。
思路摘自:CyC2018
代码如下:
class Solution {
public boolean validPalindrome(String s) {
int left = 0, right = s.length()-1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return isPalindrome(s, left, right-1) || isPalindrome(s, left+1, right);
}
left++;
right--;
}
return true;
}
private boolean isPalindrome(String s, int left, int right) {
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
}
5 合并两个有序数组–LeetCode88
给你两个有序整数数组 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]
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i1 = m-1, i2 = n-1, pos = m+n-1;
while (i1 >= 0 || i2 >= 0) {
if (i1 < 0) {
nums1[pos] = nums2[i2];
i2--;
}else if (i2 < 0) {
nums1[pos] = nums1[i1];
i1--;
}else if (nums1[i1] > nums2[i2]) {
nums1[pos] = nums1[i1];
i1--;
}else {
nums1[pos] = nums2[i2];
i2--;
}
pos--;
}
}
}
官方题解中的写法,值得学习。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// two get pointers for nums1 and nums2
int p1 = m - 1;
int p2 = n - 1;
// set pointer for nums1
int p = m + n - 1;
// while there are still elements to compare
while ((p1 >= 0) && (p2 >= 0))
// compare two elements from nums1 and nums2
// and add the largest one in nums1
nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--];
// add missing elements from nums2
System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
}
}
6 环形链表–LeetCode141
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
代码如下:
/**
* 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) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
}
时间复杂度为 O(n),空间复杂度为 O(1) ,我们只使用了慢指针和快指针两个结点,所以空间复杂度为 O(1)。
7 通过删除字母匹配到字典里最长单词–LeetCode524(Medium)
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
输出:
"apple"
示例 2:
输入:
s = "abpcplea", d = ["a","b","c"]
输出:
"a"
说明:
- 所有输入的字符串只包含小写字母。
- 字典的大小不会超过 1000。
- 所有输入的字符串长度不会超过 1000。
class Solution {
public String findLongestWord(String s, List<String> d) {
String res = "";
for (String word : d) {
if (isMatch(s, word)) {// 如果word能匹配
if (word.length() > res.length()
|| (word.length() == res.length() && word.compareTo(res) < 0)) {
res = word;
}
}
}
return res;
}
private boolean isMatch(String s, String word) {
int i = 0, j = 0;
while (i < s.length() && j < word.length()) {
if (s.charAt(i) == word.charAt(j)) {
j++;
}
i++;
}
if (j == word.length()) {
return true;
}
return false;
}
}
关于字典顺序:
word.compareTo(res)
中的compareTo()
方法,按字典顺序比较两个字符串。
返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。(摘自:RUNOOB)