LeetCode有代表性的題解---排序問題(二)

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 ≤ 數組的長度。

法一:冒泡排序,從大到小排序定位第K-1個

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int tempNum;
        //冒泡排序從大到小排序
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<nums.length-i-1;j++){
                if(nums[j]<nums[j+1]){
                    //交換
                    tempNum=nums[j];
                    nums[j]=nums[j+1];
                    nums[j+1]=tempNum;
                }
            }
        }
        return(nums[k-1]);
    }
}

冒泡排序複雜度:時間複雜度 O(N*N),空間複雜度 O(1),穩定

法二:快速排序,從大到小排序定位第K-1個

//2.快速排序
    public static void quickSort(int[] nums,int low,int high){
        if (low<high){
           int index=quickSort_findIndex(nums,low,high);
            quickSort(nums,0,index-1);
            quickSort(nums,index+1,high);
        }
    }
    public static int quickSort_findIndex(int[] nums, int low, int high) {
        int tenmpNum=nums[low];
        while (low<high){
            while (low<high&&nums[high]<=tenmpNum)//注意此處必須加等於,否則出現相同值時可能無限循環
                high--;
            nums[low]=nums[high];
            while (low<high&&nums[low]>=tenmpNum)//注意此處必須加等於,否則出現相同值時可能無限循環
                low++;
            nums[high]=nums[low];
        }
        nums[low]=tenmpNum;
        return low;
    }

快速排序複雜度:時間複雜度 O(N*logN),空間複雜度 O(1),不穩定

法三:快速選擇排序

複雜度:時間複雜度 O(N),空間複雜度 O(1)

public int findKthLargest(int[] nums, int k) {
    k = nums.length - k;
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int j = quickSort_findIndex(nums, l, h);
        if (j == k) {//如果相等則找到,當前位置的值即爲第K大的值
            break;
        } else if (j < k) {//在右部分找
            l = j + 1;
        } else {//在左部分找
            h = j - 1;
        }
    }
    return nums[k];
}
public static int quickSort_findIndex(int[] nums, int low, int high) {
        int tenmpNum=nums[low];
        while (low<high){
            while (low<high&&nums[high]>=tenmpNum)//注意此處必須加等於,否則出現相同值時可能無限循環
                high--;
            nums[low]=nums[high];
            while (low<high&&nums[low]<=tenmpNum)//注意此處必須加等於,否則出現相同值時可能無限循環
                low++;
            nums[high]=nums[low];
        }
        nums[low]=tenmpNum;
        return low;
}

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 是數組的大小。(此處說明無法使用排序實現,因爲排序最優也得O(n log n))

方法說明:

        題目最終需要返回的是前 k個頻率最大的元素,可以想到藉助堆這種數據結構,對於 k頻率之後的元素不用再去處理,進一步優化時間複雜度。

具體操作爲:

1.藉助 哈希表 來建立數字和其出現次數的映射,遍歷一遍數組統計元素的頻率
2.維護一個元素數目爲 k 的最小堆(小頂堆)
3.每次都將新的元素與堆頂元素(堆中頻率最小的元素)進行比較
        如果新的元素的頻率比堆頂端的元素大,則彈出堆頂端的元素,將新的元素添加進堆中
4.最終,堆中的 k 個元素即爲前 k 個高頻元素

Java代碼如下:

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        //1.先創建hashmap存儲每個值及其對應的頻率
        Map<Integer, Integer> hash_map = new HashMap();
        for (int num : nums) {
            hash_map.put(num, hash_map.getOrDefault(num, 0) + 1);
        }
        //2.創建小頂堆
        PriorityQueue<Integer> pq = new PriorityQueue(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return hash_map.get(o1)-hash_map.get(o2);//返回值小於0交換數a和數b的位置,否則相反;數a數又邊的數,數b是左邊的數
            }
        });

        //3.建堆
        for(int key:hash_map.keySet()){
            if (pq.size()<k){//如果小頂堆中的個數小於K,直接添加
                pq.add(key);
            }else if(hash_map.get(key)>hash_map.get(pq.peek())){//如果小頂堆中已有k個數值,但是當前數值比堆頂的數值大則刪除堆頂的數值,將當前數值添加到堆中
                pq.poll();//返回並且刪除堆頂元素——隊列爲空的時候返回null
                pq.add(key);
            }
        }

        //4.用一個List存放堆中的k個值
        List<Integer> list=new ArrayList<>();
        while (!pq.isEmpty())
            list.add(pq.poll());
        return  list;
        
    }
}

