1/21 merge & sort


分为以下几类:

  • two sum类
  • partition 类
  • 灌水类

Merge Sorted Array

这题的设定是一个array is big enough to hold another,所以不需要再创建新的array。从后往前比较A和B,把较大的放在A后面。

要注意如果B中可能会剩下一些更小的数,要在结尾加进去。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        if (nums1 == null || nums2 == null) {
            return;
        }
        int p1 = m - 1, p2 = n - 1;
        int k = m + n - 1;
        while (p1 >= 0 && p2 >= 0) {
            if (nums1[p1] > nums2[p2]) {
                nums1[k--] = nums1[p1--];
            } else {
                nums1[k--] = nums2[p2--];
            }
        }
        while (p2 >= 0) {
            nums1[k--] = nums2[p2--];
        }
    }
}

Merge Two Sorted Arrays

跟上一题的区别是要用一个新的array把merge的结果存起来,不是in-place操作,使用pointer。

public class Solution {
    public int[] mergeSortedArray(int[] A, int[] B) {
        // 
        if (A == null) return B;
        if (B == null) return A;

        int n = A.length, m = B.length;
        int p1 = 0, p2 = 0, i = 0;
        int[] array = new int[n + m];

        while (i < n + m) {
            if (p2 == m) {
                array[i++] = A[p1++];
            } else if (p1 == n) {
                array[i++] = B[p2++];
            } else {
                if (A[p1] > B[p2]) {
                    array[i++] = B[p2++];
                } else {
                    array[i++] = A[p1++];
                }
            }
        }

        return array;
    }
}

用while循环比较两个array,把较小的存在new array。要注意的细节是,使用lazy evaluation的方式在access两个数组数据前先check index有没有超出本身的长度。

Sort Integers II

  • Merge Sort ✔️
  • Quick Sort ✔️
  • 复杂度分析 ✔️

排序问题,要按要求和input选择方法。需要clarify的地方,首先能否do in-place。其次是input是否nearly sorted,还是普通乱序的。

-Merge Sort runtime: O(n log(n)) for average and worst case. space: O(n)

public class Solution {

    public void sortIntegers2(int[] A) {
        // write your code here
        if (A == null || A.length == 0) return;

        mergeSort(A, 0, A.length - 1);
    }

    private void mergeSort(int[] arr, int low, int high) {
        if (low < high) {
            // get the mid position
            int mid = low + (high - low) / 2;
            // sort two subarray
            mergeSort(arr, low, mid);
            mergeSort(arr, mid + 1, high);
            // merge
            merge(arr, low, mid, high);
        }
    } 

    private void merge(int[] arr, int low, int mid, int high) {
        // create two temp arrays
        int[] A = new int[mid - low + 1];
        int[] B = new int[high - mid];

        // copy data to temp arrays
        for (int i = 0; i < A.length; i++) {
            A[i] = arr[low + i];
        }
        for (int i = 0; i < B.length; i++) {
            B[i] = arr[mid + 1 + i];
        }
        // merge the temp arrays
        int p1 = 0, p2 = 0, i = low;
        while (p1 < A.length && p2 < B.length) {
            if (A[p1] < B[p2]) {
                arr[i++] = A[p1++];
            } else {
                arr[i++] = B[p2++];
            }
        }

        while (p1 != A.length) {
            arr[i++] = A[p1++];
        }
        while (p2 != B.length) {
            arr[i++] = B[p2++];
        }
    }
}

-Quick Sort runtime: O(n log(n)) average, O(n^2) worst case space: O(log(n))

public class Solution {
    /*
     * @param A: an integer array
     * @return: 
     */
    public void sortIntegers2(int[] A) {
        // write your code here
        if (A == null || A.length == 0) return;

        quickSort(A, 0, A.length - 1);
    }

    private void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // set mid number as pivot
            int mid = low + (high - low) / 2;
            // move pivot to the end of array
            int pivot = arr[mid];
            swap(arr, mid, high);
            // partition
            int index = partition(arr, low, high, pivot);
            // quickSort two subarray
            quickSort(arr, low, index - 1);
            quickSort(arr, index + 1, high);
        }
    }

    private int partition(int[] arr, int low, int high, int pivot) {
        int left = low;
        int right = high;

        while (left <= right) {
            while (left <= right && arr[left] < pivot) left++;
            while (left <= right && arr[right] >= pivot) right--;

            // swap elements, and move left and right indices
            if (left <= right) {     // attention: not only swap() elements, but also move left and right..
                swap(arr, left, right);
                left++;
                right--;
            }
        }
        swap(arr, high, left);
        return left;
    }

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

Merge sort和Quick sort都采用的递归,时间复杂度都是O(nlgn)。区别是Quick sort是in-place operation,是global -> local的过程,而Merge sort要创建新的array去存data,local -> global,O(n) extra space。

Quick sort 的worst case是每次不能把问题分两半,input scale decreases too slow。那么时间复杂度会接近O(n^2)。

1/8补充,

  • Insertion Sort ✔️

in-place,但是较慢,时间复杂度average and worst caseO(n^2),eg.[5,4,3,2,1]。best case O(n) if nearly sorted

Sort Integers

思路是从数组的第二个开始遍历,每次要先把当前的存入temp,用它和之前的所有data去比较,如果后面比前面的大,就右移,直到找到temp应该插入的新位置。

