算法细节系列(30):接口设计
2017-06-06 20:56
483 查看
算法细节系列(30):接口设计
详细代码可以fork下Github上leetcode项目,不定期更新。题目摘自leetcode:
Leetcode 380. Insert Delete GetRandom O(1)
Leetcode 381. Insert Delete GetRandom O(1) - Duplicates allowed
Leetcode 432. All O`one Data Structure
Leetcode 355. Design Twitter
Leetcode 460. LFU Cache
Leetcode 146. LRU Cache
Leetcode 380. Insert Delete GetRandom O(1)
数据结构题,比较有趣,传统我所熟知的remove操作,除了链表,数组版本的remove都需要O(n)的操作。数组版本的remove就不能O(1)操作了么?传统的remove操作:
找到要删除的的元素:花费O(n) 删除: 把当前位置的后续元素都向前移动一格。 这种删除比较费时,在维护有序数组时,只能用这种方法。 但此题没有必要维护有序性,所以还有一种取巧的办法,把当前元素和末尾元素进行交换,直接删除末尾元素。 删除可以在O(1)内完成,但查找却费时,所以remove操作的关键在于如何快速查找元素。 O(1)时间内完成查找的有Map,所以我们可以用一个Map来维护当前元素和它对应下标的关系。 这样在remove时可以快速定位所在下标。
代码如下:
public class RandomizedSet { List<Integer> nums; Map<Integer, Integer> cache; Random random = new Random(); /** Initialize your data structure here. */ public RandomizedSet() { nums = new ArrayList<>(); cache = new HashMap<>(); } /** * Inserts a value to the set. Returns true if the set did not already * contain the specified element. */ public boolean insert(int val) { if (cache.containsKey(val)) return false; int idx = cache.size(); nums.add(idx,val); cache.put(val, idx); return true; } /** * Removes a value from the set. Returns true if the set contained the * specified element. */ public boolean remove(int val) { if (!cache.containsKey(val)) return false; int idx = cache.get(val); if (idx != cache.size()-1){ cache.put(nums.get(cache.size()-1), idx); nums.set(idx, nums.get(cache.size()-1)); } cache.remove(val); nums.remove(nums.size()-1); return true; } /** Get a random element from the set. */ public int getRandom() { return nums.get(random.nextInt(cache.size())); } public static void main(String[] args) { RandomizedSet rs = new RandomizedSet(); rs.insert(0); rs.insert(1); rs.remove(0); rs.insert(2); rs.remove(1); System.out.println(rs.getRandom()); } }
Leetcode 381. Insert Delete GetRandom O(1) - Duplicates allowed
和第一题的思路大同小异,无非现在要记录重复元素的下标,所以在Map中,需要做点修改,把它改成Set来记录所有重复元素的下标。代码如下:
public class RandomizedCollection { List<Integer> nums; Map<Integer, Set<Integer>> map; Random random = new Random(); /** Initialize your data structure here. */ public RandomizedCollection() { nums = new ArrayList<>(); map = new HashMap<>(); } /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */ public boolean insert(int val) { // dup or not dup insert boolean dup = map.containsKey(val); nums.add(val); map.computeIfAbsent(val, a -> new LinkedHashSet<>()).add(nums.size()-1); return !dup; } /** Removes a value from the collection. Returns true if the collection contained the specified element. */ public boolean remove(int val) { if (!map.containsKey(val)) return false; int loc = map.get(val).iterator().next(); map.get(val).remove(loc); if (loc < nums.size()-1){ int last = nums.get(nums.size()-1); nums.set(loc, last); map.get(last).remove(nums.size()-1); map.get(last).add(loc); } nums.remove(nums.size()-1); if (map.get(val).isEmpty()) map.remove(val); return true; } /** Get a random element from the collection. */ public int getRandom() { return nums.get(random.nextInt(nums.size())); } }
Leetcode 432. All O`one Data Structure
思路:这道题的关键在于时刻维护最小值和最大值,传统的想法,每当加入和删除一个key时,对其计数,此时在已有的集合中找到最大key和最小key。
集合的表达方式可以有数组or链表。先来看看数组的做法吧:
假设该集合在动态插入和删除时能够维持有序操作,如:
map: "A": 4, "B": 4, "C": 2, "D": 1 array: D C A B 要做到插入过程中的有序性不难,此时,假设我们有: inc("C"); inc("C"); inc("C"); 三次操作,此时C的计数值为5 array: D C A B -> D A B C 与此同时,要让array中的C浮动到最右边,而在D和A之间,C消失了。 能够高效实现这种操作的为链表,不管是INC操作还是DEC操作,都可能改变计数值,如果把计数值当作浮动比较的val,那么链表能够很好的让结点上下浮动。且插入和删除的时间复杂度仅为O(1)。 定义: class Bucket{ int count; Set<String> keySet; Bucket prev; Bucket next; public Bucket(int cnt){ count = cnt; keySet = new HashSet<>(); } } 相同计数值的key无区别,所以可以用一个keySet来装。所以: D C A B 就可以看成 D C {A,B},此时C的浮动将变得更简单。 count是这个结构体的唯一标识,表示计数值为count的元素集合。如: 4 = {A,B} 2 = {C} 1 = {D} 整体的设计结构如下: head --- ValueNode1 ---- ValueNode2 ---- ... ---- ValueNodeN --- tail | | | first first first | | | KeyNodeA KeyNodeE KeyNodeG | | | KeyNodeB KeyNodeF KeyNodeH | | KeyNodeC KeyNodeI | KeyNodeD private Bucket head; private Bucket tail; private Map<String, Integer> keyCountMap; private Map<Integer, Bucket> countBucketMap; class Bucket{ int count; Set<String> keySet; Bucket prev; Bucket next; public Bucket(int cnt){ count = cnt; keySet = new HashSet<>(); } } /** Initialize your data structure here. */ public AllOne() { head = new Bucket(Integer.MIN_VALUE); tail = new Bucket(Integer.MAX_VALUE); head.next = tail; tail.prev = head; keyCountMap = new HashMap<>(); countBucketMap = new HashMap<>(); } } countBucketMap存在的意义巨大,它相当于让双向链表成了一个快速查找的数组形式,因为countBucketMap记录了每个计数值在链表中的位置。
整体代码如下:
public class AllOne { private Bucket head; private Bucket tail; private Map<String, Integer> keyCountMap; private Map<Integer, Bucket> countBucketMap; class Bucket{ int count; Set<String> keySet; Bucket prev; Bucket next; public Bucket(int cnt){ count = cnt; keySet = new HashSet<>(); } } /** Initialize your data structure here. */ public AllOne() { head = new Bucket(Integer.MIN_VALUE); tail = new Bucket(Integer.MAX_VALUE); head.next = tail; tail.prev = head; keyCountMap = new HashMap<>(); countBucketMap = new HashMap<>(); } /** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */ public void inc(String key) { if (keyCountMap.containsKey(key)){ changeKey(key, 1); } else{ keyCountMap.put(key, 1); if (head.next.count != 1) addBucketAfter(new Bucket(1), head); head.next.keySet.add(key); countBucketMap.put(1, head.next); } } private void changeKey(String key, int step){ int val = keyCountMap.get(key); keyCountMap.put(key, val + step); Bucket cur = countBucketMap.get(val); Bucket newNode; if (countBucketMap.containsKey(val + step)){ newNode = countBucketMap.get(val + step); }else{ newNode = new Bucket(val + step); countBucketMap.put(val + step, newNode); addBucketAfter(newNode, step == 1 ? cur : cur.prev); } newNode.keySet.add(key); removeKeyFromBucket(cur,key); } private void removeKeyFromBucket(Bucket cur, String key){ cur.keySet.remove(key); if (cur.keySet.isEmpty()){ countBucketMap.remove(cur.count); removeBucketFromList(cur); } } private void removeBucketFromList(Bucket bucket){ bucket.prev.next = bucket.next; bucket.next.prev = bucket.prev; bucket.next = null; bucket.prev = null; } private void addBucketAfter(Bucket node, Bucket head){ head.next.prev = node; node.prev = head; node.next = head.next; head.next = node; } /** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */ public void dec(String key) { if (keyCountMap.containsKey(key)){ int val = keyCountMap.get(key); if (val == 1){ keyCountMap.remove(key); removeKeyFromBucket(countBucketMap.get(val), key); } else{ changeKey(key, -1); } } } /** Returns one of the keys with maximal value. */ public String getMaxKey() { return tail.prev == head ? "" : (String) tail.prev.keySet.iterator().next(); } /** Returns one of the keys with Minimal value. */ public String getMinKey() { return head.next == tail ? "" : (String) head.next.keySet.iterator().next(); } }
Leetcode 355. Design Twitter
思路:这道题需要维护的操作是
getNewsFeed(),如果把所有用户tweet收集起来,重新排个序,将会非常费时。
题目要求让我收集最近post的Tweet,所以我们可以采取竞选的策略,在所有followed中的用户都会存在自己的Tweet,竞选一次得到一条最新post后,删除最新的post,重新加入队列,进行竞选。
Tweet用什么来维护?可以采用数组,但数组动态扩展性不够强,尤其在这种不断post的应用中,所以用链表来实现。这样,每当有新的Tweet被post,就会加入链表中,采用头插法。而且链表天然的有序,这给竞选节省了很多开销。(避免遍历先前post的Tweet)
代码如下:
public class Twitter { private static int timeStamp = 0; Map<Integer, User> userMap; private class User{ public int id; public Set<Integer> followed; public Tweet head; public User(int id){ this.id = id; followed = new HashSet<>(); follow(id); head = null; } public void follow(int id){ followed.add(id); } public void unfollow(int id){ followed.remove(id); } public void postTweet(int id){ Tweet tweet = new Tweet(id); tweet.next = head; head = tweet; } } private class Tweet{ public int id; public int time; public Tweet next; public Tweet(int id){ this.id = id; this.time = timeStamp++; next= null; } } /** Initialize your data structure here. */ public Twitter() { userMap = new HashMap<Integer,User>(); } /** Compose a new tweet. */ public void postTweet(int userId, int tweetId) { if (!userMap.containsKey(userId)) userMap.put(userId, new User(userId)); userMap.get(userId).postTweet(tweetId); } /** * Retrieve the 10 most recent tweet ids in the user's news feed. Each item * in the news feed must be posted by users who the user followed or by the * user herself. Tweets must be ordered from most recent to least recent. */ public List<Integer> getNewsFeed(int userId) { List<Integer> ans = new ArrayList<>(); if (!userMap.containsKey(userId)){ userMap.put(userId, new User(userId)); return ans; } Set<Integer> followees = userMap.get(userId).followed; Queue<Tweet> queue = new PriorityQueue<>((a,b) -> b.time - a.time); for (int f : followees){ User user = userMap.get(f); if (user.head != null) queue.add(user.head); } while (!queue.isEmpty() && ans.size() < 10){ Tweet first = queue.poll(); ans.add(first.id); if (first.next != null) queue.offer(first.next); } return ans; } /** * Follower follows a followee. If the operation is invalid, it should be a * no-op. */ public void follow(int followerId, int followeeId) { if (!userMap.containsKey(followerId)) userMap.put(followerId, new User(followerId)); if (!userMap.containsKey(followeeId)) userMap.put(followeeId, new User(followeeId)); userMap.get(followerId).follow(followeeId); } /** * Follower unfollows a followee. If the operation is invalid, it should be * a no-op. */ public void unfollow(int followerId, int followeeId) { if (followeeId == followerId) return; if (userMap.containsKey(followerId)){ if (userMap.get(followerId).followed.contains(followeeId)) userMap.get(followerId).unfollow(followeeId); } } }
Leetcode 460. LFU Cache
思路:和第三题一致,使用一个双向链表来实现元素的浮动,每当容量满时,删除队头的元素即可,而最近使用过的key都会向下浮动。
代码如下:
public class LFUCache { class Node{ public int count; public LinkedHashSet<Integer> keys; public Node next; public Node prev; public Node(int count){ this.count = count; keys = new LinkedHashSet<>(); prev = next = null; } } private Node head = null; private int cap = 0; Map<Integer, Integer> cache; Map<Integer, Node> nodeHash; public LFUCache(int capacity) { cache = new HashMap<>(); nodeHash = new HashMap<>(); this.cap = capacity; } private void increaseCount(int key){ Node node = nodeHash.get(key); node.keys.remove(key); if (node.next == null){ node.next = new Node(node.count + 1); node.next.prev = node; node.next.keys.add(key); } else if (node.next.count == node.count + 1){ node.next.keys.add(key); } else{ Node tmp = new Node(node.count + 1); tmp.keys.add(key); tmp.prev = node; tmp.next = node.next; node.next.prev = tmp; node.next = tmp; } nodeHash.put(key, node.next); if (node.keys.isEmpty()) remove(node); } private void addToHead(int key) { if (head == null){ head = new Node(0); head.keys.add(key); }else if (head.count > 0){ Node node = new Node(0); node.keys.add(key); node.next = head; head.prev = node; head = node; }else{ head.keys.add(key); } nodeHash.put(key, head); } private void removeOld(){ if (head == null) return; int old = head.keys.iterator().next(); head.keys.remove(old); if (head.keys.size() == 0) remove(head); nodeHash.remove(old); cache.remove(old); } private void remove(Node node) { if (node.prev == null){ head = node.next; }else{ node.prev.next = node.next; } if (node.next != null){ node.next.prev = node.prev; } } public int get(int key) { //update if (!cache.containsKey(key)) return -1; increaseCount(key); return cache.get(key); } public void put(int key, int value) { //remove if (cap == 0) return; if (cache.containsKey(key)){ cache.put(key, value); }else{ if (cache.size() < cap){ cache.put(key, value); }else{ removeOld(); cache.put(key, value); } addToHead(key); } increaseCount(key); } }
Leetcode 146. LRU Cache
思路:这道题要比上一题简单,思路很简单,一旦有get操作和put操作,就把当前结点在链表中的位置调至链表末尾。当超过容量限制时,直接删除头元素。
public class LRUCache { class Node{ public int key; public Node next; public Node prev; public Node(int key){ this.key = key; next = null; prev = null; } } private Node head = null; private Node tail = null; private int cap = 0; Map<Integer, Integer> cache; Map<Integer, Node> nodeMap; public LRUCache(int capacity) { cache = new HashMap<>(); nodeMap =new HashMap<>(); head = new Node(Integer.MAX_VALUE); tail = new Node(Integer.MAX_VALUE); head.next = tail; tail.prev = head; this.cap = capacity; } private void update(int key){ Node node; if (nodeMap.containsKey(key)){ node = nodeMap.get(key); node.prev.next = node.next; node.next.prev = node.prev; } else{ node = new Node(key); } tail.prev.next = node; node.prev = tail.prev; node.next = tail; tail.prev = node; nodeMap.put(key, node); } private void remove(){ Node node = head.next; head.next = node.next; node.next.prev = head; cache.remove(node.key); nodeMap.remove(node.key); } public int get(int key) { if (!cache.containsKey(key)) return -1; update(key); return cache.get(key); } public void put(int key, int value) { if (cap == 0) return; if (cache.containsKey(key)){ cache.put(key, value); } else{ if (cache.size() < cap){ cache.put(key, value); } else{ remove(); cache.put(key,value); } } update(key); } }
相关文章推荐
- App细节设计系列:Facebook Paper for iOS
- 深入理解JavaScript系列(30):设计模式之外观模式
- JQuery Smart UI 简介(六) — 框架设计【后篇】(数据接口、后台框架)[简介系列完结]
- App细节设计系列:Secret for iOS
- 深入理解JavaScript系列(30):设计模式之外观模式
- 深入理解JavaScript系列(30):设计模式之外观模式详解
- 表单设计器系列之一些细节操作
- App细节设计系列:Skype for iOS
- Android中设计模式--策略模式(封装会变化的算法部分,面向接口不针对实现)
- 每天学习一算法系列(3)(设计包含min函数的栈,要求函数min、push以及pop的时间复杂度都是O(1))
- 接口测试从零开始系列1_如何针对接口方法设计测试用例
- 深入理解JavaScript系列(30):设计模式之外观模式
- App细节设计系列:Path for iOS
- 设计技巧9:内部类利用外部类方法实现公共接口 封装实现细节
- JQuery Smart UI 简介(六) — 框架设计【后篇】(数据接口、后台框架)[简介系列完结]
- Android SDK开发指南(翻译)系列三:Tools(一)--使用AIDL, 设计一个远程接口
- 深入理解JavaScript系列(30):设计模式之外观模式
- 剑指offer系列——二维数组中,每行从左到右递增,每列从上到下递增,设计算法找其中的一个数
- 深入理解JavaScript系列(30):设计模式之外观模式
- BBS 设计思路系列 ---- 网友提供的一个用户在CSDN的总分算法