iOS算法刷题之数据结构_链表

数据结构类的题一般是考察对算法的设计能力,还有对基础数据结构的能力能力,如何根据题意才能设计出最优的结构。 也是考察对一些数据结构设计的理解,比如说Java直接就支持哈希链表(LinkedHashMap)的结构,但有些语言是没有的,如果你是Java开发一般会问哈希链表是如何设计的;如果不是Java开发的话可能会让你从头设计一个LRU结构。

常见的数据结构也有很多,比如:LRU、LFU、大顶堆,二叉堆、最大频率栈、前缀树、单调栈等等。

以LRU进行举例: 146. LRU 缓存 LRU算法就是一种缓存淘汰策略,即(最近最少使用)缓存。iOS中的YYCache就是使用LUR策略实现的。Cache的容量是有限的,当Cache的空间被占满后,如果再次发生缓存失效,就必须选择哪个最长时间未被使用的块替换。这种方法比较好地反映了局部性规律。

这个题中有两个方法,一个put一个get。要让put和get方法的时间复杂度为O(1),可以总结出有以下必要条件: 1,cache元素必须有序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素腾出位置来。 2,要在cache中快速找某个key是否已存在并得到对应的val 3,每次访问cache中的某个key,需要将这个元素变为最近使用的,也就是说cache要支持在任意位置快速插入和删除元素。 哈希表查找快,链表有顺序插入删除快,但查找慢。所以结合起来形成一种新的数据结构:哈希链表 LinkedHashMap。

iOS算法刷题之数据结构_链表_02

对于一个Cache的操作无非三种:插入、替换、查找。

  • 插入:当Cache未满时,新的数据项只需插到双链表头部即可。
  • 替换:当Cache已满时,将新的数据项插到双链表头部,并删除双链表的尾结点即可。
  • 查找:每次数据项被查询到时,都将此数据项移动到链表的头部。

具体代码实现如下:

class DLikedNode {
    var val: Int
    var next: DLikedNode?
    var pre: DLikedNode?
    var key: Int?
    init(val: Int, key: Int? = nil, next: DLikedNode? = nil, pre: DLikedNode? = nil) {
        self.val = val
        self.key = key
        self.next = next
        self.pre = pre
    }
}
class LRUCache {
    // 容量
    var capacity = 0
    var count = 0
    let first = DLikedNode(val: -1)
    let last = DLikedNode(val: -1)
    var hash = [Int: DLikedNode]()
    
    init(_ capacity: Int) {
        self.capacity = capacity
        first.next = last
        first.pre = nil
        
        last.pre = first
        last.next = nil
    }
    // 挪到第一位
    func moveToHead(node: DLikedNode) {
        let pre = node.pre
        let next = node.next
        
        pre?.next = next
        next?.pre = pre
        
        node.next = first.next
        first.next?.pre = node
        first.next = node
        node.pre = first
    }
    
    func removeLastNode() {
        let node = last.pre
        node?.pre?.next = last
        last.pre = node?.pre
        count -= 1
        hash[node!.key!] = nil
    }
    
    func addNewNode(key: Int, node: DLikedNode) {
        moveToHead(node: node)
        count += 1
        hash[key] = node
    }
    
    func get(_ key: Int) -> Int {
        let node = hash[key]
        if let n = node {
            moveToHead(node: n)
            return n.val
        }else {
            return -1
        }
    }
    
    func put(_ key: Int, _ value: Int) {
        let node = hash[key]
        if let n = node {
            // 挪到第一位
            n.val = value
            moveToHead(node: n)
        }else {
            let newNode = DLikedNode(val: value, key: key)
            // 判断容量
            if count >= capacity {
                // 移除最后一个节点
                removeLastNode()
                // 新节点插入头节点后
                addNewNode(key: key, node: newNode)
            }else {
                // 新节点插入头节点后
                addNewNode(key: key, node: newNode)
            }
        }
    }
}

至此,以上就是LRU算法的简单封装,调用函数可以避免直接操作链表和哈希表。

其他经典的设计类的算法题还有:460. LFU缓存380. O(1)时间插入、删除和获取随机元素295. 数据流的中位数895. 最大频率栈