leetcode 排序相关题目总结

Leetcode 21:合并两个有序链表

题目描述

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4

解题思路

​ 相当于归并排序中的merge操作,只不过对象换成了链表。

代码

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode res = new ListNode(-1);
        ListNode head = res;
        
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                head.next = l1;
                head = head.next;
                l1 = l1.next;
            }
            else{
                head.next = l2;
                head = head.next;
                l2 = l2.next;
            }
        }
        
        while(l1 != null){
            head.next = l1;
            head = l1;
            l1 = l1.next;
        }
        
        while(l2 != null){
            head.next = l2;
            head = l2;
            l2 = l2.next;
        }
        
        return res.next;
    }
}

Leetcode 912: 排序数组

题目描述

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

示例 1:

输入:[5,2,3,1] 输出:[1,2,3,5] 示例 2:

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

解题思路

这题是标准的数组排序题,也可以看作给各种排序算法的性能测试。在我提交的各种算法中,快排的性能最好。

代码

class Solution {
    public int[] sortArray(int[] nums) {
        quickSort(nums);
        return nums;
    }

    private void quickSort(int[] arr) {
        quickSort(arr, 0, arr.length - 1);
    }

    private void quickSort(int[] arr, int start, int end) {
        if (start >= end) return;
        int k = partition(arr, start, end);
        quickSort(arr, start, k - 1);
        quickSort(arr, k + 1, end);
    }

    
    private int partition(int arr[], int low, int high) {
        int pivot = arr[high];
        int i = low - 1;

        for (int j = low; j < high; j++) {
            if (arr[j] < pivot) {
                i++;
                swap(arr, i, j);
            }
        }

        swap(arr, i + 1, high);

        return i + 1;
    }
    private void swap(int[] arr, int a, int b) {
        int tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;
    }

}

Leetcode 378 :有序矩阵中第K小的元素

题目描述

给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。 请注意,它是排序后的第k小元素,而不是第k个元素。

示例:

matrix = [ [ 1, 5, 9], [10, 11, 13], [12, 13, 15] ], k = 8,

返回 13。 说明: 你可以假设 k 的值永远是有效的, 1 ≤ k ≤ n2 。

解题思路

​ 首先想到的是topK算法,只需要先二位矩阵展成一维数组即可。代码如下:

class Solution {
    private void swap(int[] arr, int a, int b){
        int tmp = arr[a];
        arr[a]  = arr[b];
        arr[b] = tmp;
    }
    
    private int partition(int[] arr, int lo, int hi){
        int count = lo, j = hi;
        int pivot = arr[hi];
        
        for(int i = lo; i <  hi; i++){
            if(arr[i] < pivot){
                swap(arr, i , count);
                count++;
            }
        }
        
        swap(arr, count, hi);
        return count;
        
    }
    
    private int select(int[] arr, int k){
        
        int lo = 0, hi = arr.length -1;
        
        while(lo <  hi){
            int j = partition(arr, lo, hi);
            if(j < k){
                lo = j + 1;
            }
            else if( j > k ){
                hi = j -1;
            }
            else{
               return arr[k];
            }
        }
        
        return arr[k];
    }
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        
        int[] arr = new int[n * n];
        
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                arr[i * n + j]  = matrix[i][j];
            }
        }
        
        int res = 0;
        res = select(arr, k - 1);
        
        return res;
    }
}

​ topk问题还可以用堆来解决, Java中的优先队列是基于 堆来实现的,默认是自然顺序,也就是从小到大的顺序,由于Java的priorityQueue只能通过peek(),poll()方法来取值,所以我们需要维护一个length - k+ 1的堆(比如要求9个元素中第8小的元素,也可以看作是9个元素中第2(9 - 8 + 1)大的元素)。

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int size = matrix.length;
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        
        
        for(int i = 0; i < size; i++){
            for(int j = 0; j < size; j++){
                pq.add(matrix[i][j]);
                if(pq.size() >  size*size - k + 1)  pq.poll();
            }
        }
        
        return pq.peek();
    }
}

Leetcode 148:排序链表

题目描述

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3 输出: 1->2->3->4 示例 2:

输入: -1->5->3->4->0 输出: -1->0->3->4->5

解题思路

1:比较直观的想法是将链表转化为数组,再排序,再重新构造链表

