刷题--双指针

Posted 2333wzl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刷题--双指针相关的知识,希望对你有一定的参考价值。

相向双指针的第二种题型: Partition array

基本题型:将一个数组根据某个条件分割,比如大于target,奇偶等等

例 lintcode 31. Partition Array  https://www.lintcode.com/problem/partition-array/description

首先外层的循环是left <= right 考虑相等的情况可以保证结束循环的时候两种情况 1 right 和 left左右相邻 2 right 和 left 中间空了一个,左右排列。如果条件是left < right 那么当left == right 时跳出循环,nums[left]还要再判断一次。

partition array 的同向双指针做法和quick sort是非常像的,但是在具体的细节上两者的模板有一定的不同。

    public int partitionArray(int[] nums, int k) {
        if(nums == null || nums.length == 0)return 0;
        
        int left = 0, right = nums.length - 1;
        while(left <= right){
            while(left <= right && nums[left] < k){
                left++;
            }
            
            while(left <= right && nums[right] >= k){
                right--;
            }
            
            if(left <= right){
                int tmp = nums[left];
                nums[left] = nums[right];
                nums[right] = tmp;
                left++;
                right--;
            }
        }
        
        return left;
    }

qucik sort 和partitiona array的模板不一样的地方是左右指针相向而行的时候条件是严格大于和严格小于。这样等于piovt就会均匀的分到左右两部分,同时例如全是1的情况,在这个模板下加上等于的判断指针会超出范围限制。

    public Random rand;

    public static void main(String[] args) {
        QuickSoert q = new QuickSoert();
        int[] arr = {4,3,6,1,7,3,8,1};
        q.solve(arr);
        
        for(int i : arr) {
            System.out.print(i + " ");
        }
    }
    
    private void solve(int[] arr) {
        rand = new Random();
        quickSort(arr, 0, arr.length - 1);
    }

    private void quickSort(int[] arr, int start, int end) {
        if(start >= end) return;
        //System.out.println("start " + start +"   end "+ end);
        int piovtIndex = rand.nextInt(end - start + 1) + start;
        int piovt = arr[piovtIndex];
        int left = start, right = end;
        
        while(left <= right) {
            while(left <= right && arr[left] < piovt) {
                left++;
            }
            
            while(left <= right && arr[right] > piovt){
                right--;
            }
            
            if(left <= right) {
                int tmp = arr[left];
                arr[left] = arr[right];
                arr[right] = tmp;
                left++;
                right--;
            }
        }
        
        quickSort(arr, start, right);
        quickSort(arr, left, end);
        //首先在经过循环之后,left一定是在right的右面;
        /*其次,比如[1,1],如果是按照start,left的条件进行递归,
         * 第一次left会到索引1,就是最后一个,进入递归后start 和end没有发生变化
         * 就会StackOverflow
         */
    }

 quick sort 和 quick select 是紧密相关的。再从两道quick select的题看两者的异同。

例 lintcode 461. Kth Smallest Numbers in Unsorted Array   https://www.lintcode.com/problem/kth-smallest-numbers-in-unsorted-array/description

quick select 的思路就是根据一个piovt将数组划分,然后对含有第k大/小 的其中一半再划分。在进行递归的时候需要注意进入递归的判断和递归的结束。

    public int kthSmallest(int k, int[] nums) {
        if(nums == null || nums.length == 0)return -1;
        //return quickSelect(nums, 0, nums.length - 1, k - 1);
        return helper(k - 1, nums, 0, nums.length - 1);
    }
    
    public int helper(int k, int[] nums, int start, int end){
        if(start == end)return nums[start];
        
        int left = start, right = end;
        int pivot = nums[(left + right) >>> 1];
        //System.out.println("pivot "+ (left + right)/2+ "   value " +pivot);
        
        while(left <= right){
            while(left <= right && nums[left] < pivot){
                left++;
            }
            
            while(left <= right && nums[right] > pivot){
                right--;
            }
            
            if(left <= right){
                int tmp = nums[left];
                nums[left] = nums[right];
                nums[right] = tmp;
                left++;
                right--;
            }
        }
            // System.out.println("first   right " + right+"  left " + left +"   start "+ start +"   end "+end+"   k "+k +"   pivot"+ pivot);
        if(k <= right){
            return helper(k, nums, start, right);
        }else if(k >= left){
            return helper(k, nums, left, end);
        }else
            return nums[k];
    }

两个点注意。1 是在传参给helper的时候,传的是k - 1。因为第一大/第一小对应的index是0,直接将要找的索引传给helper。

