leetcode-python-优先级队列与时间复杂度
leetcode-python-專欄目錄
專題概述
無
目錄
無
代碼相關(guān)
最后
如果覺得本文對你有幫助,為我收藏點贊,若文中有任何問題(哪步算法沒看懂,或者涉及到的python語法不了解,或者哪里出錯了)可在評論區(qū)留言。
本文的內(nèi)容
本文圍繞leetcode-347展開,先用最少量的代碼(collections.Counter)解決問題,然后根據(jù)Counter的源碼找到該算法的時間復(fù)雜度。由此引入優(yōu)先級隊列,介紹python中優(yōu)先級隊列的用法,最后用兩個不同的優(yōu)先級隊列解決本題。
熟悉了優(yōu)先隊列之后,通過leetcode-23來實戰(zhàn)。
Leetcode347號問題-前K個高頻元素
題目鏈接
代碼思路:
代碼實現(xiàn):
def topKFrequent(self, nums, k):from collections import Counterreturn [c[0] for c in Counter(nums).most_common(k)]雖然本道題目完成了,但是我們想深究下內(nèi)部的時間復(fù)雜度。
most_common()源碼分析
我們發(fā)現(xiàn)了heapq.nlargest這個python內(nèi)置的堆的方法。
def most_common(self, n=None):if n is None:return sorted(self.items(), key=_itemgetter(1), reverse=True)# 堆的使用,也就是優(yōu)先級return _heapq.nlargest(n, self.items(), key=_itemgetter(1))再繼續(xù)看heapq.nlargest源碼。
def nlargest(n, iterable, key=None):"""Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] """注釋中告訴我們這個方法約等于sorted方法,我們假設(shè)python內(nèi)置的sorted就是普通的快排(不考慮內(nèi)部的優(yōu)化),那么他的時間復(fù)雜度就是O(nlogn)
使用most_common時,即使是我們只想獲得頻率最大的一個,也需要排序整個數(shù)組。消耗O(nlogn)的時間復(fù)雜度
這時為了降低時間復(fù)雜度我們就需要優(yōu)先級隊列了。
python中的優(yōu)先級隊列
在python中優(yōu)先級隊列也是在堆的基礎(chǔ)上進(jìn)行了一層封裝。(from queue import PriorityQueue)
詳情請看:
dyq666:日常編程中的python-優(yōu)先級隊列使用優(yōu)先級隊列
在使用前需要先搞清楚優(yōu)先級隊列的時間復(fù)雜度,往一個長度為n的優(yōu)先級隊列中插入一個元素時間復(fù)雜度是O(logn)。那么把n個元素插入一個空的優(yōu)先級隊列中時間復(fù)雜度就為O(nlogn)。
如果把n個元素插入一個空的優(yōu)先級隊列中,但是優(yōu)先級隊列的最大長度為k,那么時間復(fù)雜度就為O(nlogk)。
基于這個時間復(fù)雜度的計算方式,在這道題中可以構(gòu)造出兩種優(yōu)先級隊列。隊列一保存所有頻率最大的元素,也就是長度為k。隊列二保存所有頻率最小的元素,也就是長度為(n-k)。這兩種方式哪種更好,也就取決于n與k大小之間的差距了。
(一)優(yōu)先級隊列保存頻率大的元素
題目分析:
先使用Counter統(tǒng)計頻率,然后維護(hù)一個優(yōu)先級隊列,這個隊列總共只能存k個元素( 當(dāng)前頻率最高的k各元素 ),也就是優(yōu)先級越高代表他的頻率越高。
遍歷整個Counter,入隊到k個元素。隊滿之后,每次都把隊列中優(yōu)先級最小的元素與當(dāng)前元素對比,選擇優(yōu)先級大的一個。
遍歷結(jié)束后,整個隊列就是結(jié)果了。
在這里最重要的就是想明白你把什么當(dāng)做優(yōu)先級,因為每次出隊的都是優(yōu)先級最小的,我們不想要頻率小的,所以我們讓頻率越小的優(yōu)先級越低。
代碼思路:
時間復(fù)雜度:O(nlogk)
1. 統(tǒng)計頻率,counter算是一個dict,key是數(shù),value是對應(yīng)的頻率
2. 如果k跟所有數(shù)相同就直接返回所有數(shù)
3. 創(chuàng)建優(yōu)先隊列,設(shè)置優(yōu)先隊列的最大長度(這里最大長度是k)。
4. 遍歷所有數(shù)和它的頻率。這里的優(yōu)先級與頻率相同。
5. 如果當(dāng)前隊列長度小于最大長度,直接入隊
6. 如果等于最大長度了,并且當(dāng)前的優(yōu)先級(-freq)比隊列中優(yōu)先級最低的元素優(yōu)先級高(第一個元素)。
那么把優(yōu)先級最低的出隊列,當(dāng)前的入隊列
7. 返回優(yōu)先級隊列中所有的數(shù)
代碼實現(xiàn):(注釋中標(biāo)明了代碼思路中步驟的序號)
def topKFrequent02(self, nums, k):# 步驟一from collections import Countercounter = Counter(nums)len_counter = len(counter)# 步驟二if len_counter == k:return list(counter.keys())# 步驟三from queue import PriorityQueue as PQpq, max_len = PQ(), k# 步驟四for num, freq in counter.items():# 步驟五if len(pq.queue) < max_len:pq.put((freq, num))# 步驟六elif freq > pq.queue[0][0]:pq.get()pq.put((freq, num))# 步驟七return [p[-1] for p in pq.queue](二)優(yōu)先級隊列保存頻率小的元素
區(qū)別分析:
Leetcode23號問題-合并K個排序鏈表
題目鏈接
題目分析:
總共有n個降序的鏈表,意味著每個鏈表當(dāng)前的頭結(jié)點都是最小的,所以只需要維護(hù)一個大小為n的優(yōu)先級隊列,每次出隊后,出隊的內(nèi)條鏈表的頭結(jié)點,如果不是最后一個節(jié)點,就將它的下一個節(jié)點入隊。
舉個例子:[1->2->6, 2->3, 3->4->5]。第一步1,2,和3入隊,然后1出隊,1的下一個節(jié)點2入隊。這種方式保證了最終合成的鏈表一定是排好序的,因為一條鏈表不能有兩個節(jié)點同時在隊列中,這是沒有意義的,因為一條鏈表上前面節(jié)點的一定是更小的或者是相等。
基于上述的講解,我們只需要把整個優(yōu)先級隊列都出完就結(jié)束了
代碼思路:
1. queue_index的作用:假設(shè)兩個元素的優(yōu)先級相同,那么將會去比較元組中第二個值。
但是第二個值如果是一個類的實例,可能沒重寫比較的方法,所以需要一個可以比較的輔助變量來區(qū)分。(這部分一定要仔細(xì)閱讀,或者嘗試幾個例子,或者去文章提到的優(yōu)先級隊列詳情的文章中去看看)
2. 初始化隊列,將所有鏈表的頭結(jié)點入隊
3. 創(chuàng)建最終結(jié)果鏈表的虛擬頭結(jié)點
4. 每次從隊列中獲取優(yōu)先級最小的元素,用needle去穿針引線
5. 如果該節(jié)點后面還有節(jié)點,就將后面的一個節(jié)點入隊
代碼實現(xiàn):
def mergeKLists(self, lists):from queue import PriorityQueue as PQpq = PQ()# 步驟一queue_index = 0# 步驟二for node in lists:if node:pq.put((node.val, queue_index, node))queue_index += 1# 步驟三dummy = ListNode(None)needle = dummy# 步驟四while pq.queue:node = pq.get()[-1]needle.next = nodeneedle = needle.next# 步驟五if node.next:pq.put((node.next.val, queue_index, node.next))queue_index += 1return dummy.next總結(jié)
總結(jié)
以上是生活随笔為你收集整理的leetcode-python-优先级队列与时间复杂度的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习笔记:手写一个单隐层的神经网络
- 下一篇: Python中机器学习的特征选择工具