class Solution {
    public ListNode sortList(ListNode head) {
        List<Integer> arr = new ArrayList<>();
        
        ListNode tmp = head;
        
        while(tmp!= null){
            arr.add(tmp.val);
            tmp = tmp.next;
        }
        
        Collections.sort(arr);
        
        tmp = head;
        int idx = 0;
        
        while(tmp != null){
            tmp.val = arr.get(idx++);
            tmp = tmp.next;
        }
        
        return head;
    }
}

2:当然也可以实现基于链表的快速排序和归并排序。由于快排会用到大量的swap,而归并排序只是相邻元素间的copy,所以归并排序会更简单实现一点,效率也会更好。merge操作可以参考leetcode 21;

代码

class Solution {
    public ListNode sortList(ListNode head) {
        if(head == null || head.next == null) return head;
        
        ListNode slow  = head, fast = head.next;
        
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        
        ListNode mid = slow.next;
        slow.next = null;
        
        return mergeTwoLists(sortList(head),sortList(mid));
    }
    
    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode res = new ListNode(-1);
        ListNode head = res;
        
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                head.next = l1;
                head = head.next;
                l1 = l1.next;
            }
            else{
                head.next = l2;
                head = head.next;
                l2 = l2.next;
            }
        }
        
        while(l1 != null){
            head.next = l1;
            head = l1;
            l1 = l1.next;
        }
        
        while(l2 != null){
            head.next = l2;
            head = l2;
            l2 = l2.next;
        }
        
        return res.next;
    }
}

Leetcode 88 :合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 示例:

输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3

输出: [1,2,2,3,5,6]

解题思路

数组的merge操作

代码

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int[] aux = nums1.clone();

        int i = 0, j = 0;
        int idx = 0;

        while(i < m && j < n){
            if(aux[i] < nums2[j]){
                nums1[idx] = aux[i];
                i++;
            }
            else{
                nums1[idx] = nums2[j];
                j++;
            }
            idx++;
        }

        while(i < m){
            nums1[idx] = aux[i];
            idx++;
            i++;
        }

        while( j < n){
            nums1[idx] = nums2[j];
            idx++;
            j++;
        }
    }
}

Leetcode 280:摆动排序

题目描述

给你一个无序的数组 nums, 将该数字 原地 重排后使得 nums[0] <= nums[1] >= nums[2] <= nums[3]…。

示例:

输入: nums = [3,5,2,1,6,4] 输出: 一个可能的解答是 [3,5,1,6,2,4]

题目思路:

  1. 先排序,然后从第二个元素开始,每两个元素交换一下位置。
class Solution {
    public void wiggleSort(int[] nums) {
        Arrays.sort(nums);
        
        for(int i = 1; i < nums.length - 1; i+= 2){
            swap(nums, i , i+ 1);
        }
    }
    
    private void swap(int[] arr, int a, int b){
        int tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;
    }
}
  1. 第二种方法就像想我们能不能一步步的将数组按摆动的顺序排好?摆动的顺序用一个表达式来表示就是:当i为偶数时,nums[i] <= nums[i + 1];当i 为奇数时,nums[i]>= nums[i +1];如果实际顺序是相反的,通过一次交换就可以达到目的,并且不会和已排好序的部分冲突。
class Solution {
    public void wiggleSort(int[] nums) {
        
        for(int i = 0; i < nums.length - 1; i++){
            if((i % 2 == 1 && nums[i] < nums[i + 1])|| (i % 2 == 0 && nums[i]> nums[i+ 1])){
            swap(nums, i, i + 1);
            }
        }
    }
    
    private void swap(int[] arr, int a, int b){
        int tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;
    }
}

Leetcode 324: 摆动排序 II

题目描述

给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。

示例 1:

输入: nums = [1, 5, 1, 1, 6, 4] 输出: 一个可能的答案是 [1, 4, 1, 5, 1, 6] 示例 2:

输入: nums = [1, 3, 2, 2, 3, 1] 输出: 一个可能的答案是 [2, 3, 1, 3, 1, 2]

解题思路

相比于leetcode280 ,这题的要求更为严苛,没有了等于。所以之前的方法是不行了,不过我们还是可以先采用先排序再构造的思路。可以先将数组折半,然后再将后半段插入到前半段中。但这样是通不过[4,5, 5, 6]这个测试案例的。所以我们需要做出调整,将后半段倒着插入到倒着的前半段中,这样就可以了。

参考答案

