Partition

目前我分为二分和三分两类问题。

大部分这一类问题都是要求in-place,O(n)空间没什么意义,显然。

一般是O(n)/O(1)的复杂度


二分

Partition Array II

和前面不太一样的是,这里面的判断条件都变成了i < = j,和quicksort非常相似。因为排序的本质是数组划分。

因为在这里i和j要对撞之后错开才能保证所有的元素被审查过

public class Solution {
    public int partitionArray(int[] nums, int k) {
        if (nums == null || nums.length == 0) return 0;

        int i = 0;
        int j = nums.length - 1;

        while (i <= j) {
            while (i <= j && nums[i] < k) i++;
            while (i <= j && nums[j] >= k) j--;
            if (i <= j) {
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j] = tmp;
            }
        }
        return j + 1;
    }
}

Partition Array by Odd and Even

和上一道一样。

public class Solution {
    public void partitionArray(int[] nums) {
        if (nums == null || nums.length == 0) return;
        int i = 0;
        int j = nums.length - 1;
        while (i <= j) {
            while (i <= j && nums[i] % 2 == 1) i++;
            while (i <= j && nums[j] % 2 == 0) j--;
            if (i <= j) {
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j] = tmp;
            }
        }
    }
}

Sort Letters by Case

二分都是一个意思,就当quicksort的复习。

public class Solution {
    public void sortLetters(char[] chars) {
        if(chars == null || chars.length == 0) return;

        int i = 0int j = chars.length - 1;
        while (i <= j) {
            while (i <= j && Character.isLowerCase(chars[i])) i++;
            while (i <= j && Character.isUpperCase(chars[j])) j--;

            if (i <= j) {
                char tmp = chars[i];
                chars[i] = chars[j];
                chars[j] = tmp;
            }
        }
    }
}

三分

其实三分就是两个二分。

75. Sort Colors / Partition Array II

非常有名的算法,人称“荷兰旗算法” Dutch national flag problem,经典的 "3-way partitioning"算法,用于解决 quick sort 类排序算法中对于重复元素的健壮性问题,在原有 2-way partitioning 的基础上把所有 val == key 的元素集中于数组中间,实现【(小于),(等于),(大于)】的分区。

  • 一张图说明 Dijkstra 的 3-way partitioning,左右指针维护 < key 和 > key 的元素,[left , cur - 1] 为 = key 的元素,[cur, right] 为未知元素。

  • 只有在和 right 换元素时,cur 指针的位置是不动的,因为下一轮还要看一下换过来的元素是不是 < key 要放到左边。

  • O(n) 时间复杂度

public class Solution {
    public void sortColors(int[] nums) {
        if (nums == null || nums.length == 0) return;

        int i = 0;
        int left = 0;
        int right = nums.length - 1;

        while (i <= right) {
            if (nums[i] == 0) {
                swap(nums, left++, i++);
            } else if (nums[i] == 2) {
                swap(nums, i, right--);
            } else i++;
        }
    }
    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

Sort Colors II

进阶版,n个对象,k个颜色进行sort。

下面这个解法是延续自前面的思路,相当于每次排两个颜色,过去是可以AC的,现在TLE了,可以说lintcode的testcase也是越来越阴险了。

复杂度O(nk)

class Solution {
    public void sortColors2(int[] colors, int k) {
        int n = 0;
        int start = 0;
        int end = colors.length - 1;

        while (n < k) {
            int max = Integer.MIN_VALUE;
            int min = Integer.MAX_VALUE;
            for (int j = start; j <= end; ++j) {
                max = Math.max(max, colors[j]);
                min = Math.min(min, colors[j]);
            }
            int i = start;
            int left = start;
            int right = end;
            while (i <= right) {
                if (colors[i] == min) {
                    swap(colors, left++, i++);
                } else if (colors[i] == max) {
                    swap(colors, i, right--);
                } else i++;
            }
            n += 2;
            start = left;
            end = right;
        }
    }
    private void swap(int[] colors, int a, int b){
        int tmp = colors[a];
        colors[a] = colors[b];
        colors[b] = tmp;
    }
}

九章的解法,O(nlogk)的复杂度,把一个多分的问题转成递归二分的调用。

class Solution {
    public void sortColors2(int[] colors, int k) {
        if (colors == null || colors.length == 0) return;
        rainbowSort(colors, 0, colors.length - 1, 1, k);
    }

