leetcode 排序

归并排序

21.合并两个有序链表

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

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

JAVA代码:

//类似于归并排序的归并部分
//迭代法
//没有头指针
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
//类似于归并排序的归并部分
//迭代法
//没有头指针
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
            ListNode head  = new ListNode(-1);
            ListNode p = head;
        while (l1!=null && l2!=null){
            if (l1.val<=l2.val){
                p.next = l1;
                p = p.next;
                l1 = l1.next;
            }
            else{
                p.next = l2;
                p = p.next;
                l2 = l2.next;
            }
        }
        //有且仅有一个列表没有遍历完
        p.next = l1 == null ? l2 : l1;
        return head.next;
    }
}

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

解答
有时间复杂度O(nlogn), 想到归并排序
在这里插入图片描述

  1. 对链表进行切分并归并排序。
    将链表切分成大小为1的多个子链表,两两排序合并,共2个元素合并。
    将链表切分成大小为2的多个子链表,两两排序合并,共4个元素合并。
    将链表切分成大小为4的多个子链表,两两排序合并,共8个元素合并。
    一共切分 logn 次。 .
    JAVA代码:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        //dummyhead:一次排序后链表
        //curr:无序链表。
        //prev: 当前排好的有序链表的尾结点。
        
        ListNode dummyhead = new ListNode(-1);
        dummyhead.next = head;   //dummyhead的后面一个结点指向head
        
        //链表长度
        int len = listLength(head);
        
        for (int step=1; step<=len; step<<=1){
            ListNode curr = dummyhead.next;
            ListNode prev = dummyhead;
            
            while (curr!=null){
                ListNode left = curr;
                ListNode right = split(left, step);  //从left切掉step个结点得到left.并返回切点,即right链表头结点
                curr = split(right, step);  //从right切掉step个结点得到right,并返回切点。
                prev.next = merge(left, right); 
                while(prev.next!=null) prev = prev.next;
            }
        }
        
        return dummyhead.next;
    }
    
    //切分链表: head前step个结点切掉作为left部分,返回后一个切点,即right部分的头节点
    public ListNode split(ListNode head, int step){
        ListNode curr = head;
        for (int i=1; i<step && curr!=null; i++){
            curr = curr.next;
        }
        if (curr==null) return null;
        ListNode right = curr.next;
        curr.next = null;
        return right;
    }
    
    //二路归并
    public ListNode merge(ListNode left, ListNode right){
        ListNode dummy = new ListNode(-1);
        ListNode curr = dummy;
        while(left!=null && right!=null){
           if (left.val<= right.val){
               curr.next = left;
               left = left.next;
               curr = curr.next;
           }
           else{
               curr.next = right;
               right = right.next;
               curr = curr.next;
           }
        }
        curr.next = left==null? right: left;
        return dummy.next;
    }
    
    //求链表长度
    public int listLength(ListNode head){
        int length = 0;
        ListNode curr = head;
        while (curr!=null){
            curr = curr.next;
            length++;
        }
        return length;
    }
}

快速排序

75. 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

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

JAVA代码:

class Solution {
    public void sortColors(int[] nums) {
        int red = -1;
        int blue = nums.length;
        int i = 0;
        while (i<blue){
            if (nums[i]==0){
                swap(nums, i++, ++red);
            }
            else if(nums[i]==2){
                swap(nums, i, --blue);
            }
            else i++;
        }
    }
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }   
}

堆排序

347. 前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

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

示例 2:

输入: nums = [1], k = 1
输出: [1]

说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。

题解:

1. 哈希表 + 小根堆

  1. 使用哈希表,统计每个元素出现的次数,元素作为键,出现的次数作为值。
  2. 将元素放入小根堆中,根据元素出现的次数进行排序,维持小根堆大小为k,最后得到的小根堆中的元素即为前k高频的元素。
  3. 遍历k个元素的小根堆,放入结果数组中。
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
        Map<Integer, Integer> map = new HashMap<>();
        for (int num: nums){
            map.put(num, map.getOrDefault(num, 0)+1);
        }
        
        // 堆排序:将元素放入小根堆中,按照元素的频率排序,并维持堆大小为k
        
        // 重写compare方法
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>(){
            public int compare(Integer a, Integer b){
                return map.get(a)-map.get(b);
            }
        });
        
        // 小根堆,维持大小为k
        for (int key: map.keySet()){
            pq.offer(key);
            if (pq.size() > k){
                 pq.poll();
            }
        }
        
        // 访问小根堆中的元素
        int[] res = new int[k];
        int i = 0;
        while(!pq.isEmpty()){
            res[i++] = pq.poll();
        }
        return res;
        
    }
}

时间复杂度O(n*logk)
空间复杂度O(n)

2. 哈希表 + 桶

  1. 哈希表存放各数字出现的频率。
  2. 准备若干个桶,每个桶存放出现次数相同的数字,桶的下标表示出现的次数。
  3. 只要倒序遍历k个非空桶即可得到前K个高频元素。
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num: nums){
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        
        // 频率作为下标,将数放在桶中,频率越大的数越在后面
        List<Integer>[] buckets = new ArrayList[nums.length + 1];
        for (int key : map.keySet()) {  // 返回map中所有key值的列表
            int frequency = map.get(key);
            if (buckets[frequency] == null){
                buckets[frequency] = new ArrayList<>();
            }
            buckets[frequency].add(key);
        }
        
        // 获取频率前k高的元素
        int[] res = new int[k];
        int index = 0;
        for (int i = buckets.length-1; index < k; i--){
            if (buckets[i] == null)
                continue;
            for(int j = 0; j<buckets[i].size(); j++){
                res[index++] = buckets[i].get(j);
            }
        }
        return res;
    }
}