class Solution {
    public void wiggleSort(int[] nums) {
        Arrays.sort(nums);
       int[] aux = nums.clone();
       int n = nums.length;
       int j = (n-1)/2, k = n - 1;
        
        for(int i = 0;  i< n ; i++){
            if(i % 2 == 0){
                nums[i] = aux[j];
                j--;
            }
            else{
                nums[i] = aux[k];
                k--;
            }
        }
    }
}
  1. 上述算法的时间复杂度为O(nlogn),进阶的要求是时间复杂度为O(n),这个怎样做到呢?肯定是不能用排序了,但是我们又要保证交叉的顺序,topK算法正好可以达到要求。我们只需要用topk算法先将数组按小于median , 等于median,大于median,后续的算法就和上面一样了。

    参考答案

class Solution {
    
    private int quickSelect(int[] arr, int k){
        int lo = 0, hi = arr.length -1;
        
        while(lo <= hi){
            int j = partition(arr, lo, hi);
            if( j == k ) return arr[j];
            
            if( j < k){
                lo = j + 1;
            }
            else{
                hi = j - 1;
            }
        }
        
        return -1;
    }
    private int partition(int[] arr, int lo, int hi){
        int count = lo;
        int pivot = arr[hi];
        
        for(int i = lo; i < hi; i++){
            if(arr[i] < pivot){
                swap(arr, count, i);
                count++;
            }
        }
        
        swap(arr, count , hi);
        return count;
    }
    
    public void wiggleSort(int[] nums) {
        int n = nums.length;
       int median = quickSelect(nums,n/2 );
        
        int[] aux =new int[n];
        
        int left = 0, right = n - 1;
        
        for(int i = 0 ; i < n; i++){
            if(nums[i] < median){
                aux[left] = nums[i];
                left++;
            }
            else if(nums[i] > median) {
                aux[right] = nums[i];
                right--;
            }
        }
        
        for(int i = left ; i <= right; i++){
            aux[i] = median;
        }
        
        left = (n- 1)/2;
        right = n - 1;
         
        for (int i = 0; i < n; i++) {
            if ( i % 2 == 0) {
                nums[i] = aux[left];
                left--;
            } else {
                nums[i] = aux[right];
                right--;
            }
        }
}
    
    private void swap(int[] arr, int a, int b){
        int tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;
    }
}

如果需要使空间复杂度降到O(1),需要用到坐标映射。另外这个O(n) 的算法在leetcode上实际还没有O(nlogn)的算法快。

Leetcode 1122 :数组的相对排序

题目描述

给你两个数组,arr1 和 arr2,

arr2 中的元素各不相同 arr2 中的每个元素都出现在 arr1 中 对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

示例:

输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6] 输出:[2,2,2,1,4,3,3,9,6,7,19]

提示:

arr1.length, arr2.length <= 1000 0 <= arr1[i], arr2[i] <= 1000 arr2 中的元素 arr2[i] 各不相同 arr2 中的每个元素 arr2[i] 都出现在 arr1 中

解题思路

​ 可以用一个map<value, count>将arr1排序,由于arr1中的值的范围比较小,可以直接用一个数值代替map.先排arr2中的数,边排边从map中去掉。再排剩下的元素。

参考答案

class Solution {
    public int[] relativeSortArray(int[] arr1, int[] arr2) {
        int[] cnt = new int[1001];
        
        for(int num : arr1){
            cnt[num]++;
        }
        
        int idx = 0;
        
        for(int num : arr2){
            while(cnt[num] > 0){
                arr1[idx++] = num;
                cnt[num]--;
            }
        }
        
        for(int i= 0; i < 1001; i++){
            while(cnt[i] > 0){
                arr1[idx++] = i;
                cnt[i]--;
            }
        }
        
        return arr1;
    }
}

Leetcode 147:对链表进行插入排序

题目描述:

对链表进行插入排序。

插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。 每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。

插入排序算法:

插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。 重复直到所有输入数据插入完为止。

解题思路

模仿数组的InsertSort,所要注意的是在head之前插入的问题和ListNode的next调整的顺序。

参考答案

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode insertionSortList(ListNode head) {
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        
        ListNode curr = head;
        
        while(curr != null&& curr.next != null){
            ListNode tmp = curr.next;
            while(tmp != null && tmp.val < curr.val){
                curr.next = tmp.next;
                insert(dummyHead, tmp);
                tmp = curr.next;
                
            }
            curr= curr.next;
        }
        
        return dummyHead.next;
    }
    
    private void insert(ListNode dummyHead, ListNode tmp){
        ListNode pre = dummyHead;
        while(pre.next.val <= tmp.val){
            pre = pre.next;
        }
        
        tmp.next = pre.next;
        pre.next = tmp;
    }
}