    public void rainbowSort(int[] colors, int left, int right, 
                            int colorFrom, int colorTo) {
        if (colorFrom == colorTo) return;

        if (left >= right) return;

        int colorMid = (colorFrom + colorTo) / 2;
        int l = left, r = right;
        while (l <= r) {
            while (l <= r && colors[l] <= colorMid) l++;
            while (l <= r && colors[r] > colorMid) r--;
            if (l <= r) {
                int temp = colors[l];
                colors[l] = colors[r];
                colors[r] = temp;

                l++;
                r--;
            }
        }

        rainbowSort(colors, left, r, colorFrom, colorMid);
        rainbowSort(colors, l, right, colorMid + 1, colorTo);
    }
}

还有一个特别好的解法,用的是桶排序的思想。

帖子

class Solution {
    public void sortColors2(int[] colors, int k) {
        if (colors == null) return;

        int len = colors.length;
        for (int i = 0; i < len; i++) {
            // Means need to deal with A[i]
            while (colors[i] > 0) {
                int num = colors[i];
                if (colors[num - 1] > 0) {    
                    // 1. There is a number in the bucket, 
                    // Store the number in the bucket in position i;
                    colors[i] = colors[num - 1];
                    colors[num - 1] = -1;
                } else if (colors[num - 1] <= 0) {
                    // 2. Bucket is using or the bucket is empty.
                    colors[num - 1]--;
                    // delete the A[i];
                    colors[i] = 0;
                }
            }
        }
        int index = len - 1;
        for (int i = k - 1; i >= 0; i--) {
            int cnt = -colors[i];

            // Empty number.
            if (cnt == 0) {
                continue;
            }
            while (cnt > 0) {
                colors[index--] = i + 1;
                cnt--;
            }
        }
    }
}

215. Kth Largest Element in an Array

三分和二分的思路都可以做这个题,主要是递归调用去找区间,partition还是比较明显的。

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        return partition(nums, k, 0, nums.length - 1);
    }
    private int partition(int[] nums, int k, int start, int end) {
        int left = start;
        int right = end;
        int cur = start;
        int pivot = nums[start];

        while (cur <= right) {
            if (nums[cur] > pivot) {
                swap(nums, left++, cur++);        
            } else if (nums[cur] < pivot) {
                swap(nums, cur, right--);
            } else cur++;
        }

        if (k == right + 1) return nums[right];
        if (k > right + 1) {
            return partition(nums, k, right + 1, end);
        } else return partition(nums, k, start, right - 1);
    }
    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

378. Kth Smallest Element in a Sorted Matrix

Nuts & Bolts Problem

  • 所有的不等式判断条件都没有 '=' 号。

  • 所有元素 1-1 对应,不会有多个元素等于 pivot.

  • pivot 元素来自外部,而不是内部已知位置。

如果 partition array 不带等于号的写,数组里有多个元素等于 pivot 就会挂了。 原因在于,在这题中,每次 partition 用的是外部元素,而且 array 里有且只有一个正确 pivot 元素与其对应,所以最终一定会 left == right 停留在 pivot 上。 这题如果像 partition 一样全带 '=' 去判断,其实也是可以正确把数组 partition 的,但是不同于 quick select 里面固定用 num[start] 做 pivot,这种写法里面我们不知道正确的 pivot 位置在哪。

假如按 left < = right,各种 cmp > = 0 < = 0 挪动

【2,1,|7|,8,3,9,|4|,5,6】,pivot = 5 【2,1,4,|8|,|3|,9,7,5,6】 【2,1,4,|3|,|8|,9,7,5,6】

总的来说

  • 全带等号的比较方式,可以 partition,要手动找 pivot 位置做收尾 swap,适用于自己选好 pivot 的情况

  • 全不带等号的比较方式,如果 pivot 元素有唯一对应,可以 partition 并且 left == right 停留在 pivot 上;否则多个等于 pivot 时会死循环,因为不带等号无法让指针跨过 等于 pivot 的元素。

/**
 * public class NBCompare {
 *     public int cmp(String a, String b);
 * }
 * You can use compare.cmp(a, b) to compare nuts "a" and bolts "b",
 * if "a" is bigger than "b", it will return 1, else if they are equal,
 * it will return 0, else if "a" is smaller than "b", it will return -1.
 * When "a" is not a nut or "b" is not a bolt, it will return 2, which is not valid.
*/
public class Solution {

    public void sortNutsAndBolts(String[] nuts, String[] bolts, NBComparator compare) {
        // write your code here
        helper(nuts, bolts, compare, 0, nuts.length - 1);
    }

    private void helper(String[] nuts, String[] bolts, NBComparator compare, 
                        int start, int end){
        if(start >= end) return;
        int pivot = partitionNuts(nuts, bolts[start], start, end, compare);
        partitionBolts(bolts, nuts[pivot], start, end, compare);

        helper(nuts, bolts, compare, start, pivot - 1);
        helper(nuts, bolts, compare, pivot + 1, end);
    }

    private int partitionNuts(String[] nuts, String bolt, int start, int end,
                                 NBComparator compare){
        int left = start;
        int right = end;

        while(left < right){
            while(left < right && compare.cmp(nuts[left], bolt) < 0) left ++;
            while(left < right && compare.cmp(nuts[right], bolt) > 0) right --;
            if(left < right) swap(nuts, left, right);
        }

        // 最终 left = right ,都停留在 pivot 上
        return left;
    }

    private int partitionBolts(String[] bolts, String nut, int start, int end,
                                  NBComparator compare){
        int left = start;
        int right = end;

        while(left < right){
            while(left < right && compare.cmp(nut, bolts[left]) > 0) left ++;
            while(left < right && compare.cmp(nut, bolts[right]) < 0) right --;
            if(left < right) swap(bolts, left, right);
        }

        return right;
    }

    private void swap(String[] arr, int a, int b){
        String temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
};

results matching ""

    No results matching ""