双指针算法

一、相向双指针

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

原文地址:https://www.cnblogs.com/jenna/p/10867192.html