Back
Featured image of post LeetCode 排序数组

LeetCode 排序数组

LeetCode—排序数组

题目

给你一个整数数组 nums,请你将该数组升序排列。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

1 <= nums.length <= 5 * 104
-5 * 104 <= nums[i] <= 5 * 104

冒泡排序 (超出时间限制)

此题一开始想到的就是冒泡排序,暴力两层循环,可以运行,但时间复杂度太大, 超出时间限制

public int[] sortArray(int[] nums) {
    for(int i = 0; i < nums.length; i++) {
        for(int j = i+1; j < nums.length; j++) {
            if(nums[i] > nums[j]) {
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
    }
    return nums;
}

快速排序 (最佳:8ms)

// 快速排序 1:基本快速排序

/**
 * 列表大小等于或小于该大小,将优先于 quickSort 使用插入排序
 */
private static final int INSERTION_SORT_THRESHOLD = 7;

private static final Random RANDOM = new Random();
public int[] sortArray(int[] nums) {
    int len = nums.length;
    quickSort(nums, 0, len - 1);
    return nums;
}

private void quickSort(int[] nums, int left, int right) {
    // 小区间使用插入排序
    if (right - left <= INSERTION_SORT_THRESHOLD) {
        insertionSort(nums, left, right);
        return;
    }

    int pIndex = partition(nums, left, right);
    quickSort(nums, left, pIndex - 1);
    quickSort(nums, pIndex + 1, right);
}

/**
 * 对数组 nums 的子区间 [left, right] 使用插入排序
 *
 * @param nums  给定数组
 * @param left  左边界,能取到
 * @param right 右边界,能取到
 */
private void insertionSort(int[] nums, int left, int right) {
    for (int i = left + 1; i <= right; i++) {
        int temp = nums[i];
        int j = i;
        while (j > left && nums[j - 1] > temp) {
            nums[j] = nums[j - 1];
            j--;
        }
        nums[j] = temp;
    }
}

private int partition(int[] nums, int left, int right) {
    int randomIndex = RANDOM.nextInt(right - left + 1) + left;
    swap(nums, left, randomIndex);

    // 基准值
    int pivot = nums[left];
    int lt = left;
    // 循环不变量:
    // all in [left + 1, lt] < pivot
    // all in [lt + 1, i) >= pivot
    for (int i = left + 1; i <= right; i++) {
        if (nums[i] < pivot) {
            lt++;
            swap(nums, i, lt);
        }
    }
    swap(nums, left, lt);
    return lt;
}

private void swap(int[] nums, int index1, int index2) {
    int temp = nums[index1];
    nums[index1] = nums[index2];
    nums[index2] = temp;
}

归并排序 (此题最佳算法:6ms)

/**
 * 列表大小等于或小于该大小,将优先于 mergeSort 使用插入排序
 */
private static final int INSERTION_SORT_THRESHOLD = 7;

public int[] sortArray(int[] nums) {
    int len = nums.length;
    int[] temp = new int[len];
    mergeSort(nums, 0, len - 1, temp);
    return nums;
}

/**
 * 对数组 nums 的子区间 [left, right] 进行归并排序
 *
 * @param nums
 * @param left
 * @param right
 * @param temp  用于合并两个有序数组的辅助数组,全局使用一份,避免多次创建和销毁
 */
private void mergeSort(int[] nums, int left, int right, int[] temp) {
    // 小区间使用插入排序
    if (right - left <= INSERTION_SORT_THRESHOLD) {
        insertionSort(nums, left, right);
        return;
    }

    int mid = left + (right - left) / 2;
    // Java 里有更优的写法,在 left 和 right 都是大整数时,即使溢出,结论依然正确
    // int mid = (left + right) >>> 1;

    mergeSort(nums, left, mid, temp);
    mergeSort(nums, mid + 1, right, temp);
    // 如果数组的这个子区间本身有序,无需合并
    if (nums[mid] <= nums[mid + 1]) {
        return;
    }
    mergeOfTwoSortedArray(nums, left, mid, right, temp);
}

/**
 * 对数组 arr 的子区间 [left, right] 使用插入排序
 *
 * @param arr   给定数组
 * @param left  左边界,能取到
 * @param right 右边界,能取到
 */
private void insertionSort(int[] arr, int left, int right) {
    for (int i = left + 1; i <= right; i++) {
        int temp = arr[i];
        int j = i;
        while (j > left && arr[j - 1] > temp) {
            arr[j] = arr[j - 1];
            j--;
        }
        arr[j] = temp;
    }
}

/**
 * 合并两个有序数组:先把值复制到临时数组,再合并回去
 *
 * @param nums
 * @param left
 * @param mid   [left, mid] 有序,[mid + 1, right] 有序
 * @param right
 * @param temp  全局使用的临时数组
 */
private void mergeOfTwoSortedArray(int[] nums, int left, int mid, int right, int[] temp) {
    System.arraycopy(nums, left, temp, left, right + 1 - left);

    int i = left;
    int j = mid + 1;

    for (int k = left; k <= right; k++) {
        if (i == mid + 1) {
            nums[k] = temp[j];
            j++;
        } else if (j == right + 1) {
            nums[k] = temp[i];
            i++;
        } else if (temp[i] <= temp[j]) {
            // 注意写成 < 就丢失了稳定性(相同元素原来靠前的排序以后依然靠前)
            nums[k] = temp[i];
            i++;
        } else {
            // temp[i] > temp[j]
            nums[k] = temp[j];
            j++;
        }
    }
}

插入排序 (1560ms)

// 插入排序:稳定排序,在接近有序的情况下,表现优异

public int[] sortArray(int[] nums) {
    int len = nums.length;
    // 循环不变量:将 nums[i] 插入到区间 [0, i) 使之成为有序数组
    for (int i = 1; i < len; i++) {
        // 先暂存这个元素,然后之前元素逐个后移,留出空位
        int temp = nums[i];
        int j = i;
        // 注意边界 j > 0
        while (j > 0 && nums[j - 1] > temp) {
            nums[j] = nums[j - 1];
            j--;
        }
        nums[j] = temp;
    }
    return nums;
}

选择排序 (超出时间限制)

// 选择排序:每一轮选择最小元素交换到未排定部分的开头

public int[] sortArray(int[] nums) {
    int len = nums.length;
    // 循环不变量:[0, i) 有序,且该区间里所有元素就是最终排定的样子
    for (int i = 0; i < len - 1; i++) {
        // 选择区间 [i, len - 1] 里最小的元素的索引,交换到下标 i
        int minIndex = i;
        for (int j = i + 1; j < len; j++) {
            if (nums[j] < nums[minIndex]) {
                minIndex = j;
            }
        }
        swap(nums, i, minIndex);
    }
    return nums;
}

private void swap(int[] nums, int index1, int index2) {
    int temp = nums[index1];
    nums[index1] = nums[index2];
    nums[index2] = temp;
}

public static void main(String[] args) {
    int[] nums = {5, 2, 3, 1};
    Solution solution = new Solution();
    int[] res = solution.sortArray(nums);
    System.out.println(Arrays.toString(res));
}
承认自己的无知 , 乃是开启智慧的大门
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy