1. 程式人生 > 其它 >LeetCode騰訊50題-Day12-146/148/155

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();
 */