双指针
-
Leetcode 题解 - 双指针
-
1. 有序数组的 Two Sum
-
2. 两数平方和
-
3. 反转字符串中的元音字符
-
4. 回文字符串
-
5. 归并两个有序数组
-
6. 判断链表是否存在环
-
7. 最长子序列
- 8. 接雨水
-
1. 有序数组的 Two Sum
167. Two Sum II - Input array is sorted (Easy)
Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2
题目描述:在有序数组中找出两个数,使它们的和为 target。
思路:使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
-
- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
- 如果 sum > target,移动较大的元素,使 sum 变小一些;
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
数组中的元素最多遍历一次,时间复杂度为 O(N)。只使用了两个额外变量,空间复杂度为 O(1)
class Solution { public int[] twoSum(int[] numbers, int target) { int l = 0; int r = numbers.length-1; while(l < r){ int sum = numbers[l]+numbers[r]; if(sum == target) return new int[]{l+1, r+1}; else if(sum < target) l ++; else r --; } return null; } }
2. 两数平方和
633. Sum of Square Numbers (Easy)
Input: 5
Output: True
Explanation: 1 * 1 + 2 * 2 = 5
题目描述:判断一个非负整数是否为两个整数的平方和。
可以看成是在元素为 0~target 的有序数组中查找两个数,使得这两个数的平方和为 target,如果能找到,则返回 true,表示 target 是两个整数的平方和。
思路:双指针
可以看成是在元素为 0~target 的有序数组中查找两个数,使得这两个数的平方和为 target,如果能找到,则返回 true,表示 target 是两个整数的平方和。
剪枝:右指针的初始化,从而降低时间复杂度。
因为最多只需要遍历一次 0~sqrt(target),所以时间复杂度为 O(sqrt(target))。又因为只使用了两个额外的变量,因此空间复杂度为 O(1)。
public boolean judgeSquareSum(int target) { if (target < 0) return false; int i = 0, j = (int) Math.sqrt(target); while (i <= j) { int powSum = i * i + j * j; if (powSum == target) { return true; } else if (powSum > target) { j--; } else { i++; } } return false; }
3. 反转字符串中的元音字符
345. Reverse Vowels of a String (Easy)
Given s = "leetcode", return "leotcede".
思路:利用「双指针」进行前后扫描,当左右指针都是元音字母时,进行互换并移到下一位。
class Solution { 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) { if (s == null) return null; int i = 0, j = s.length() - 1; 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; } else if (!vowels.contains(cj)) { result[j--] = cj; } else { result[i++] = cj; result[j--] = ci; } } return new String(result); } }
4. 回文字符串
680. Valid Palindrome II (Easy)
Input: "abca"
Output: True
Explanation: You could delete the character 'c'.
题目描述:可以删除一个字符,判断是否能构成回文字符串。
思路:所谓的回文字符串,是指具有左右对称特点的字符串,例如 "abcba" 就是一个回文字符串。
判断回文串用双指针的,i从前往后遍历,j从后往前遍历。
难点:怎么去判断删除一个元素后的字符串是不是回文串
解决:如果遇到不等的判断去掉左边或者去掉右边是否为回文。
public boolean validPalindrome(String s) { for (int i = 0, j = s.length() - 1; i < j; i++, j--) { if (s.charAt(i) != s.charAt(j)) { return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j); } } return true; } private boolean isPalindrome(String s, int i, int j) { while (i < j) { if (s.charAt(i++) != s.charAt(j--)) { return false; } } return true; }
5. 归并两个有序数组
88. Merge Sorted Array (Easy)
Input:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
Output: [1,2,2,3,5,6]
题目描述:把归并结果存到第一个数组上。
思路:双指针 ,从后往前:
用的是倒序的方式。指针i,j,k分别指向num1数组m-1位置,num2数组n-1位置和num1数组m+n-1位置,将i,j指向数值大的放在k位置。
需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值。
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int k = m+n; while (n >0){ if (m >0 && nums1[m-1] > nums2[n-1]) { nums1[--k] = nums1[--m]; }else{ nums1[--k] = nums2[--n]; } } } }
6. 判断链表是否存在环
141. Linked List Cycle (Easy)
思路:使用快慢指针,若指针相遇则判断有环。具体一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
public boolean hasCycle(ListNode head) { if (head == null) { return false; } ListNode l1 = head, l2 = head.next; while (l1 != null && l2 != null && l2.next != null) { if (l1 == l2) { return true; } l1 = l1.next; l2 = l2.next.next; } return false; }
7. 归并两个有序数组最长子序列
524. Longest Word in Dictionary through Deleting (Medium)
Input: s = "abpcplea", d = ["ale","apple","monkey","plea"]
Output:"apple"
题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回字典序的最小字符串。
思路:排序 + 双指针 + 贪心
可以先对 dictionary 根据题意进行自定义排序;
长度不同的字符串,按照字符串长度排倒序。长度相同的,则按照字典序排升序;
然后我们只需要对dictionary 进行顺序查找,找到的第一个符合条件的字符串即是答案。
class Solution { public String findLongestWord(String s, List<String> dictionary) { Collections.sort(dictionary, (a,b)->{ if (a.length() != b.length()) return b.length()-a.length(); return a.compareTo(b); }); int n = s.length(); for(String ss: dictionary){ int m = ss.length(); int i = 0, j = 0; while(i < n && j < m){ if(s.charAt(i)== ss.charAt(j)) j++; i++; } if (j == m) return ss; } return ""; } }
8. 接雨水
42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
思路:
(1)使用首尾双指针,每次遍历更新左右两边的最大值
(2)对于某一列,能接到的雨水,取决于 min(左边最大值,右边最大值) - 当前柱子的高度
(3)直到左右指针相遇遍历结束
public int trap(int[] height) {
int sum = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);// 记录左边柱子的最高高度
rightMax = Math.max(rightMax, height[right]);// 记录右边柱子的最高高度
// 取最低的柱子来接雨水
if (leftMax < rightMax) {
sum += leftMax - height[left];
left++;
} else {
sum += rightMax - height[right];
right--;
}
}
return sum;
};

浙公网安备 33010602011771号