时间复杂度O(n)
空间复杂度O(n)


215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

题解:
1. 直接排序

class Solution {
    public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length-k];
    }
}

时间复杂度O(nlogn)
空间复杂度O(1)

2. 堆排序:优先队列
维护小根堆的大小为k,最后队列中第一个元素即为第k大的元素。

 class Solution{
     public int findKthLargest(int[] nums, int k){
         PriorityQueue<Integer> pq = new PriorityQueue<>();
         for (int num: nums){
            pq.add(num);
             // 维护堆的大小
             if (pq.size()>k){
                 pq.poll();
             }
         }
         return pq.peek();
     }
 }

时间复杂度O(nlogk)
空间复杂度O(1)

3. 堆排序:大根堆
先对数组所有元素形成大根堆,然后再根下沉直到排好最大的k的元素。

class Solution{
    public int findKthLargest(int[] nums, int k){
        // 形成大根堆
        for (int i = 0; i<nums.length; i++){
            heapInsert(nums, i);
        }
        int heapSize = nums.length;
        swap(nums, 0, --heapSize);
        while(heapSize > nums.length-k){
            heapify(nums, heapSize);
            swap(nums, 0, --heapSize);
        }
        return nums[nums.length-k];
    }
    public void heapInsert(int[] nums, int index){
        while(nums[index] > nums[(index-1)/2]){
            swap(nums, index, (index-1)/2);
            index = (index-1)/2;
        }
    }
    
    public void heapify(int[] nums, int heapSize){
        int index = 0;
        int left = 1;
        while(left < heapSize){
            int right = left + 1;
            int largest = right < heapSize && nums[left]<nums[right]
                          ? right
                          : left;
            largest = nums[largest] < nums[index]? index:largest;
            if (largest == index) 
                break;
            
            swap(nums, largest, index);
            index = largest;
            left = index*2+1;   
        }
    }
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

时间复杂度O(nlogk)
空间复杂度O(1)

4. 快速选择
将无序部分nums[L: R]的最后一个元素作为基准,小于等于基准的放在左边,大于基准的放在右边,如此就能知道该基准是第几小的数。设基准下标为posi

  • posi<k ,那么第k大的数在nums[L+1, R]区间
  • posi>k, 那么第k大的数在nums[L, R-1]区间
  • posi == k, 那么nums[posi]正好为第k大的数。

思路过程类似与快速排序,不同于快排的是,快速选择只要排左右其中一个区间,而快速排序左右两个区间都要排序。

class Solution{
    public int findKthLargest(int[] nums, int k){
        k = nums.length - k;
        int L = 0;
        int R = nums.length-1;
        int posi = 0;
        while(L<=R){
            posi = position(nums, L, R);
            if (posi == k){
                break;
            }
            else if (posi < k){
                L = posi + 1;
            }
            else 
                R = posi - 1;
        }
        return nums[posi];
    }
    
    public int position(int[] nums, int L, int R){
        int less = L-1;
        int more = R;
        int i = L;
        while(i < more){
            if (nums[i] <= nums[R]){
                i++;
                less++;
            }
            else {
                swap(nums, i, --more);
            }
        }
        swap(nums, more, R);
        int posi = more;
        return posi;
    }
    
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

时间复杂度平均情况O(n), 最坏情况O(n^2)
空间复杂度O(1)

桶排序

451. 根据字符出现频率排序

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入:
"tree"

输出:
"eert"

解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

示例 2:

输入:
"cccaaa"

输出:
"cccaaa"

解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。

示例 3:

输入:
"Aabb"

输出:
"bbAa"

解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。

题解:哈希表 + 桶

  1. 将字符和对应出现的频率存放在哈希表中:{字符:频率}
  2. 将不同频率的字符装进相应的桶中,桶的下标表示不同的频率
  3. 倒序遍历桶中的字符,即按照频率降序的方式遍历,根据字符及对应的频率拼接成结果字符串。
class Solution {
    public String frequencySort(String s) {
        Map<Character, Integer> map = new HashMap<>();
        
        for (int i = 0; i<s.length(); i++){
            char ch = s.charAt(i);
            map.put(ch, map.getOrDefault(ch, 0)+1);
        }
        
        List<Character>[] buckets = new ArrayList[s.length()+1];
        
        // 将不同频率的字符装进相应的桶中
        for (Character key: map.keySet()){
            int fq = map.get(key);
            if (buckets[fq] == null){
                buckets[fq] = new ArrayList<>();
            }
            buckets[fq].add(key);
        }
        
        // 倒序遍历桶
        StringBuffer sb = new StringBuffer();
        for(int i = buckets.length-1; i>0; i--){
            if (buckets[i] == null)
                continue;
            for (int j = 0; j<buckets[i].size(); j++){
                for (int k = 0; k<i; k++){
                    sb.append(buckets[i].get(j));
                }
            }
            
        }
        return sb.toString();
        
    }
}

时间复杂度O(n)
空间复杂度O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值