複雜度分析
時間複雜度:O(nlogk),n 表示數組的長度。首先,遍歷一遍數組統計元素的頻率,這一系列操作的時間複雜度是O(n);接着,遍歷用於存儲元素頻率的 map,如果元素的頻率大於最小堆中頂部的元素,則將頂部的元素刪除並將該元素加入堆中,這裏維護堆的數目是 k,所以這一系列操作的時間複雜度是 O(nlogk) 的;因此,總的時間複雜度是O(nlog⁡k)。
空間複雜度:O(n),最壞情況下(每個元素都不同),map 需要存儲 n 個鍵值對,優先隊列需要存儲 k個元素,因此,空間複雜度是 O(n).

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'被認爲是兩種不同的字符。

解題思想:本題和leetcode347. 前k個高頻元素實際上是一個類型的,都可以利用桶排序來高效解決。步驟如下:

1.遍歷字符數組,將每個字符的和它對應的頻次存入哈希表中。
2.新建一個桶,將每個字符存入索引爲它的頻次的那個桶中。由於桶的索引本身就是自增的,因此這樣就直接利用桶完成了對每個字符按照它的出現次數進行了從大到小的排序。
3.倒着遍歷桶,將每個桶裏的元素取出來,並按照它的頻次存入要返回的結果中

JAVA代碼如下:

class Solution {
    public String frequencySort(String s) {
        char[] chs = s.toCharArray();
        //1.用一個HashMap統計每個字符出現的頻率
        Map<Character,Integer> frequencyMap=new HashMap<>();
        for(char c:s.toCharArray()){
            //統計每個字符出現的頻率
            frequencyMap.put(c,frequencyMap.getOrDefault(c,0)+1);
        }
        //2.利用桶存儲字符和字符出現的頻率,頻率即爲桶的下標
        List<Character>[] frequencyBucket= new ArrayList[s.length()+1];
        for(char c:frequencyMap.keySet()){
            //得到當前字符對應的頻率
            int frequencyValue=frequencyMap.get(c);
            if (frequencyBucket[frequencyValue]==null) {
                frequencyBucket[frequencyValue] = new ArrayList<>();
            }
                frequencyBucket[frequencyValue].add(c);
        }
        //3.倒序取出桶中的字符以及其對應的下標(字符出現的頻率)
        List<Character> list=new ArrayList<>();
        int p=0;
        for (int i=frequencyBucket.length-1;i>0;i--){
            if(frequencyBucket[i]==null)
                continue;
            for (char c:frequencyBucket[i]){
                for (int j=0;j<i;j++) chs[p++]=c;
            }
        }
        return new String(chs) ;
    }
}

75. 顏色分類

問題:給定一個包含紅色、白色和藍色,一共 n 個元素的數組,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。

此題中,我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。

注意:
不能使用代碼庫中的排序函數來解決這道題。

示例:

輸入: [2,0,2,1,1,0]
輸出: [0,0,1,1,2,2]
進階:

一個直觀的解決方案是使用計數排序的兩趟掃描算法。
首先,迭代計算出0、1 和 2 元素的個數,然後按照0、1、2的排序,重寫當前數組。
你能想出一個僅使用常數空間的一趟掃描算法嗎?

分析:本問題被稱爲 荷蘭國旗問題,最初由 Edsger W. Dijkstra提出。其主要思想是給每個數字設定一種顏色,並按照荷蘭國旗顏色的順序進行調整。

算法:

1.初始化0的最右邊界:p0 = 0。在整個算法執行過程中 nums[idx < p0] = 0.

2.初始化2的最左邊界 :p2 = n - 1。在整個算法執行過程中 nums[idx > p2] = 2.

3.初始化當前考慮的元素序號 :curr = 0.

4.While curr <= p2 :

      若 nums[curr] = 0 :交換第 curr個 和 第p0個 元素,並將指針都向右移

      若 nums[curr] = 2 :交換第 curr個和第 p2個元素,並將 p2指針左移

      若 nums[curr] = 1 :將指針curr右移

JAVA代碼如下:

class Solution {
    public void sortColors(int[] nums) {
        int p1=0;
        int p2=nums.length-1;
        int cur=0;
        int tempNum;
        while(cur<=p2){//注意點1:cur要小於等於p2,否則已經換好位置的2可能再經遍歷又被換回去
            if(nums[cur]==2){
                tempNum=nums[p2];
                nums[p2]=nums[cur];
                nums[cur]=tempNum;
                p2--;
            }
            else if(nums[cur]==0){
                tempNum=nums[p1];
                nums[p1]=nums[cur];
                nums[cur]=tempNum;
                p1++;
                cur++;//注意點2:此處必須cur++
            }else
                cur++;

        }
        
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章