void insertionSort(int arr[])
{
    int n = arr.length;
    for (int i=1; i<n; ++i)
    {
        int key = arr[i];
        int j = i-1;

        /* Move elements of arr[0..i-1], that are
           greater than key, to one position ahead
           of their current position */
        while (j >= 0 && arr[j] > key)
        {
            arr[j+1] = arr[j];
            j = j-1;
        }
        arr[j+1] = key;
    }
}

Kth Largest Element in an Array

第一眼看这题BF先sort,再返回第k大的element,复杂度是O(nlgn)。但考点并不是写sort,而是基于quick sort的quick selection。注意第二层while循环,大数放左边nums[i] > pivot,小数放右边nums[j] < pivot,都不能有等号。

  • 复杂度分析 ✔️
class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0) return 0;

        return quickSelect(nums, 0, nums.length - 1, k);
    }

    private int quickSelect(int[] nums, int start, int end, int k) {
        // base case
        if (start == end) {
            return nums[start];
        }

        int i = start, j = end;
        int mid = i + (j - i) / 2;
        int pivot = nums[mid];
        while (i <= j) {
            while (i <= j && nums[i] > pivot) i++;
            while (i <= j && nums[j] < pivot) j--;

            if (i <= j) {
                swap(nums, i, j);
                i++;
                j--;
            }
        }

        // start ... j(.)i... end
        // start ... j
        if (start + k - 1 <= j) {
            return quickSelect(nums, start, j, k);
        }
        // i ... end
        if (start + k - 1 >= i) {
            return quickSelect(nums, i, end, k - (i - start));
        }
        // j(.)i
        return nums[j + 1];
    }

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

使用quick sort,但每次只搜索有解的那一半。通过O(n)的时间把规模为n的问题变成n/2,优化后的时间复杂度是O(n)

12/08重做时写得太快,少了一种corner case。有可能在最后一次sort(start = end)时才能找到解。

如果把quick select用在求top k largest 的问题里,且在不要求返回的数组有序的情况下,时间复杂度是O(n),比用heap还快。

related problems: kth smallest number in a matrix; (using queue) <-> search 2D matrix II (start at top-right corner)


Bucket Sort

First missing positives

The key idea is to rearrange the array so that value will be stored in the (value-1)th slot for positive values. Because we only care about the nums[i](nums[i] > 0 && nums[i] <= nums.length) in specific range. Also, the value and index have some mapping. So we can use bucket sort to solve the problem.

e.g. [1,2,3,4, ...]

After you rearrange the array, you scan the array to find the first missing positive number.

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

        // this is kind of bucket sort
        // we only care about the nums[i](nums[i] > 0 && nums[i] <= nums.length) in specific range. 
        // [0, 1, 2, 3]
        // [1, 2, 3, 4]
        // we need to garantee that nums[i] = i + 1;
        // take an example, 
        // [3, 4, -1, 1]
        // => [-1, 4, 3, 1]
        // => [-1, 1, 3, 4]
        // => [1, -1, 3, 4]
        for (int i = 0; i < nums.length; ++i) {
            while (nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] != nums[i]) {
                // swap two numbers
                int temp = nums[nums[i] - 1];
                nums[nums[i] - 1] = nums[i];
                nums[i] = temp;
            }
        }

        for (int i = 0; i < nums.length; ++i) {
            if (nums[i] != i + 1) {
                return i + 1; 
            }
        }
        return nums.length + 1;
    }
}

Note, the time complexity is O(n) and space complexity is O(1).

Reverse Pairs

BF可以用两层for循环,依次检查比较大小,时间复杂度O(n^2)。

优化方法是用归并排序,时间复杂度是O(nlgn),trade off是需额外空间。

具体思路是:和归并排序一样,先需要把数组拆分至最小单元,通过比较相邻两个数字的大小,如果后面比前面的大,逆序对加1。下一层比较的是两个数组,这里和归并不同的是,比较的顺序是从后往前,每次逆序对增加的对数是较小的数的下标,这样就不会漏掉逆序对。比如在比较[1,4] [2,3]两个数组时,4大于3,逆序对的个数累加3之前的所有数(count += 2),因为4肯定比3之前的所有数都大。再把4放入merge array,比较1和3。

class Solution:
    count = 0

    def reversePairs(self, A):
        if not A or len(A) == 1:
            return 0

        self.mergeSort(A)

        return self.count

    def mergeSort(self, A):
        if not A:
            return []

        if len(A) == 1:
            return A[:]

        mid = len(A) // 2
        left = self.mergeSort(A[:mid])
        right = self.mergeSort(A[mid:])

        return self.merge(left, right)

    def merge(self, left, right):
        m, n = len(left), len(right)
        merged_length = m+n
        merged_array = [None] * merged_length
        i, j, k = m-1, n-1, merged_length - 1

        while k >= 0:
            is_left_exhausted = i < 0
            is_right_exhausted = j < 0

            if not is_left_exhausted and (is_right_exhausted or left[i] > right[j]):
                merged_array[k] = left[i]
                i -= 1
                self.count += j + 1

            else:
                merged_array[k] = right[j]
                j -= 1

            k -= 1

        return merged_array

similar problems: Count of smaller numbers after self, Count of range sum

results matching ""

    No results matching ""