diff --git a/lab/exercises/01-arrays/longest-unique-characters.js b/lab/exercises/01-arrays/longest-unique-characters.js new file mode 100644 index 00000000..895e4e91 --- /dev/null +++ b/lab/exercises/01-arrays/longest-unique-characters.js @@ -0,0 +1,72 @@ +/** + * @param {string} s + * @return {number} + */ +function lengthOfLongestSubstring(s) { + let max = 0; + let start = 0; + const map = {}; + + for (let i = 0; i < s.length; i++) { + const char = s[i]; + + if (map[char]) { + start = map[char] + 1; + } + + map[char] = i; + max = Math.max(1 + i - start, max); + } + + return max; +} + +const assert = require('assert'); + +const testCases = { abcabcbb: 3 }; + +for (const [string, unique] of Object.entries(testCases)) { + assert.equal(lengthOfLongestSubstring(string), unique); +} + +/* + Longest string without duplicate chars. + + "abcabcbb" + 3 (abc) + + i=6 + c=b + s=5 + h={a:3,b:4,c:2} + m=3 + + --- + a + 1 + + aa + 1 + + ab + 2 + + abc + 3 + + aab + 2 + + aabca + 3 + + "dvdf" + 3 (vdf) + + "abcabcbb" + 3 (abc) + --- + + map: O(n) + backtracking +*/ diff --git a/src/data-structures/custom/lru-cache-1.js b/src/data-structures/custom/lru-cache-1.js index d6453b5d..521559a6 100644 --- a/src/data-structures/custom/lru-cache-1.js +++ b/src/data-structures/custom/lru-cache-1.js @@ -1,30 +1,7 @@ - /** - * Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put. - - get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. - put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item. - - Follow up: - Could you do both operations in O(1) time complexity? - - Example: - - LRUCache cache = new LRUCache( 2); - - cache.put(1, 1); - cache.put(2, 2); - cache.get(1); // returns 1 - cache.put(3, 3); // evicts key 2 - cache.get(2); // returns -1 (not found) - cache.put(4, 4); // evicts key 1 - cache.get(1); // returns -1 (not found) - cache.get(3); // returns 3 - cache.get(4); // returns 4 - - * https://leetcode.com/problems/lru-cache/description/ - * - * @param {number} capacity + * Least Recently Used (LRU) cache. + * Map + Array: O(n) + * @param {number} capacity - Number of items to hold. */ const LRUCache = function (capacity) { this.map = new Map(); diff --git a/src/data-structures/custom/lru-cache-2.js b/src/data-structures/custom/lru-cache-2.js new file mode 100644 index 00000000..b07a4b6b --- /dev/null +++ b/src/data-structures/custom/lru-cache-2.js @@ -0,0 +1,72 @@ +/** + * Least Recently Used (LRU) cache. + * Map + (Hash)Set: O(1) + * @param {number} capacity - Number of items to hold. + */ +var LRUCache = function(capacity) { + this.capacity = capacity || 2; + this.map = new Map(); + this.set = new Set(); + this.size = 0; +}; + +/** + * @param {number} key + * @return {number} + */ +LRUCache.prototype.get = function(key) { + if (!this.map.has(key)) return -1; + // move to top + this.set.delete(key); + this.set.add(key); + + return this.map.get(key); +}; + +/** + * @param {number} key + * @param {number} value + * @return {void} + */ +LRUCache.prototype.put = function(key, value) { + this.map.set(key, value); + // move to top + this.set.delete(key); + this.set.add(key); + + if (this.set.size > this.capacity) { + const leastUsedKey = this.set.values().next().value; + this.map.delete(leastUsedKey); + this.set.delete(leastUsedKey); + } + + this.size = this.map.size; +}; + +/** + * Your LRUCache object will be instantiated and called as such: + * var obj = new LRUCache(capacity) + * var param_1 = obj.get(key) + * obj.put(key,value) + */ + + +/* + Implement a hashMap cache with a given capacity that once reach deletes the least used element and store the new one. + + --- + + c = new LRUCache(2); + c.put(1,1); + c.put(2,2); + c.put(3,3); // deletes key 1 + + c = new LRUCache(2); + c.put(1,1); + c.put(2,2); + c.get(1); + c.put(3,3); // deletes key 2 + +*/ + +module.exports = LRUCache; diff --git a/src/data-structures/custom/lru-cache-3.js b/src/data-structures/custom/lru-cache-3.js new file mode 100644 index 00000000..506ffcce --- /dev/null +++ b/src/data-structures/custom/lru-cache-3.js @@ -0,0 +1,51 @@ +const DLinkedList = require('../linked-lists/linked-list'); +/** + * Least Recently Used (LRU) cache. + * Map + Double LinkedList: O(1) + * @param {number} capacity - Number of items to hold. + */ +class LRUCache extends Map { + constructor(capacity) { + super(); // initialize map + this.capacity = capacity; + this.list = new DLinkedList(); + } + + get(key) { + if (!super.has(key)) { return -1; } + + // console.log('get', {key}); + const node = super.get(key); + this.moveToHead(key, node); + + return node.value.value; + } + + put(key, value) { + // console.log('put', {key, value}); + let node; + if (super.has(key)) { + node = super.get(key); + node.value.value = value; + } else { + node = this.list.addLast({key, value}); + } + this.moveToHead(key, node); + + if (this.list.size > this.capacity) { + const firstNode = this.list.removeFirst(); + super.delete(firstNode.key); + } + } + + moveToHead(key, node) { + // remove node and put it in front + this.list.removeByNode(node); + const newNode = this.list.addLast(node.value); + super.set(key, newNode); + // console.log('\tlist', Array.from(this.list).map(l => l.node.value)); + // console.log('\tlist', Array.from(this.list).map(l => l.node.value.key)); + } +} + +module.exports = LRUCache; diff --git a/src/data-structures/custom/lru-cache.js b/src/data-structures/custom/lru-cache.js index 13638b6c..2907f8e6 100644 --- a/src/data-structures/custom/lru-cache.js +++ b/src/data-structures/custom/lru-cache.js @@ -1,34 +1,7 @@ /** - * Design and implement a data structure for Least Recently Used (LRU) cache. - * It should support the following operations: get and put. - - get(key) - Get the value (will always be positive) of the key - if the key exists in the cache, otherwise return -1. - put(key, value) - Set or insert the value if the key is not already present. - When the cache reached its capacity, it should invalidate the least - recently used item before inserting a new item. - - Follow up: - Could you do both operations in O(1) time complexity? - - Example: - - LRUCache cache = new LRUCache( 2); - - cache.put(1, 1); - cache.put(2, 2); - cache.get(1); // returns 1 - cache.put(3, 3); // evicts key 2 - cache.get(2); // returns -1 (not found) - cache.put(4, 4); // evicts key 1 - cache.get(1); // returns -1 (not found) - cache.get(3); // returns 3 - cache.get(4); // returns 4 - - * https://leetcode.com/problems/lru-cache/description/ - * https://leetcode.com/submissions/detail/178329173/ - * - * @param {number} capacity + * Least Recently Used (LRU) cache. + * (ordered) Map: O(1) + * @param {number} capacity - Number of items to hold. */ class LRUCache { constructor(capacity) { @@ -53,7 +26,7 @@ class LRUCache { rotate(key) { this.moveToTop(key); while (this.map.size > this.capacity) { - const it = this.map.keys(); + const it = this.map.keys(); // keys are in insertion order. this.map.delete(it.next().value); } } @@ -65,6 +38,10 @@ class LRUCache { this.map.set(key, value); } } + + get size() { + return this.map.size; + } } module.exports = LRUCache; diff --git a/src/data-structures/custom/lru-cache.spec.js b/src/data-structures/custom/lru-cache.spec.js new file mode 100644 index 00000000..73368627 --- /dev/null +++ b/src/data-structures/custom/lru-cache.spec.js @@ -0,0 +1,108 @@ +const LRUCache = require('./lru-cache-3'); + +describe('LRU Cache', () => { + let c; + + describe('#constructor', () => { + it('should initialize', () => { + c = new LRUCache(); + expect(c).toBeDefined(); + }); + + it('should initialize', () => { + c = new LRUCache(7); + expect(c.capacity).toEqual(7); + }); + }); + + describe('when initialized', () => { + beforeEach(() => { + c = new LRUCache(2); + }); + + describe('#put', () => { + it('should insert new elements', () => { + c.put(1, 1); + expect(c.size).toEqual(1); + }); + + it('should update existing element', () => { + c.put(1, 1); + c.put(1, 2); + expect(c.size).toEqual(1); + }); + }); + + describe('#get', () => { + it('should get element', () => { + c.put(1, 1); + expect(c.get(1)).toEqual(1); + }); + + it('should return -1 for non-existing elements', () => { + expect(c.get(1)).toEqual(-1); + }); + + it('should not add non-existing number to the top of the list', () => { + c.put(1, 1); + expect(c.get(8)).toEqual(-1); + c.put(2, 2); + expect(c.get(9)).toEqual(-1); + expect(c.get(1)).toEqual(1); + expect(c.get(2)).toEqual(2); + }); + + it('should return -1 for removed elements', () => { + c.put(1, 1); + c.put(2, 2); + c.put(3, 3); + expect(c.get(1)).toEqual(-1); + }); + + it('should not remove value if accessed recently', () => { + c.put(1, 1); + c.put(2, 2); + expect(c.get(1)).toEqual(1); + c.put(3, 3); + expect(c.get(1)).toEqual(1); + expect(c.get(2)).toEqual(-1); + }); + + it('should update a value', () => { + c.put(1, 1); + c.put(1, 2); + expect(c.get(1)).toEqual(2); + }); + }); + + it('should work with size 10', () => { + c = new LRUCache(10); + + c.put(10, 13); + c.put(3, 17); + c.put(6, 11); + c.put(10, 5); + c.put(9, 10); + expect(c.get(13)).toEqual(-1); + c.put(2, 19); + expect(c.get(2)).toEqual(19); + expect(c.get(3)).toEqual(17); + c.put(5, 25); + expect(c.get(8)).toEqual(-1); + c.put(9, 22); + c.put(5, 5); + c.put(1, 30); + expect(c.get(11)).toEqual(-1); + c.put(9, 12); + expect(c.get(7)).toEqual(-1); + expect(c.get(5)).toEqual(5); + expect(c.get(8)).toEqual(-1); + expect(c.get(9)).toEqual(12); + c.put(4, 30); + c.put(9, 3); + expect(c.get(9)).toEqual(3); + expect(c.get(10)).toEqual(5); + expect(c.get(10)).toEqual(5); + }); + }); +});