双指针算法
一、相向双指针
1、一个典型的相向双指针问题就是翻转字符串的问题。
三步翻转法:
public void reverse(char[] s) { int left = 0, right = s.length - 1; while (left < right) { char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } }
2、 给一个字符串,判断这个字符串是不是回文串。我们可以使用双指针算法轻易解决。
boolean isPalindrome(String s) { for (int i = 0, j = s.length() - 1; i < j; i++, j--) { if (s.charAt(i) != s.charAt(j)) { return false; } } return true; }
Follow Up 1 : 不区分大小写,忽略非英文字母
http://www.lintcode.com/problem/valid-palindrome/
这个问题本身没有太大难度,只是为了给过于简单的 isPalindrome 函数增加一些实现技巧罢了。
代码上和上面的 isPalindrome 函数主要有2个区别:
1. 在 i++ 和 j-- 的时候,要用 while 循环不断的跳过非英文字母。
2. 比较的时候都要变成小写之后再比较。
public class Solution { public boolean isPalindrome(String s) { if (s == null || s.length() == 0) { return true; } int front = 0; int end = s.length() - 1; while (front < end) { while (front < s.length() && !isvalid(s.charAt(front))) { // nead to check range of a/b front++; } if (front == s.length()) { // for empty string “.,,,” return true; } while (end >= 0 && ! isvalid(s.charAt(end))) { // same here, need to check border of a,b end--; } if (Character.toLowerCase(s.charAt(front)) != Character.toLowerCase(s.charAt(end))) { break; } else { front++; end--; } } return end <= front; } private boolean isvalid (char c) { return Character.isLetter(c) || Character.isDigit(c); } }
Follow Up 2 :允许删掉一个字母(类似的,允许插入一个字母)
http://www.lintcode.com/problem/valid-palindrome-ii/
1、 依然用相向双指针的方式从两头出发,两根指针设为 L 和 R。
2、如果 S[L] 和 S[R] 相同的话,L++, R--
3、如果 S[L] 和 S[R] 不相同的话,停下来,此时可以证明,如果能够通过删除一个字符使得整个字符串变成回文串的话,那么一定要么是 S[L], 要么是 S[R]。
class Solution { public boolean validPalindrome(String s) { int left = 0, right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) { break; } left++; right--; } if (left >= right) { return true; } return isSubPalindrome(s, left + 1, right) || isSubPalindrome(s, left, right - 1); } private boolean isSubPalindrome(String s, int left, int right) { while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; } }
3、双指针的鼻祖:两数之和 = target,给一个整数数组,找到两个数使得他们的和等于一个给定的数 target。返回这两个数。
方法一:用哈希表来解决。时间复杂度是 O(n),空间复杂度是 O(n)
方法二:先排序,再使用双指针算法来解决。时间复杂度是 O(nlogn),空间复杂度是 O(1)
二、同向双指针
1、 数组去重问题 Remove duplicates in an array
https://www.lintcode.com/problem/remove-duplicate-numbers-in-array/description
这个问题有两种做法,第一种做法比较容易想到的是,把所有的数扔到 hash 表里,然后就能找到不同的整数有哪些。但是这种做法会耗费额外空间 O(n)。面试官会追问,如何不耗费额外空间。
public class Solution { /** * @param nums: an array of integers * @return: the number of unique integers */ public int deduplication(int[] nums) { // write your code here if(nums == null || nums.length == 0) { return 0; } Set<Integer> set = new HashSet<>(); for(int i = 0; i < nums.length; i++) { set.add(nums[i]); } int result = 0; for(Integer num: set) { nums[result++] = num; } return result; } }
此时我们需要用到双指针算法,首先将数组排序,这样哪些重复的整数就会被挤在一起。然后用两根指针,一根指针走得快一些遍历整个数组,另外一根指针,一直指向当前不重复部分的最后一个数。快指针发现一个和慢指针指向的数不同的数之后,就可以把这个数丢到慢指针后面的一个位置,并把慢指针 ++
public class Solution { /** * @param nums: an array of integers * @return: the number of unique integers */ public int deduplication(int[] nums) { // write your code here if(nums == null || nums.length == 0) { return 0; } int result = 0; Arrays.sort(nums); for(int i = 0; i < nums.length; i++) { if(nums[i] != nums[result]) { nums[++result] = nums[i]; } } return result + 1; } }
2、滑动窗口问题 Window Sum
求出一个数组每 kk 个连续整数的和的数组。如 nums = [1,2,3,4]
, k = 2
的话,window sum 数组为 [3,5,7]
。
http://www.lintcode.com/problem/window-sum/
3、两数之差问题 Two Difference
在一个数组中,求出满足两个数之差等于 target 的那一对数。返回他们的下标。
LintCode 练习地址:
http://www.lintcode.com/problem/two-sum-difference-equals-to-target/
方法一:我自己的方法:(不好,时间复杂度有点高)
public int[] twoSum7(int[] nums, int target) { // write your code here int len = nums.length; //Arrays.sort(nums); for(int left = 0; left < len; left++) { int right = left + 1; while(right < len) { if(Math.abs(nums[right] - nums[left]) == Math.abs(target)) { return new int[]{left + 1, right + 1}; } right++; } } return new int[]{}; }
方法二:使用HashMap的方法,时间复杂度为 O(n), 空间复杂度也是O(n)
public int[] twoSum7(int[] nums, int target) { // write your code here if(nums == null || nums.length < 2) { return new int[]{}; } Map<Integer, Integer> map = new HashMap<>(); for(int i = 0; i < nums.length; i++) { int sum = nums[i] + target; if(map.containsKey(sum)) { int index = map.get(sum); int[] pair = new int[2]; pair[0] = index + 1; pair[1] = i + 1; return pair; } int diff = nums[i] - target; if(map.containsKey(diff)) { int index = map.get(diff); int[] pair = new int[2]; pair[0] = index + 1; pair[1] = i + 1; return pair; } map.put(nums[i], i); } return new int[]{}; }
4、链表中点问题 Middle of Linked LIst
public ListNode middleNode(ListNode head) { // write your code here if(head == null) return null; ListNode dummy = new ListNode(0); dummy.next = head; ListNode slow = dummy, fast = dummy; while(fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; } return slow; }
5、带环链表问题 Linked List Cycle