2 递归的进入条件是k <= right 或者 k >= left。可以不考虑right >=  start 和 left <= end 因为考虑极端情况[2],左右指针在一个无意义的互换之后都越界了,但是k确实不会符合两者的条件直接输出nums[k]。当然保险起见还是加上比较好。

 

例 lintcode 5. Kth Largest Element   https://www.lintcode.com/problem/kth-largest-element/description

和第k小的代码除了partition的大小判断相反,完全一样。

    public int kthLargestElement(int n, int[] nums) {
        if(nums == null || nums.length == 0)return -1;
        return quickSelect(n -1, nums, 0, nums.length - 1);
    }
    
    private int quickSelect(int n, int[] nums, int start, int end){
        if(start == end)return nums[start];
        
        int left = start, right = end;
        int piovt = nums[(start + end) >>> 1];
        //注意!!!这里piovt必须赋一个nums[(start+end)>>>1],因为在下面的循环中数组的值会发生改变!
        while(left <= right){
            while(left <= right && nums[left] > piovt){
                left++;
            
            }
            while(left <= right && nums[right] < piovt){
                right--;
            }
            
            if(left <= right){
                int temp = nums[right];
                nums[right] = nums[left];
                nums[left] = temp;
                left++;
                right--;
            }
        }
        //System.out.println("left "+left+"   right "+right+"   start"+start+"   end "+end +"   pivot"+piovt);
        if(n >= left && left <= end){
            return quickSelect(n, nums, left, end);
        }else if(n <= right && right >= start){
            return quickSelect(n, nums, start, right);
        }else{
            return nums[n];
        }
    }

 

sort colors

例  148. Sort Colors  https://www.lintcode.com/problem/sort-colors/description

public void sortColors(int[] nums) {
       if(nums == null || nums.length == 0)return;
       int index = helper(nums, 0, 0);
       helper(nums, index, 1);
    }
    
    private int helper(int[] nums, int start, int value){
        int left = start, right = nums.length - 1;
        while(left <= right){
            while(left <= right && nums[left] == value){
                left++;
            }
            while(left <= right && nums[right] > value){
                right--;
            }
            
            if(left <= right){
                int tmp = nums[left];
                nums[left] = nums[right];
                nums[right] = tmp;
                left++;
                right--;
            }
        }
        
        return left;
    }

 

最简单的方法是counting sort,数一下有几个0,1,2就行。counting sort的适用范围是值域有范围,范围不能太大是可以enumerate的。实数不行,值域是无穷的;字符串不行,单纯按字典序是无穷的。counting sort需要按照counting到的最大值开始循环,所以存储无序的hashtable是不行的。

基数排序radix sort 也可以处理

最好的方法是用双指针partition array,对于k个数,每次将k/2个数分开。这道题是三个数,可以先分0和1 2,0在左侧,第二次分1 2,1在左侧。

 

进阶问题 rainbow sort

例  143. Sort Colors II   https://www.lintcode.com/problem/sort-colors-ii/description

    public void sortColors2(int[] colors, int k) {
        if(colors == null || colors.length == 0)return;
        helper(colors, 0, colors.length - 1, 1, k);
        return;
    }
    
    private void helper(int[] colors, int start, int end, int colorStart, int colorEnd){
        if(start == end || colorStart == colorEnd)return;
        
        int mid = (colorStart + colorEnd) >>> 1; 
        int left = start, right = end;
        while(left <= right){
            while(left <= right && colors[left] <= mid){
                left++;
            }
            
            while(left <= right && colors[right] > mid){
                right--;
            }
            
            if(left <= right){
                int tmp = colors[left];
                colors[left] = colors[right];
                colors[right] = tmp;
                left++;
                right--;
            }
        }
        
        helper(colors, start, right, colorStart, mid);
        helper(colors, left, end, mid +1, colorEnd);
    }

 

数组中有k个数来排序。这道题上的注意点是时间复杂度和方法的关系。首先找例子来考虑。k == 1,一种颜色不用排序 O1;k==2,partition一次,On;k==3, partition两次,On;........k==n,就相当于整个排序,时间复杂度Onlogn。所以可以猜出来时间复杂度是Onlogk。那么,使用的方法就是每次将小于等于k/2的放在一边,大于等于k/2 + 1的放在另一边。

 

以上是关于刷题--双指针的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode刷题100天—1221. 分割平衡字符串( 双指针或贪心)—day31

刷题--双指针

力扣刷题之双指针(C++)

LeetCode刷题总结-双指针位运算和分治法篇

LeetCode刷题 -- 双指针篇 -- 三数之和

LeetCode刷题 -- 双指针篇 -- 三数之和