LeetCode騰訊50題-Day12-146/148/155
技術標籤:LeetCode騰訊50題資料結構資料結構演算法連結串列棧
LeetCode50題(17天)-Day12
146 LRU快取機制
- 題號:146
- 難度:中等
- https://leetcode-cn.com/problems/lru-cache/
運用你所掌握的資料結構,設計和實現一個 LRU (最近最少使用) 快取機制。它應該支援以下操作: 獲取資料 get 和 寫入資料 put 。
獲取資料 get(key)
- 如果金鑰 (key) 存在於快取中,則獲取金鑰的值(總是正數),否則返回 -1。
寫入資料 put(key, value)
- 如果金鑰不存在,則寫入其資料值。當快取容量達到上限時,它應該在寫入新資料之前刪除最近最少使用的資料值,從而為新的資料值留出空間。
進階:
你是否可以在 O(1) 時間複雜度內完成這兩種操作?
示例:
LRUCache cache = new LRUCache( 2 /* 快取容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 該操作會使得金鑰 2 作廢
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 該操作會使得金鑰 1 作廢
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
實現
計算機的快取容量有限,如果快取滿了就要刪除一些內容,給新內容騰位置。但問題是,刪除哪些內容呢?我們肯定希望刪掉哪些沒什麼用的快取,而把有用的資料繼續留在快取裡,方便之後繼續使用。那麼,什麼樣的資料,我們判定為「有用的」的資料呢?
LRU 快取淘汰演算法就是一種常用策略。LRU 的全稱是 Least Recently Used,也就是說我們認為最近使用過的資料應該是是「有用的」,很久都沒用過的資料應該是無用的,記憶體滿了就優先刪那些很久沒用過的資料。
第一種:利用單鏈表的方式:
- 狀態:通過
- 18 / 18 個通過測試用例
- 執行用時: 868 ms, 在所有 C# 提交中擊敗了 6.25% 的使用者
- 記憶體消耗: 47.8 MB, 在所有 C# 提交中擊敗了 26.67% 的使用者
public class LRUCache
{
private readonly int _length;
private readonly List<KeyValuePair<int, int>> _lst;
public LRUCache(int capacity)
{
_length = capacity;
_lst = new List<KeyValuePair<int, int>>();
}
private int GetIndex(int key)
{
for (int i=0,len=_lst.Count;i<len;i++)
{
if (_lst[i].Key == key)
{
return i;
}
}
return -1;
}
public int Get(int key)
{
int index = GetIndex(key);
if (index!=-1)
{
int val = _lst[index].Value;
_lst.RemoveAt(index);
_lst.Add(new KeyValuePair<int, int>(key, val));
return val;
}
return -1;
}
public void Put(int key, int value)
{
int index = GetIndex(key);
if (index!=-1)
{
_lst.RemoveAt(index);
}
else if (_lst.Count == _length)
{
_lst.RemoveAt(0);
}
_lst.Add(new KeyValuePair<int, int>(key, value));
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.Get(key);
* obj.Put(key,value);
*/
第二種:利用 字典 + 列表 的方式:
把字典當作一個儲存容器,由於字典是無序的,即 dict
內部存放的順序和 key
放入的順序是沒有關係的,所以需要一個 list
來輔助排序。
C# 語言
- 狀態:通過
- 18 / 18 個通過測試用例
- 執行用時: 392 ms, 在所有 C# 提交中擊敗了 76.56% 的使用者
- 記憶體消耗: 47.9 MB, 在所有 C# 提交中擊敗了 20.00% 的使用者
public class LRUCache
{
private readonly List<int> _keys;
private readonly Dictionary<int, int> _dict;
public LRUCache(int capacity)
{
_keys = new List<int>(capacity);
_dict = new Dictionary<int, int>(capacity);
}
public int Get(int key)
{
if (_dict.ContainsKey(key))
{
_keys.Remove(key);
_keys.Add(key);
return _dict[key];
}
return -1;
}
public void Put(int key, int value)
{
if (_dict.ContainsKey(key))
{
_dict.Remove(key);
_keys.Remove(key);
}
else if (_keys.Count == _keys.Capacity)
{
_dict.Remove(_keys[0]);
_keys.RemoveAt(0);
}
_keys.Add(key);
_dict.Add(key, value);
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.Get(key);
* obj.Put(key,value);
*/
Python 語言
- 執行結果:通過
- 執行用時:628 ms, 在所有 Python3 提交中擊敗了 12.15% 的使用者
- 記憶體消耗:22 MB, 在所有 Python3 提交中擊敗了 65.38% 的使用者
class LRUCache:
def __init__(self, capacity: int):
self._capacity = capacity
self._dict = dict()
self._keys = list()
def get(self, key: int) -> int:
if key in self._dict:
self._keys.remove(key)
self._keys.append(key)
return self._dict[key]
return -1
def put(self, key: int, value: int) -> None:
if key in self._dict:
self._dict.pop(key)
self._keys.remove(key)
elif len(self._keys) == self._capacity:
self._dict.pop(self._keys[0])
self._keys.remove(self._keys[0])
self._keys.append(key)
self._dict[key] = value
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
148 排序連結串列
- 題號:148
- 難度:中等
- https://leetcode-cn.com/problems/sort-list/
在 O(n log n) 時間複雜度和常數級空間複雜度下,對連結串列進行排序。
示例 1:
輸入: 4->2->1->3
輸出: 1->2->3->4
示例 2:
輸入: -1->5->3->4->0
輸出: -1->0->3->4->5
實現
思路:模仿並歸排序的思路,典型的回溯演算法。
如果待排的元素儲存在陣列中,我們可以用並歸排序。而這些元素儲存在連結串列中,我們無法直接利用並歸排序,只能借鑑並歸排序的思想對演算法進行修改。
並歸排序的思想是將待排序列進行分組,直到包含一個元素為止,然後回溯合併兩個有序序列,最後得到排序序列。
對於連結串列我們可以遞迴地將當前連結串列分為兩段,然後merge,分兩段的方法是使用雙指標法,p1
指標每次走兩步,p2
指標每次走一步,直到p1
走到末尾,這時p2
所在位置就是中間位置,這樣就分成了兩段。
C# 語言
- 狀態:通過
- 16 / 16 個通過測試用例
- 執行用時: 124 ms, 在所有 C# 提交中擊敗了 100.00% 的使用者
- 記憶體消耗: 29 MB, 在所有 C# 提交中擊敗了 25.00% 的使用者
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) { val = x; }
* }
*/
public class Solution
{
public ListNode SortList(ListNode head)
{
if (head == null)
return null;
return MergeSort(head);
}
private ListNode MergeSort(ListNode node)
{
if (node.next == null)
{
return node;
}
ListNode p1 = node;
ListNode p2 = node;
ListNode cut = null;
while (p1 != null && p1.next != null)
{
cut = p2;
p2 = p2.next;
p1 = p1.next.next;
}
cut.next = null;
ListNode l1 = MergeSort(node);
ListNode l2 = MergeSort(p2);
return MergeTwoLists(l1, l2);
}
private ListNode MergeTwoLists(ListNode l1, ListNode l2)
{
ListNode pHead = new ListNode(-1);
ListNode temp = pHead;
while (l1 != null && l2 != null)
{
if (l1.val < l2.val)
{
temp.next = l1;
l1 = l1.next;
}
else
{
temp.next = l2;
l2 = l2.next;
}
temp = temp.next;
}
if (l1 != null)
temp.next = l1;
if (l2 != null)
temp.next = l2;
return pHead.next;
}
}
Python 語言
- 執行結果:通過
- 執行用時:216 ms, 在所有 Python3 提交中擊敗了 75.99% 的使用者
- 記憶體消耗:20.7 MB, 在所有 Python3 提交中擊敗了 28.57% 的使用者
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def sortList(self, head: ListNode) -> ListNode:
if head is None:
return head
return self.mergeSort(head)
def mergeSort(self, node: ListNode) -> ListNode:
if node.next is None:
return node
p1 = node
p2 = node
cute = None
while p1 is not None and p1.next is not None:
cute = p2
p2 = p2.next
p1 = p1.next.next
cute.next = None
l1 = self.mergeSort(node)
l2 = self.mergeSort(p2)
return self.mergeTwoLists(l1, l2)
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
pHead = ListNode(-1)
temp = pHead
while l1 is not None and l2 is not None:
if l1.val < l2.val:
temp.next = l1
l1 = l1.next
else:
temp.next = l2
l2 = l2.next
temp = temp.next
if l1 is not None:
temp.next = l1
if l2 is not None:
temp.next = l2
return pHead.next
155 最小棧
- 題號:155
- 難度:簡單
- https://leetcode-cn.com/problems/min-stack/
設計一個支援 push,pop,top 操作,並能在常數時間內檢索到最小元素的棧。
- push(x) – 將元素 x 推入棧中。
- pop() – 刪除棧頂的元素。
- top() – 獲取棧頂元素。
- getMin() – 檢索棧中的最小元素。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
實現
第一種:利用單鏈表的方式
- 狀態:通過
- 18 / 18 個通過測試用例
- 執行用時: 776 ms, 在所有 C# 提交中擊敗了 22.32% 的使用者
- 記憶體消耗: 33.8 MB, 在所有 C# 提交中擊敗了10.60% 的使用者
public class MinStack
{
/** initialize your data structure here. */
private readonly IList<int> _lst;
public MinStack()
{
_lst = new List<int>();
}
public void Push(int x)
{
_lst.Add(x);
}
public void Pop()
{
_lst.RemoveAt(_lst.Count - 1);
}
public int Top()
{
return _lst[_lst.Count - 1];
}
public int GetMin()
{
return _lst.Min();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.Push(x);
* obj.Pop();
* int param_3 = obj.Top();
* int param_4 = obj.GetMin();
*/
第二種:利用輔助棧的方式
- 狀態:通過
- 18 / 18 個通過測試用例
- 執行用時: 192 ms, 在所有 C# 提交中擊敗了 96.43% 的使用者
- 記憶體消耗: 33.5 MB, 在所有 C# 提交中擊敗了 13.63% 的使用者
public class MinStack
{
/** initialize your data structure here. */
private readonly Stack<int> _stack;
private readonly Stack<int> _stackMin;
public MinStack()
{
_stack = new Stack<int>();
_stackMin = new Stack<int>();
}
public void Push(int x)
{
_stack.Push(x);
if (_stackMin.Count == 0 || _stackMin.Peek() >= x)
{
_stackMin.Push(x);
}
}
public void Pop()
{
int x = _stack.Pop();
if (_stackMin.Peek() == x)
{
_stackMin.Pop();
}
}
public int Top()
{
return _stack.Peek();
}
public int GetMin()
{
return _stackMin.Peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.Push(x);
* obj.Pop();
* int param_3 = obj.Top();
* int param_4 = obj.GetMin();
*/