LeetCode 146, 4

115 阅读2分钟

LeetCode 146 LRU Cache

链接:leetcode.com/problems/lr…

方法1:双向链表

时间复杂度:O(1),put和get都是

空间复杂度:O(n)

想法:Least Recently Used. 这个题在现在应该是属于人人都会做的题了,就是复习一下熟练熟练。双向链表,这样的话从中间删一个节点或者挪到列表的某个地方都比较容易。Node类里面要放key和val,HashMap是从key映射到Node。我之前有一次写的时候Node只放了val没有放key,这样会导致在evict的时候,也就是说链表节点数已经达到限制了,这时候又打算插入一个新的进来,这个时候要把链表最前面那个节点给删了,也要从HashMap里面删掉,那么这时候需要这个节点的key,因此key也得存在Node类里面。

代码:

class Node {
    public int key, val;
    public Node next, prev;
    
    public Node(int key, int val) {
        this.key = key;
        this.val = val;
        this.next = null;
        this.prev = null;
    }
}

class LRUCache {
    
    private int capacity;
    private Node head, tail;
    private Map<Integer, Node> map;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.head = new Node(-1, -1);
        this.tail = new Node(-1, -1);
        this.head.next = this.tail;
        this.tail.prev = this.head;
        this.map = new HashMap<>();
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        
        Node node = map.get(key);
        node.prev.next = node.next;
        node.next.prev = node.prev;
        moveToTail(node);
        return node.val;
    }
    
    private void moveToTail(Node node) {
        Node prev = tail.prev;
        prev.next = node;
        node.prev = prev;
        node.next = tail;
        tail.prev = node;
    }
    
    public void put(int key, int value) {
        if (get(key) != -1) {
            map.get(key).val = value;
            return;
        }
        
        if (map.size() == capacity) {
            map.remove(head.next.key);
            head.next = head.next.next;
            head.next.prev = head;
        }
        
        Node insert = new Node(key, value);
        map.put(key, insert);
        moveToTail(insert);
    }
}

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

方法2:单向链表

时间复杂度:O(1),put和get都是

空间复杂度:O(n)

想法:这个是我参考自九章的做法www.jiuzhang.com/problem/lru… ,就是我之前以为单向链表是不能写的,后来发现居然可以。但总归感觉思路没有上面那种做法这么直观,可能也比较容易写错。不确定面试的时候会不会有人就让你按单向列表写(见过很多没必要又无用的要求)。但是不管怎么样,想法是HashMap记录的是key到含有这个key的节点的前一个节点。这样的话在删节点的时候,我们就知道key对应的那个节点是什么,比方说叫prev,那key对应的节点就是prev.next。这样就让删除操作变得容易一点。然后单向链表的话就用一个dummy一直不会变,相当于是链表的头之前的那个元素,然后插入操作,因为是要放到链表的最后面,因此还得有个tail变量代表链表的最后一个节点。

代码:

class Node {
    public int key, value;
    public Node next;
    
    public Node(int key, int value) {
        this.key = key;
        this.value = value;
        this.next = null;
    }
}

class LRUCache {
    
    private Map<Integer, Node> keyToPrev;
    private int capacity, size;
    private Node dummy, tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        this.dummy = new Node(-1, -1);
        this.tail = this.dummy;
        this.keyToPrev = new HashMap<>();
    }
    
    public int get(int key) {
        if (!keyToPrev.containsKey(key)) {
            return -1;
        }
        
        moveToTail(key);
        return tail.value;
    }
    
    private void moveToTail(int key) {
        Node prev = keyToPrev.get(key);
        Node cur = prev.next;
        if (cur == tail) {
            return;
        }
        
        prev.next = prev.next.next;
        if (prev.next != null) {
            keyToPrev.put(prev.next.key, prev);
        }
        
        tail.next = cur;
        cur.next = null;
        keyToPrev.put(key, tail);
        tail = cur;
    }
    
    public void put(int key, int value) {
        if (get(key) != -1) {
            Node prev = keyToPrev.get(key);
            prev.next.value = value;
            return;
        }
        
        if (size == capacity) {
            keyToPrev.remove(dummy.next.key);
            dummy.next.key = key;
            dummy.next.value = value;
            keyToPrev.put(key, dummy);
            moveToTail(key);
            return;
        }
        
        size++;
        Node insert = new Node(key, value);
        tail.next = insert;
        insert.next = null;
        keyToPrev.put(key, tail);
        tail = insert;
    }
}

LeetCode 4 Median of Two Sorted Arrays

链接:leetcode.com/problems/me…

方法:二分

时间复杂度:O(log(min(m, n)))

空间复杂度:O(1)

想法:多年前一开始做没做出来,看到了youtube上面的一个非常强的讲解www.youtube.com/watch?v=LPF… ,我觉得我不管怎么写应该都没法超过这个讲解了。这个题的二分也是我觉得LeetCode里面基本上最强的二分题。

想法就是,假设两个数组分别叫nums1和nums2,我们在nums1中找一个分界点px,把nums1分成两部分,这样的话对nums1来说,左边那部分严格<=右边那部分。然后,假设说第一个数组长度是n1,第二个数组长度是n2,我们用(n1 + n2 + 1) / 2 - px求出一个分界点py,这样的话nums1左边的部分和nums2左边的部分加起来,与右边部分加起来相比,要么元素个数相等(n1+n2是偶数),要么左边部分只多一个元素(n1+n2是奇数)。如果说所有左边的元素最右边那个分别叫maxLeftX/maxLeftY,右边元素当中,最左边那两个分别叫minRightX/minRightY, 如果所有左边元素都<=所有右边的元素,那么会有maxLeftX <= minRightY && maxLeftY <= minRightX. 否则的话如果maxLeftX > minRightY,说明左边元素集合在nums1当中拿的元素过多了,应当使用二分来减小搜索px的范围的右边界。

如果出现假设说某个分割点,左边元素集合在nums1当中不取任何一个元素,那么怎么比较呢?这时候应该认为maxLeftX是负无穷,nums2同理。如果右边元素不取任何一个元素,那应该认为minRightX或minRightY是正无穷。

最后,二分所分的不是下标,而是一个分割点,px代表的是,nums1的元素中在这个分割点的左边的元素有多少个,这样才能比较容易地判断左边元素集合和右边元素集合各拿走了多少个元素。

代码:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n1 = nums1.length, n2 = nums2.length;
        if (n1 > n2) return findMedianSortedArrays(nums2, nums1);
        
        int left = 0, right = n1;
        while (left <= right) {
            int px = left + (right - left) / 2;
            int py = (n1 + n2 + 1) / 2 - px;
            
            int maxLeftX = px == 0 ? Integer.MIN_VALUE : nums1[px - 1];
            int minRightX = px == n1 ? Integer.MAX_VALUE : nums1[px];
            int maxLeftY = py == 0 ? Integer.MIN_VALUE : nums2[py - 1];
            int minRightY = py == n2 ? Integer.MAX_VALUE : nums2[py];
            
            if (maxLeftX <= minRightY && maxLeftY <= minRightX) {
                if ((n1 + n2) % 2 == 0) {
                    return (Math.max(maxLeftX, maxLeftY) + Math.min(minRightX, minRightY)) * 0.5;
                }
                return (double) Math.max(maxLeftX, maxLeftY);
            }
            else if (maxLeftX > minRightY) {
                right = px - 1;
            }
            else {
                left = px + 1;
            }
        }
        
        return 0.0;
    }
}