最近刷力扣題,對於我這種 0 基礎來講,真的是腦袋疼啊。這個月我估計都是中等和困難題,沒有簡單題了。java
幸虧,力扣上有各類大牛給寫題解。看着他們行雲流水的代碼,真的是羨慕不已。讓我印象最深入的就是人稱 「甜姨」 的知心姐姐,還有名叫威哥的大哥。幾乎天天他們的題解我都是必看的。node
甜姨的題解,雖然姿式很帥,可是對於我這種新手來講,感受不是太友好,由於思路寫的太少,不是很詳細。因此,每次我看不明白的時候,都得反覆看好幾遍,才能想明白她代碼中的思路。算法
上個週末的一道題是,讓實現一個 LFU 緩存算法。通過我幾個小時的研究(其實,應該有8個小時以上了,沒得辦法啊,菜就得多勤奮咯),終於把甜姨的思路整明白了。爲了便於之後本身複習,就把整個思路記下來了,並配上圖示和大量代碼註釋,我相信對於跟我同樣的新手來講,是很是友好的。數組
通過甜姨贊成,參考來源我也會貼出來:https://leetcode-cn.com/problems/lfu-cache/solution/java-13ms-shuang-100-shuang-xiang-lian-biao-duo-ji/緩存
雖然,力扣要求是用時間複雜度 O(1) 來解,可是其它方式我感受也有必要了解,畢竟是一個由淺到深的過程,本身實現一遍總歸是好的。所以,我就把五種求解方式,從簡單到複雜,都講一遍。數據結構
力扣原題描述以下:ide
請你爲 最不常用(LFU)緩存算法設計並實現數據結構。它應該支持如下操做:get 和 put。 get(key) - 若是鍵存在於緩存中,則獲取鍵的值(老是正數),不然返回 -1。 put(key, value) - 若是鍵不存在,請設置或插入值。當緩存達到其容量時,則應該在插入新項以前,使最不常用的項無效。在此問題中,當存在平局(即兩個或更多個鍵具備相同使用頻率)時,應該去除 最近 最少使用的鍵。 「項的使用次數」就是自插入該項以來對其調用 get 和 put 函數的次數之和。使用次數會在對應項被移除後置爲 0 。 示例: LFUCache cache = new LFUCache( 2 /* capacity (緩存容量) */ ); cache.put(1, 1); cache.put(2, 2); cache.get(1); // 返回 1 cache.put(3, 3); // 去除 key 2 cache.get(2); // 返回 -1 (未找到key 2) cache.get(3); // 返回 3 cache.put(4, 4); // 去除 key 1 cache.get(1); // 返回 -1 (未找到 key 1) cache.get(3); // 返回 3 cache.get(4); // 返回 4 來源:力扣(LeetCode) 連接:https://leetcode-cn.com/problems/lfu-cache
就是要求咱們設計一個 LFU 算法,根據訪問次數(訪問頻次)大小來判斷應該刪除哪一個元素,get和put操做都會增長訪問頻次。當訪問頻次相等時,就判斷哪一個元素是最久未使用過的,把它刪除。函數
所以,這道題須要考慮兩個方面,一個是訪問頻次,一個是訪問時間的前後順序。this
思路:spa
咱們可使用JDK提供的優先隊列 PriorityQueue 來實現 。 由於優先隊列內部維護了一個二叉堆,便可以保證每次 poll 元素的時候,均可以根據咱們的要求,取出當前全部元素的最大值或是最小值。只須要咱們的實體類實現 Comparable 接口就能夠了。
所以,咱們須要定義一個 Node 來保存當前元素的訪問頻次 freq,全局的自增的 index,用於比較大小。而後定義一個 Map<Integer,Node> cache ,用於存放元素的信息。
當 cache 容量不足時,根據訪問頻次 freq 的大小來刪除最小的 freq 。若相等,則刪除 index 最小的,由於index是自增的,越大說明越是最近訪問過的,越小說明越是很長時間沒訪問過的元素。
因本質是用二叉堆實現,故時間複雜度爲O(logn)。
public class LFUCache4 { public static void main(String[] args) { LFUCache4 cache = new LFUCache4(2); cache.put(1, 1); cache.put(2, 2); // 返回 1 System.out.println(cache.get(1)); cache.put(3, 3); // 去除 key 2 // 返回 -1 (未找到key 2) System.out.println(cache.get(2)); // 返回 3 System.out.println(cache.get(3)); cache.put(4, 4); // 去除 key 1 // 返回 -1 (未找到 key 1) System.out.println(cache.get(1)); // 返回 3 System.out.println(cache.get(3)); // 返回 4 System.out.println(cache.get(4)); } //緩存了全部元素的node Map<Integer,Node> cache; //優先隊列 Queue<Node> queue; //緩存cache 的容量 int capacity; //當前緩存的元素個數 int size; //全局自增 int index = 0; //初始化 public LFUCache4(int capacity){ this.capacity = capacity; if(capacity > 0){ queue = new PriorityQueue<>(capacity); } cache = new HashMap<>(); } public int get(int key){ Node node = cache.get(key); // node不存在,則返回 -1 if(node == null) return -1; //每訪問一次,頻次和全局index都自增 1 node.freq++; node.index = index++; // 每次都從新remove,再offer是爲了讓優先隊列可以對當前Node重排序 //否則的話,比較的 freq 和 index 就是不許確的 queue.remove(node); queue.offer(node); return node.value; } public void put(int key, int value){ //容量0,則直接返回 if(capacity == 0) return; Node node = cache.get(key); //若是node存在,則更新它的value值 if(node != null){ node.value = value; node.freq++; node.index = index++; queue.remove(node); queue.offer(node); }else { //若是cache滿了,則從優先隊列中取出一個元素,這個元素必定是頻次最小,最久未訪問過的元素 if(size == capacity){ cache.remove(queue.poll().key); //取出元素後,size減 1 size--; } //不然,說明能夠添加元素,因而建立一個新的node,添加到優先隊列中 Node newNode = new Node(key, value, index++); queue.offer(newNode); cache.put(key,newNode); //同時,size加 1 size++; } } //必須實現 Comparable 接口才可用於排序 private class Node implements Comparable<Node>{ int key; int value; int freq = 1; int index; public Node(){ } public Node(int key, int value, int index){ this.key = key; this.value = value; this.index = index; } @Override public int compareTo(Node o) { //優先比較頻次 freq,頻次相同再比較index int minus = this.freq - o.freq; return minus == 0? this.index - o.index : minus; } } }
思路:
只用一條雙向鏈表,來維護頻次和時間前後順序。那麼,能夠這樣想。把頻次 freq 小的放前面,頻次大的放後面。若是頻次相等,就從當前節點日後遍歷,直到找到第一個頻次比它大的元素,並插入到它前面。(固然,若是遍歷到了tail,則插入到tail前面)這樣能夠保證同頻次的元素,最近訪問的老是在最後邊。
所以,總的來講,最低頻次,而且最久未訪問的元素確定就是鏈表中最前面的那一個了。這樣的話,當 cache容量滿的時候,直接把頭結點刪除掉就能夠了。可是,咱們這裏爲了方便鏈表的插入和刪除操做,用了兩個哨兵節點,來表示頭節點 head和尾結點tail。所以,刪除頭結點就至關於刪除 head.next。
PS:哨兵節點只是爲了佔位,實際並不存儲有效數據,只是爲了鏈表插入和刪除時,不用再判斷當前節點的位置。否則的話,若當前節點佔據了頭結點或尾結點的位置,還須要從新賦值頭尾節點元素,較麻煩。
爲了便於理解新節點如何插入到鏈表中合適的位置,做圖以下:
代碼以下:
public class LFUCache { public static void main(String[] args) { LFUCache cache = new LFUCache(2); cache.put(1, 1); cache.put(2, 2); // 返回 1 System.out.println(cache.get(1)); cache.put(3, 3); // 去除 key 2 // 返回 -1 (未找到key 2) System.out.println(cache.get(2)); // 返回 3 System.out.println(cache.get(3)); cache.put(4, 4); // 去除 key 1 // 返回 -1 (未找到 key 1) System.out.println(cache.get(1)); // 返回 3 System.out.println(cache.get(3)); // 返回 4 System.out.println(cache.get(4)); } private Map<Integer,Node> cache; private Node head; private Node tail; private int capacity; private int size; public LFUCache(int capacity) { this.capacity = capacity; this.cache = new HashMap<>(); /** * 初始化頭結點和尾結點,並做爲哨兵節點 */ head = new Node(); tail = new Node(); head.next = tail; tail.pre = head; } public int get(int key) { Node node = cache.get(key); if(node == null) return -1; node.freq++; moveToPostion(node); return node.value; } public void put(int key, int value) { if(capacity == 0) return; Node node = cache.get(key); if(node != null){ node.value = value; node.freq++; moveToPostion(node); }else{ //若是元素滿了 if(size == capacity){ //直接移除最前面的元素,由於這個節點就是頻次最小,且最久未訪問的節點 cache.remove(head.next.key); removeNode(head.next); size--; } Node newNode = new Node(key, value); //把新元素添加進來 addNode(newNode); cache.put(key,newNode); size++; } } //只要當前 node 的頻次大於等於它後邊的節點,就一直向後找, // 直到找到第一個比當前node頻次大的節點,或者tail節點,而後插入到它前面 private void moveToPostion(Node node){ Node nextNode = node.next; //先把當前元素刪除 removeNode(node); //遍歷到符合要求的節點 while (node.freq >= nextNode.freq && nextNode != tail){ nextNode = nextNode.next; } //把當前元素插入到nextNode前面 node.pre = nextNode.pre; node.next = nextNode; nextNode.pre.next = node; nextNode.pre = node; } //添加元素(頭插法),並移動到合適的位置 private void addNode(Node node){ node.pre = head; node.next = head.next; head.next.pre = node; head.next = node; moveToPostion(node); } //移除元素 private void removeNode(Node node){ node.pre.next = node.next; node.next.pre = node.pre; } class Node { int key; int value; int freq = 1; //當前節點的前一個節點 Node pre; //當前節點的後一個節點 Node next; public Node(){ } public Node(int key ,int value){ this.key = key; this.value = value; } } }
能夠看到無論是插入元素仍是刪除元素時,都不須要額外的判斷,這就是設置哨兵節點的好處。
因爲每次訪問元素的時候,都須要按必定的規則把元素放置到合適的位置,所以,元素須要從前日後一直遍歷。因此,時間複雜度 O(n)。
思路:
咱們再也不使用一條鏈表,同時維護頻次和訪問時間了。此處,換爲用 map 鍵值對來維護,用頻次做爲鍵,用當前頻次對應的一條具備前後訪問順序的鏈表來做爲值。它的結構以下:
Map<Integer, LinkedHashSet<Node>> freqMap
因爲LinkedHashSet 的 iterator迭代方法是按插入順序的,所以迭代到的第一個元素確定是當前頻次下,最久未訪問的元素。這樣的話,當緩存 cache滿的時候,直接刪除迭代到的第一個元素就能夠了。
另外 freqMap,也須要在每次訪問元素的時候,從新維護關係。從當前元素的頻次對應的雙向鏈表中移除當前元素,並加入到高頻次的鏈表中。
public class LFUCache1 { public static void main(String[] args) { LFUCache1 cache = new LFUCache1(2); cache.put(1, 1); cache.put(2, 2); // 返回 1 System.out.println(cache.get(1)); cache.put(3, 3); // 去除 key 2 // 返回 -1 (未找到key 2) System.out.println(cache.get(2)); // 返回 3 System.out.println(cache.get(3)); cache.put(4, 4); // 去除 key 1 // 返回 -1 (未找到 key 1) System.out.println(cache.get(1)); // 返回 3 System.out.println(cache.get(3)); // 返回 4 System.out.println(cache.get(4)); } //緩存 cache private Map<Integer,Node> cache; //存儲頻次和對應雙向鏈表關係的map private Map<Integer, LinkedHashSet<Node>> freqMap; private int capacity; private int size; //存儲最小頻次值 private int min; public LFUCache1(int capacity) { this.capacity = capacity; cache = new HashMap<>(); freqMap = new HashMap<>(); } public int get(int key) { Node node = cache.get(key); if(node == null) return -1; //若找到當前元素,則頻次加1 freqInc(node); return node.value; } public void put(int key, int value) { if(capacity == 0) return; Node node = cache.get(key); if(node != null){ node.value = value; freqInc(node); }else{ if(size == capacity){ Node deadNode = removeNode(); cache.remove(deadNode.key); size --; } Node newNode = new Node(key,value); cache.put(key,newNode); addNode(newNode); size++; } } //處理頻次map private void freqInc(Node node){ //從原來的頻次對應的鏈表中刪除當前node LinkedHashSet<Node> set = freqMap.get(node.freq); if(set != null) set.remove(node); //若是當前頻次是最小頻次,而且移除元素後,鏈表爲空,則更新min值 if(node.freq == min && set.size() == 0){ min = node.freq + 1; } //添加到新的頻次對應的鏈表 node.freq ++; LinkedHashSet<Node> newSet = freqMap.get(node.freq); //若是高頻次鏈表還未存在,則初始化一條 if(newSet == null){ newSet = new LinkedHashSet<Node>(); freqMap.put(node.freq,newSet); } newSet.add(node); } //添加元素,更新頻次 private void addNode(Node node){ //添加新元素,確定是須要加入到頻次爲1的鏈表中的 LinkedHashSet<Node> set = freqMap.get(1); if(set == null){ set = new LinkedHashSet<>(); freqMap.put(1,set); } set.add(node); //更新最小頻次爲1 min = 1; } //刪除頻次最小,最久未訪問的元素 private Node removeNode(){ //找到最小頻次對應的 LinkedHashSet LinkedHashSet<Node> set = freqMap.get(min); //迭代到的第一個元素就是最久未訪問的元素,移除之 Node node = set.iterator().next(); set.remove(node); //若是當前node的頻次等於最小頻次,而且移除元素以後,set爲空,則 min 加1 if(node.freq == min && set.size() == 0){ min ++; } return node; } private class Node { int key; int value; int freq = 1; public Node(int key, int value){ this.key = key; this.value = value; } public Node(){ } } }
思路:
因爲方案三用的是JDK自帶的 LinkedHashSet ,其是實現了哈希表和雙向鏈表的一個類,所以爲了減小哈希相關的計算,提升效率,咱們本身實現一條雙向鏈表來替代它。
那麼,這條雙向鏈表,就須要維護當前頻次下的全部元素的前後訪問順序。咱們採用頭插法,把新加入的元素添加到鏈表頭部,這樣的話,最久未訪問的元素就在鏈表的尾部。
一樣的,咱們也用兩個哨兵節點來表明頭尾節點,以方便鏈表的操做。
代碼以下:
public class LFUCache2 { public static void main(String[] args) { LFUCache2 cache = new LFUCache2(2); cache.put(1, 1); cache.put(2, 2); // 返回 1 System.out.println(cache.get(1)); cache.put(3, 3); // 去除 key 2 // 返回 -1 (未找到key 2) System.out.println(cache.get(2)); // 返回 3 System.out.println(cache.get(3)); cache.put(4, 4); // 去除 key 1 // 返回 -1 (未找到 key 1) System.out.println(cache.get(1)); // 返回 3 System.out.println(cache.get(3)); // 返回 4 System.out.println(cache.get(4)); } private Map<Integer,Node> cache; private Map<Integer,DoubleLinkedList> freqMap; private int capacity; private int size; private int min; public LFUCache2(int capacity){ this.capacity = capacity; cache = new HashMap<>(); freqMap = new HashMap<>(); } public int get(int key){ Node node = cache.get(key); if(node == null) return -1; freqInc(node); return node.value; } public void put(int key, int value){ if(capacity == 0) return; Node node = cache.get(key); if(node != null){ node.value = value; //更新value值 freqInc(node); }else{ //若size達到最大值,則移除頻次最小,最久未訪問的元素 if(size == capacity){ //因鏈表是頭插法,因此尾結點的前一個節點就是最久未訪問的元素 DoubleLinkedList list = freqMap.get(min); //須要移除的節點 Node deadNode = list.tail.pre; cache.remove(deadNode.key); list.removeNode(deadNode); size--; } //新建一個node,並把node放到頻次爲 1 的 list 裏面 Node newNode = new Node(key,value); DoubleLinkedList newList = freqMap.get(1); if(newList == null){ newList = new DoubleLinkedList(); freqMap.put(1,newList); } newList.addNode(newNode); cache.put(key,newNode); size++; min = 1;//此時須要把min值從新設置爲1 } } //修改頻次 private void freqInc(Node node){ //先刪除node對應的頻次list DoubleLinkedList list = freqMap.get(node.freq); if(list != null){ list.removeNode(node); } //判斷min是否等於當前node的頻次,且當前頻次的list爲空,是的話更新min值 if(min == node.freq && list.isEmpty()){ min ++; } //而後把node頻次加 1,並把它放到高頻次list node.freq ++; DoubleLinkedList newList = freqMap.get(node.freq); if(newList == null){ newList = new DoubleLinkedList(); freqMap.put(node.freq, newList); } newList.addNode(node); } private class Node { int key; int value; int freq = 1; Node pre; Node next; public Node(){ } public Node(int key, int value){ this.key = key; this.value = value; } } //自實現的一個雙向鏈表 private class DoubleLinkedList { Node head; Node tail; // 設置兩個哨兵節點,做爲頭、尾節點便於插入和刪除操做 public DoubleLinkedList(){ head = new Node(); tail = new Node(); head.next = tail; tail.pre = head; } //採用頭插法,每次都插入到鏈表的最前面,即 head 節點後邊 public void addNode(Node node){ node.pre = head; node.next = head.next; //注意先把head的後節點的前節點設置爲node head.next.pre = node; head.next = node; } //刪除元素 public void removeNode(Node node){ node.pre.next = node.next; node.next.pre = node.pre; } //判斷是否爲空,便是否存在除了哨兵節點外的有效節點 public boolean isEmpty(){ //判斷頭結點的下一個節點是不是尾結點,是的話即爲空 return head.next == tail; } } }
思路:
能夠發現方案三和方案四,都是用 freqmap 來存儲頻次和它對應的鏈表之間的關係,它自己也是一個哈希表。此次,咱們徹底用本身實現的雙向鏈表來代替 freqMap,進一步提升效率。
可是,結構有些複雜,它是一個雙向鏈表中,每一個元素又是雙向鏈表。爲了便於理解,我把它的結構做圖以下:(爲了方便,分別叫作外層鏈表,內層鏈表)
咱們把總體當作一個由 DoubleLinkedList組成的雙向鏈表,而後,每個 DoubleLinkedList 對象中又是一個由 Node 組成的雙向鏈表。像極了 HashMap 數組加鏈表的形式。
可是,咱們這裏沒有數組,也就不存在哈希碰撞的問題。而且都是雙向鏈表,都有哨兵存在,便於靈活的從鏈表頭部或者尾部開始操做元素。
這裏,firstLinkedList 和 lastLinkedList 分別表明外層鏈表的頭尾結點。鏈表中的元素 DoubleLinkedList 有一個字段 freq 記錄了頻次,而且按照前大後小的順序組成外層鏈表,即圖中的 DoubleLinkedList1.freq 大於它後面的 DoubleLinkedList2.freq。
每當有新頻次的 DoubleLinkedList 須要添加進來的時候,直接插入到 lastLinkedList 這個哨兵前面,所以 lastLinkedList.pre 就是一個最小頻次的內部鏈表。
內部鏈表中是由 Node組成的雙向鏈表,也有兩個哨兵表明頭尾節點,並採用頭插法。其實,能夠看到內部鏈表和方案四,圖中所示的雙向鏈表結構是同樣的,不用多說了。
這樣的話,咱們就能夠找到頻次最小,而且最久未訪問的元素,即
//頻次最小,最久未訪問的元素,cache滿時須要刪除 lastLinkedList.pre.tail.pre
因而,代碼就好理解了:
public class LFUCache3 { public static void main(String[] args) { LFUCache3 cache = new LFUCache3(2); cache.put(1, 1); cache.put(2, 2); // 返回 1 System.out.println(cache.get(1)); cache.put(3, 3); // 去除 key 2 // 返回 -1 (未找到key 2) System.out.println(cache.get(2)); // 返回 3 System.out.println(cache.get(3)); cache.put(4, 4); // 去除 key 1 // 返回 -1 (未找到 key 1) System.out.println(cache.get(1)); // 返回 3 System.out.println(cache.get(3)); // 返回 4 System.out.println(cache.get(4)); } Map<Integer,Node> cache; /** * 這兩個表明的是以 DoubleLinkedList 鏈接成的雙向鏈表的頭尾節點, * 且爲哨兵節點。每一個list中,又包含一個由 node 組成的一個雙向鏈表。 * 最外層雙向鏈表中,freq 頻次較大的 list 在前面,較小的 list 在後面 */ DoubleLinkedList firstLinkedList, lastLinkedList; int capacity; int size; public LFUCache3(int capacity){ this.capacity = capacity; cache = new HashMap<>(); //初始化外層鏈表的頭尾節點,做爲哨兵節點 firstLinkedList = new DoubleLinkedList(); lastLinkedList = new DoubleLinkedList(); firstLinkedList.next = lastLinkedList; lastLinkedList.pre = firstLinkedList; } //存儲具體鍵值對信息的node private class Node { int key; int value; int freq = 1; Node pre; Node next; DoubleLinkedList doubleLinkedList; public Node(){ } public Node(int key, int value){ this.key = key; this.value = value; } } public int get(int key){ Node node = cache.get(key); if(node == null) return -1; freqInc(node); return node.value; } public void put(int key, int value){ if(capacity == 0) return; Node node = cache.get(key); if(node != null){ node.value = value; freqInc(node); }else{ if(size == capacity){ /** * 若是滿了,則須要把頻次最小的,且最久未訪問的節點刪除 * 因爲list組成的鏈表頻次從前日後依次減少,故最小的頻次list是 lastLinkedList.pre * list中的雙向node鏈表採用的是頭插法,所以最久未訪問的元素是 lastLinkedList.pre.tail.pre */ //最小頻次list DoubleLinkedList list = lastLinkedList.pre; //最久未訪問的元素,須要刪除 Node deadNode = list.tail.pre; cache.remove(deadNode.key); list.removeNode(deadNode); size--; //若是刪除deadNode以後,此list中的雙向鏈表空了,則刪除此list if(list.isEmpty()){ removeDoubleLinkedList(list); } } //沒有滿,則新建一個node Node newNode = new Node(key, value); cache.put(key,newNode); //判斷頻次爲1的list是否存在,不存在則新建 DoubleLinkedList list = lastLinkedList.pre; if(list.freq != 1){ DoubleLinkedList newList = new DoubleLinkedList(1); addDoubleLinkedList(newList,list); newList.addNode(newNode); }else{ list.addNode(newNode); } size++; } } //修改頻次 private void freqInc(Node node){ //從當前頻次的list中移除當前 node DoubleLinkedList list = node.doubleLinkedList; if(list != null){ list.removeNode(node); } //若是當前list中的雙向node鏈表空,則刪除此list if(list.isEmpty()){ removeDoubleLinkedList(list); } //當前node頻次加1 node.freq++; //找到當前list前面的list,並把當前node加入進去 DoubleLinkedList preList = list.pre; //若是前面的list不存在,則新建一個,並插入到由list組成的雙向鏈表中 //前list的頻次不等於當前node頻次,則說明不存在 if(preList.freq != node.freq){ DoubleLinkedList newList = new DoubleLinkedList(node.freq); addDoubleLinkedList(newList,preList); newList.addNode(node); }else{ preList.addNode(node); } } //從外層雙向鏈表中刪除當前list節點 public void removeDoubleLinkedList(DoubleLinkedList list){ list.pre.next = list.next; list.next.pre = list.pre; } //知道了它的前節點,便可把新的list節點插入到其後面 public void addDoubleLinkedList(DoubleLinkedList newList, DoubleLinkedList preList){ newList.pre = preList; newList.next = preList.next; preList.next.pre = newList; preList.next = newList; } //維護一個雙向DoubleLinkedList鏈表 + 雙向Node鏈表的結構 private class DoubleLinkedList { //當前list中的雙向Node鏈表全部頻次都相同 int freq; //當前list中的雙向Node鏈表的頭結點 Node head; //當前list中的雙向Node鏈表的尾結點 Node tail; //當前list的前一個list DoubleLinkedList pre; //當前list的後一個list DoubleLinkedList next; public DoubleLinkedList(){ //初始化內部鏈表的頭尾節點,並做爲哨兵節點 head = new Node(); tail = new Node(); head.next = tail; tail.pre = head; } public DoubleLinkedList(int freq){ head = new Node(); tail = new Node(); head.next = tail; tail.pre = head; this.freq = freq; } //刪除當前list中的某個node節點 public void removeNode(Node node){ node.pre.next = node.next; node.next.pre = node.pre; } //頭插法將新的node插入到當前list,並在新node中記錄當前list的引用 public void addNode(Node node){ node.pre = head; node.next = head.next; head.next.pre = node; head.next = node; node.doubleLinkedList = this; } //當前list中的雙向node鏈表是否存在有效節點 public boolean isEmpty(){ //只有頭尾哨兵節點,則說明爲空 return head.next == tail; } } }
因爲,此方案全是鏈表的增刪操做,所以時間複雜度可到 O(1)。
終於總結完了,其實,感受思想搞明白了,代碼實現起來就相對容易一些。可是,仍是須要多寫,多實踐。過段時間再來回顧一下~