Showing posts with label Design. Show all posts
Showing posts with label Design. Show all posts

Sunday, December 15, 2019

Leetcode 155: Min Stack

https://leetcode.com/problems/min-stack/description/



Notes:

This is an interesting question.

In order to increase time complexity, we usually need to sacrifice some space.

Here I want to share two ways to solve this problem.

1): use two stacks. One is a normal stack. The second one is used to record the instant minimum only as pushing elements into the first stack.

2): use one stack only. Every time  after we push in a new element, we will push in the instant minimum first.

Let realize the first one first.

See the code below:

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {
       
    }

    void push(int x) {
        sk1.push(x);
        if(sk2.empty() || sk2.top() >= x) sk2.push(x);
    }

    void pop() {
        int x = sk1.top();
        sk1.pop();
        if(x==sk2.top()) sk2.pop();
    }

    int top() {
        return sk1.top();
    }

    int getMin() {
        return sk2.top();
    }

private:
    stack<int> sk1, sk2;
};

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

Now let see the second one:

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {
       
    }

    void push(int x) {
        int y = sk.empty() ? x : min(x, sk.top());
        sk.push(x); 
        sk.push(y);
    }

    void pop() {
        sk.pop();
        sk.pop();
    }

    int top() {
        int x = sk.top();
        sk.pop();
        int res = sk.top();
        sk.push(x);
        return res;
    }

    int getMin() {
        return sk.top();
    }

private:
    stack<int> sk;
};

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

Thursday, October 17, 2019

Leetcode 381: Insert Delete GetRandom O(1) - Duplicates allowed

https://leetcode.com/problems/insert-delete-getrandom-o1-duplicates-allowed/description/

Design a data structure that supports all following operations in average O(1) time.
Note: Duplicate elements are allowed.
  1. insert(val): Inserts an item val to the collection.
  2. remove(val): Removes an item val from the collection if present.
  3. getRandom: Returns a random element from current collection of elements. The probability of each element being returned is linearly related to the number of same value the collection contains.
Example:
// Init an empty collection.
RandomizedCollection collection = new RandomizedCollection();

// Inserts 1 to the collection. Returns true as the collection did not contain 1.
collection.insert(1);

// Inserts another 1 to the collection. Returns false as the collection contained 1. Collection now contains [1,1].
collection.insert(1);

// Inserts 2 to the collection, returns true. Collection now contains [1,1,2].
collection.insert(2);

// getRandom should return 1 with the probability 2/3, and returns 2 with the probability 1/3.
collection.getRandom();

// Removes 1 from the collection, returns true. Collection now contains [1,2].
collection.remove(1);

// getRandom should return 1 and 2 both equally likely.
collection.getRandom();

Notes:

This question can be viewed as a follow up to Leetcode 380: Insert Delete GetRandom O(1).

If we still use a vector to keep all the elements, the getRandom() should be still in O(1). But now we need to deal with the duplicates.

One identifier to duplicates are their indexes in the vector. So one way is that we can use the index as one part of the key of the hashmap.

See the code below:
class RandomizedCollection {
public:
    /** Initialize your data structure here. */
    RandomizedCollection() {

    }

    /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
    bool insert(int val) {
        int len = v.size();
        ump[val][len] = 1;
        v.push_back(val);
        return ump[val].size() == 1;
    }

    /** Removes a value from the collection. Returns true if the collection contained the specified element. */
    bool remove(int val) {
        if(!ump.count(val) || ump[val].empty() ) return false;
        int id = ump[val].begin()->first, a = v.back(), len = v.size();
        v[id] = a;
        ump[val].erase(id);
        ump[a][id] = 1;
        ump[a].erase(len-1);
        v.pop_back();
        return true;
    }

    /** Get a random element from the collection. */
    int getRandom() {
        return v[rand() % v.size()];
    }

private:
    unordered_map<int, unordered_map<int, int>> ump;//<val, <indx, ct>>
    vector<int> v;
};

/**
 * Your RandomizedCollection object will be instantiated and called as such:
 * RandomizedCollection* obj = new RandomizedCollection();
 * bool param_1 = obj->insert(val);
 * bool param_2 = obj->remove(val);
 * int param_3 = obj->getRandom();
 */

Another way is that we can define the following data structure:

unordered_map<int, vector<int>> mp;  // <val, <index>>;

Then how can we localize the index of the element inside <index> above? We can store this information inside the vector:

vector<pair<int , int>> //<pair<val, index inside mp[val] or <index>>

When a new element is inserted:

mp[val].push_back(v.size());
v.push_back({val, mp[val].size() - 1});

In this way, mp[val[i].first][val[i].second] = i. Thus, from the element inside the v, we can localize the element position inside the mp <int, vector<int>>.

Leetcode 380: Insert Delete GetRandom O(1)

https://leetcode.com/problems/insert-delete-getrandom-o1/description/

Design a data structure that supports all following operations in average O(1) time.
  1. insert(val): Inserts an item val to the set if not already present.
  2. remove(val): Removes an item val from the set if present.
  3. getRandom: Returns a random element from current set of elements. Each element must have the same probability of being returned.
Example:
// Init an empty set.
RandomizedSet randomSet = new RandomizedSet();

// Inserts 1 to the set. Returns true as 1 was inserted successfully.
randomSet.insert(1);

// Returns false as 2 does not exist in the set.
randomSet.remove(2);

// Inserts 2 to the set, returns true. Set now contains [1,2].
randomSet.insert(2);

// getRandom should return either 1 or 2 randomly.
randomSet.getRandom();

// Removes 1 from the set, returns true. Set now contains [2].
randomSet.remove(1);

// 2 was already in the set, so return false.
randomSet.insert(2);

// Since 2 is the only number in the set, getRandom always return 2.
randomSet.getRandom();

Notes:

The key to this question is that: how can we achieve O(1) in getRandom()? since there is exiting data structures for insert() and remove() in O(1), for example, hashmap.

Vector or array can localize the element or getRandom() in O(1) (by using rand() for getRandom()), but how can we achieve O(1) in deleting one element?

It is known that we can pop_back() of a vector in O(1). So if we do not care about the deleted element any more, we can do a trick here:

Swap the A[i] with the A.back(). Then we can pop_back(). Both steps are in O(1). So we just need to use a hashmap to memorize the location of the A[i], then we can perform all swap and delete in O(1).

See the code below:

class RandomizedSet {
public:
    /** Initialize your data structure here. */
    RandomizedSet() {
       
    }

    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    bool insert(int val) {
        if(!ump.count(val)) {
            ump[val] = data.size();
            data.push_back(val);
            return true;
        }
        return false;
    }

    /** Removes a value from the set. Returns true if the set contained the specified element. */
    bool remove(int val) {
        if(ump.count(val)) {
            int indx = ump[val], last = data.back();
            data[indx] = last;
            ump[last] = indx;
            ump.erase(val);
            data.pop_back();
            return true;
        }
        return false;
    }

    /** Get a random element from the set. */
    int getRandom() {
        int len = data.size();
        return data[rand()%len];
    }

private:
    unordered_map<int, int> ump;
    vector<int> data;
};

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet* obj = new RandomizedSet();
 * bool param_1 = obj->insert(val);
 * bool param_2 = obj->remove(val);
 * int param_3 = obj->getRandom();
 */

Wednesday, October 9, 2019

Leetcode 1146: Snapshot Array

https://leetcode.com/problems/snapshot-array/description/


Implement a SnapshotArray that supports the following interface:
  • SnapshotArray(int length) initializes an array-like data structure with the given length.  Initially, each element equals 0.
  • void set(index, val) sets the element at the given index to be equal to val.
  • int snap() takes a snapshot of the array and returns the snap_id: the total number of times we called snap() minus 1.
  • int get(index, snap_id) returns the value at the given index, at the time we took the snapshot with the given snap_id

Example 1:
Input: ["SnapshotArray","set","snap","set","get"]
[[3],[0,5],[],[0,6],[0,0]]
Output: [null,null,0,null,5]
Explanation: 
SnapshotArray snapshotArr = new SnapshotArray(3); // set the length to be 3
snapshotArr.set(0,5);  // Set array[0] = 5
snapshotArr.snap();  // Take a snapshot, return snap_id = 0
snapshotArr.set(0,6);
snapshotArr.get(0,0);  // Get the value of array[0] with snap_id = 0, return 5

Constraints:
  • 1 <= length <= 50000
  • At most 50000 calls will be made to set, snap, and get.
  • 0 <= index < length
  • 0 <= snap_id < (the total number of times we call snap())
  • 0 <= val <= 10^9

Notes:

The size of the array can be very large, and the query number can also be very large. Thus, updating the entire array for every snapshot is not a good idea.

Instead, we just need to update the element that is changed. And we can store this information in a sorted order relative to the snap_id. And the snap_id is monotonically increases.

After storing this information, for getting the value at a specific snap_id, we just need to do a binary search at that index, since the snap_id is sorted.

To this question, we need to find the upper boundary of the snap_id first, then -1 if it is not 0. The reason to do the upper boundary searching is that, we did not update the snap_id for every element, and only update the one when it is changed.

See the code below:

class SnapshotArray {
public:
    SnapshotArray(int length) {
        //initialization
        ct = 0;
        for(int i=0; i<length; ++i) {
            ns.push_back(vector<pair<int, int>>{{ct, 0}});
        }
    }

    void set(int index, int val) {
        ns[index].push_back({ct, val});
    }

    int snap() {
        return ct++;
    }

    int get(int index, int snap_id) {
        int ps = help(ns, index, snap_id);
        if(ps>0) --ps;
        return ns[index][ps].second;
    }

private:
    vector<vector<pair<int, int>>> ns;
    int ct;
    int help(vector<vector<pair<int, int>>> &ns, int i, int id) {
        int left = 0, right = ns[i].size();
        while(left < right) {
            int mid = left + (right - left) /2;
            if(ns[i][mid].first <= id) left = mid + 1;
            else right = mid;
        }
        return left;
    }
};

/**
 * Your SnapshotArray object will be instantiated and called as such:
 * SnapshotArray* obj = new SnapshotArray(length);
 * obj->set(index,val);
 * int param_2 = obj->snap();
 * int param_3 = obj->get(index,snap_id);
 */

Monday, September 30, 2019

Leetcode 341: Flatten Nested List Iterator

https://leetcode.com/problems/flatten-nested-list-iterator/description/

Given a nested list of integers, implement an iterator to flatten it.
Each element is either an integer, or a list -- whose elements may also be integers or other lists.
Example 1:
Input: [[1,1],2,[1,1]]
Output: [1,1,2,1,1]
Explanation: By calling next repeatedly until hasNext returns false, 
             the order of elements returned by next should be: [1,1,2,1,1].
Example 2:
Input: [1,[4,[6]]]
Output: [1,4,6]
Explanation: By calling next repeatedly until hasNext returns false, 
             the order of elements returned by next should be: [1,4,6].


Notes:

This is a design problem. There are a lot of ways to implement it. Here I want to list two of them.

The first one is we extract the data from the nested array directly and store them in a queue, then gradually print them out.

There are many similar solutions online, but it all involves copying the data. So the second method is without data copy. Instead, we can use the iterator directly.

For method two, I basically copy the code from the this link:

https://leetcode.com/problems/flatten-nested-list-iterator/discuss/80146/Real-iterator-in-Python-Java-C++

See the code for method 1 below:

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * class NestedInteger {
 *   public:
 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
 *     bool isInteger() const;
 *
 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
 *     // The result is undefined if this NestedInteger holds a nested list
 *     int getInteger() const;
 *
 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
 *     // The result is undefined if this NestedInteger holds a single integer
 *     const vector<NestedInteger> &getList() const;
 * };
 */
class NestedIterator {
public:
    NestedIterator(vector<NestedInteger> &nestedList) {
        make_stack(nestedList);
    }

    int next() {
        int t = q.front();
        q.pop();
        return t;
    }

    bool hasNext() {
        return q.size();
    }

private:
    queue<int> q;
    void make_stack(vector<NestedInteger> &ns) {
        for(int i=0; i<ns.size(); ++i) {
            if(ns[i].isInteger()) q.push(ns[i].getInteger());
            else make_stack(ns[i].getList());
        }
    }
};

/**
 * Your NestedIterator object will be instantiated and called as such:
 * NestedIterator i(nestedList);
 * while (i.hasNext()) cout << i.next();
 */


See the code for method 2 below:

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * class NestedInteger {
 *   public:
 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
 *     bool isInteger() const;
 *
 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
 *     // The result is undefined if this NestedInteger holds a nested list
 *     int getInteger() const;
 *
 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
 *     // The result is undefined if this NestedInteger holds a single integer
 *     const vector<NestedInteger> &getList() const;
 * };
 */

class NestedIterator {
public:
    NestedIterator(vector<NestedInteger> &nestedList) {
        begins.push(nestedList.begin());
        ends.push(nestedList.end());
    }

    int next() {
        hasNext();
        return (begins.top()++)->getInteger();
    }

    bool hasNext() {
        while(begins.size()) {
            if(begins.top() == ends.top()) {
                begins.pop();
                ends.pop();
            }
            else {
                auto x = begins.top();
                if(x->isInteger()) return true;
                begins.top()++;//now the top() iterator points to the next element in the vector<NestedInteger>;
                               //the following two lines means that we gong to finish the nested structures inside the first element first;
                               //when it is done, it will return to the next element (this is why we use stack here);
                               //and it will repeat this to all the elements.
                begins.push(x->getList().begin());
                ends.push(x->getList().end());
            }
        }
        return false;
    }

private:
    stack<vector<NestedInteger>::iterator> begins, ends;
};

/**
 * Your NestedIterator object will be instantiated and called as such:
 * NestedIterator i(nestedList);
 * while (i.hasNext()) cout << i.next();
 */

Sunday, September 29, 2019

Leetcode 239: Sliding Window Maximum

https://leetcode.com/problems/sliding-window-maximum/description/


Notes:

This question is labeled as a hard problem. Apparently, it means the linear time.

We need to do two things:

1): find the maximum of a sling window;

2): pop up the element when it is out of range;

In order to pop up one element in O(1), we need set or map like data structures. (Actually we need multiset considering there may be elements with the same value.) But the time complexity is O(N*logK) in this way. Since we need to sort the element inside the container, which requires (logK).

Since we only care about the maximum, or larger elements. We can consider to use a deque.

No, a deque cannot sort the elements automatically, but we can maintain a deque with a decreasing order. Why? since we only need to keep larger elements.

We also need to consider how to pop up the element of out of the k range. Since we move element by element, for each step, we just need to take care of nums[i-k]. If it is still in the container, it must be the first one and also the largest one in the container. Otherwise, it had been removed earlier.

So if it is the front of the deque, we just need to pop the front element. That is another reason we choose the deque structure.

The last question, why is it a linear time?

For each element, we put it in the container once. And we pop them out by most once. Thus it is less than O(2*N), or O(N).

See the code below:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        deque<int> dq;
        for(int i=0; i<nums.size(); ++i) {
            if(dq.size() && dq.front() == i - k) dq.pop_front();
            while(dq.size() && nums[dq.back()] < nums[i]) dq.pop_back();
            dq.push_back(i);
            if(i>=k-1) res.push_back(nums[dq.front()]);
        }
        return res;
    }
};

Leetcode 480: Sliding Window Median

https://leetcode.com/problems/sliding-window-median/description/

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
Examples:
[2,3,4] , the median is 3
[2,3], the median is (2 + 3) / 2 = 2.5
Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Your job is to output the median array for each window in the original array.
For example,
Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.
Window position                Median
---------------               -----
[1  3  -1] -3  5  3  6  7       1
 1 [3  -1  -3] 5  3  6  7       -1
 1  3 [-1  -3  5] 3  6  7       -1
 1  3  -1 [-3  5  3] 6  7       3
 1  3  -1  -3 [5  3  6] 7       5
 1  3  -1  -3  5 [3  6  7]      6
Therefore, return the median sliding window as [1,-1,-1,3,5,6].
Note:
You may assume k is always valid, ie: k is always smaller than input array's size for non-empty array.

Notes:

This another design problem, which seems easy but actually it is not easy to achieve an algorithm  (or data structure, more precisely) faster than O(N*K).

This question can be viewed to one variant to another question Leetcode 239.

For this question, we can consider to use multiset, which can localize any element by the key or its value. There may be elements with the same value, thus we need multiset instead of set.

Multiset or set can order the element in O(logK) with its build-in property. When all the K elements are ordered, it is easy to figure out the median.

Of course it depends on the polarity of K. Here we adopt some very useful operators about the pointers.

See the code below:

class Solution {
public:
    vector<double> medianSlidingWindow(vector<int>& nums, int k) {
        multiset<int> mst(nums.begin(), nums.begin() + k);
        vector<double> res;
        auto mid = next(mst.begin(), k/2);//next() function
        for(int i=k; ; ++i) {
            res.push_back(((double)*mid + (double)*prev(mid, 1-k%2))/2);//prev() function
            if(i==nums.size()) return res;
            mst.insert(nums[i]);
            if(nums[i] < *mid) --mid;
            if(nums[i-k] <= *mid) ++mid;
            mst.erase(mst.lower_bound(nums[i-k]));//lower_bound() function
        }
    }
};

Saturday, September 28, 2019

Leetcode 432: All O`one Data Structure

https://leetcode.com/problems/all-oone-data-structure/description/


Notes:

This question is not that straightforward.

We need to think about a data-structure to realize the O(1) operation of adding, erasing, and getting date.

Usually, we can couple a list with a hash map, just like Leetcode 146 LRU cache and 460 LFU cache.

If we know the related address, it is convenient (O(1) in time) to remove and add elements for a list. So a hash map is used to correlate the key with the address (iterator) of the element, which can give O(1) time to access the element.

Please see the code below:

class AllOne {
private:
    struct Bucket{
        int val;
        unordered_set<string> keys;
    };
    list<Bucket> buckets;
    unordered_map<string, list<Bucket>::iterator> mp;

public:

    /** Initialize your data structure here. */
    AllOne() {
     
    }

    /** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */
    void inc(string key) {
        if(!mp.count(key)) {
            mp[key] = buckets.insert(buckets.begin(), {0, {key}});
        }
        auto nxt = mp[key], bucket = nxt++;
        if(nxt == buckets.end() || nxt->val > bucket->val + 1) {//if nxt does not exit, wee need to create one
            nxt = buckets.insert(nxt, {bucket->val + 1, {}});
        }
        nxt->keys.insert(key);
        mp[key] = nxt;
        bucket->keys.erase(key);//remove key from the previous position
        if(bucket->keys.empty()) buckets.erase(bucket);
    }

    /** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */
    void dec(string key) {
        if(!mp.count(key)) return;
        auto pre = mp[key], bucket = pre--;
        mp.erase(key);
        if(bucket->val > 1) {
            if(bucket == buckets.begin() || pre->val < bucket->val - 1) {//if pre does not exit, we need to create one
                pre = buckets.insert(bucket, {bucket->val - 1, {}});
            }
            pre->keys.insert(key);
            mp[key] = pre;
        }
        bucket->keys.erase(key);//remove key from the previous position
        if(bucket->keys.empty()) buckets.erase(bucket);
    }

    /** Returns one of the keys with maximal value. */
    string getMaxKey() {
        if(buckets.empty()) return "";
        return *(buckets.rbegin()->keys.begin());
    }

    /** Returns one of the keys with Minimal value. */
    string getMinKey() {
        if(buckets.empty()) return "";
        return *(buckets.begin()->keys.begin());
    }
};
/**
 * Your AllOne object will be instantiated and called as such:
 * AllOne* obj = new AllOne();
 * obj->inc(key);
 * obj->dec(key);
 * string param_3 = obj->getMaxKey();
 * string param_4 = obj->getMinKey();
 */

Friday, September 27, 2019

Leetcode 641: Design Circular Deque

https://leetcode.com/problems/design-circular-deque/description/


Notes:

This question is similar to another design question Leetcode 622: Design Circular Queue.

Please see the code below:

class MyCircularDeque {
private:
    vector<int> buffer;
    int len;
    int front;
    int rear;
    int ct;

public:
    /** Initialize your data structure here. Set the size of the deque to be k. */
    MyCircularDeque(int k): buffer(k, 0), len(k), front(k-1), rear(0), ct(0) {

    }

    /** Adds an item at the front of Deque. Return true if the operation is successful. */
    bool insertFront(int value) {
        if(isFull()) return false;
        buffer[front] = value;
        front = (front - 1 + len)%len;
        ++ct;
        return true;
    }

    /** Adds an item at the rear of Deque. Return true if the operation is successful. */
    bool insertLast(int value) {
        if(isFull()) return false;
        buffer[rear] = value;
        rear = (rear + 1)%len;
        ++ct;
        return true;
    }

    /** Deletes an item from the front of Deque. Return true if the operation is successful. */
    bool deleteFront() {
        if(isEmpty()) return false;
        front = (front + 1)%len;
        --ct;
        return true;
    }

    /** Deletes an item from the rear of Deque. Return true if the operation is successful. */
    bool deleteLast() {
        if(isEmpty()) return false;
        rear = (rear - 1 + len)%len;
        --ct;
        return true;
    }
   
    /** Get the front item from the deque. */
    int getFront() {
        if(isEmpty()) return -1;
        return buffer[(front+1)%len];
    }

    /** Get the last item from the deque. */
    int getRear() {
        if(isEmpty()) return -1;
        return buffer[(rear-1+len)%len];
    }

    /** Checks whether the circular deque is empty or not. */
    bool isEmpty() {
        return ct == 0;
    }

    /** Checks whether the circular deque is full or not. */
    bool isFull() {
        return ct == len;
    }
};

/**
 * Your MyCircularDeque object will be instantiated and called as such:
 * MyCircularDeque* obj = new MyCircularDeque(k);
 * bool param_1 = obj->insertFront(value);
 * bool param_2 = obj->insertLast(value);
 * bool param_3 = obj->deleteFront();
 * bool param_4 = obj->deleteLast();
 * int param_5 = obj->getFront();
 * int param_6 = obj->getRear();
 * bool param_7 = obj->isEmpty();
 * bool param_8 = obj->isFull();
 */

Leetcode 622: Design Circular Queue

https://leetcode.com/problems/design-circular-queue/description/

Notes:

This is a good design question. It seems boring, but actually can test some data structure capability.

This question is similar to another question: Leetcode 641 Design Circular Deque.

See the code below:

class MyCircularQueue {
private:
    vector<int> buffer;
    int front;
    int rear;
    int len;
    int ct;

public:
    /** Initialize your data structure here. Set the size of the queue to be k. */
    MyCircularQueue(int k): buffer(k, 0), front(0), rear(0), len(k), ct(0) {
    }
   
    /** Insert an element into the circular queue. Return true if the operation is successful. */
    bool enQueue(int value) {
        if(isFull()) return false;
        buffer[rear] = value;
        rear = (rear + 1)%len;
        ++ct;
        return true;
    }

    /** Delete an element from the circular queue. Return true if the operation is successful. */
    bool deQueue() {
        if(isEmpty()) return false;
        front = (front + 1)%len;
        --ct;
        return true;
    }

    /** Get the front item from the queue. */
    int Front() {
        if(isEmpty()) return -1;
        return buffer[front];
    }

    /** Get the last item from the queue. */
    int Rear() {
        if(isEmpty()) return -1;
        return buffer[(rear - 1 + len)%len];
    }

    /** Checks whether the circular queue is empty or not. */
    bool isEmpty() {
        return ct == 0;
    }

    /** Checks whether the circular queue is full or not. */
    bool isFull() {
        return ct == len;
    }
};

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue* obj = new MyCircularQueue(k);
 * bool param_1 = obj->enQueue(value);
 * bool param_2 = obj->deQueue();
 * int param_3 = obj->Front();
 * int param_4 = obj->Rear();
 * bool param_5 = obj->isEmpty();
 * bool param_6 = obj->isFull();
 */

Saturday, August 24, 2019

Leetcode 460: LFU Cache

https://leetcode.com/problems/lfu-cache/description/


Design and implement a data structure for Least Frequently Used (LFU) 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 reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ );

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.get(3);       // returns 3.
cache.put(4, 4);    // evicts key 1.
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4


Note:

This question is a follow-up to Leetcode 146: LRU

The key points are:

(1): We need to get() a element in O(1), so hash map is a choice;

(2): We need to memorize both frequency and order of the elements, which are not covered by a hash map. So we need add some other kind of data structure. Since we need to erase a element in O(1) too, so list should be a choice;

(3): Using a list, it is true that we can erase one element in O(1), but we need to localize the position of the element in O(1) too. So another hash map is needed to correlate the key and the position in the list;

(4): We also need to record the frequency of each element;

(5): We need to maintain the least frequency as well.

See the code below:

class LFUCache {
public:
    LFUCache(int capacity) {
        cap = capacity;
        minF = 0;
    }

    int get(int key) {
        if(!ump.count(key)) return -1;
        freq[ump[key].second].erase(itr[key]);
        ++ump[key].second;
        freq[ump[key].second].push_back(key);
        itr[key] = --freq[ump[key].second].end();//cannot use rbegin() here; 
                                                                     //different iterator types
        if(freq[minF].empty()) ++minF;
        return ump[key].first;
    }

    void put(int key, int value) {
        if(cap<=0) return;
        if(get(key) != -1) { //must use get() here, to update the freq and minF; 
                                                 //otherwise, we have to update them individually.
            ump[key].first = value;
            return;
        }
        if(ump.size() >= cap) {
            ump.erase(freq[minF].front());//smallest freq and least used;
            itr.erase(freq[minF].front());
            freq[minF].pop_front();
        }
        ump[key] = {value, 1};
        freq[1].push_back(key);
        itr[key] = --freq[1].end();
        minF = 1;
    }

private:
    int cap, minF;
    unordered_map<int, pair<int, int>> ump; //<key, <value, frequency>>
    unordered_map<int, list<int>::iterator> itr; //<key, <iterator of key> in the list>
    unordered_map<int, list<int>> freq; //<freq, <key>>
};

/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache* obj = new LFUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

Leetcode 146: LRU Cache


Notes:

There are two functions: one is get() and the other is put(). And both of them need O(1) time complexity. For get(), it is easy to think about a hash map. But for put(), and also the total size is finite, we need to remove the element once it is beyond full; and we need to always update the front element for both get() and put().

For put(), if the key is existed, we need to update the value to the key; and update the position in the list to the front.

One solution is using list, or double linked list. The erase() of list is O(1), but we need to know which one to be erased in O(1) too. So we need to a hash map to correlate the address of the element in the list too. In addition, when remove one element from a list, we need to know the address, not the value. Thus, the hash map needs to set up a relation between the key and the address.

We still need to return the value in the {key, value} pair. So we can find a way to put the value and address together.

See the code below:

class LRUCache{
public:
    LRUCache(int capacity) {
        cap = capacity;
    }
   
    int get(int key) {
        if(!ump.count(key)) return -1;
        auto it = ump[key];
        lt.splice(lt.begin(), lt, it);//splice() of list!
        return lt.front().second;
    }
   
    void put(int key, int value) {
        if(ump.count(key)) {
            auto it = ump[key];
            it->second = value;//override the previous value
            lt.splice(lt.begin(), lt, it);
        }
        else {
            lt.push_front({key, value});
            ump[key] = lt.begin();
            if(lt.size() > cap) {
                ump.erase(lt.back().first);
                lt.pop_back();
            }
        }
    }

private:
    int cap;
    list<pair<int, int>> lt;
    unordered_map<int, list<pair<int, int>>::iterator> ump;//iterator!
};

There is a follow-up question: Leetcode 460.

Sunday, August 4, 2019

Leetcode 981: Time Based Key-Value Store

https://leetcode.com/problems/time-based-key-value-store/description/



Notes:

This question is pretty straightforward, and it is essentially a question of data structure + binary search. See the code below,
class TimeMap {
public:
    /** Initialize your data structure here. */
    TimeMap() {

    }

    void set(string key, string value, int timestamp) {
        if(ump.count(key) == 0) ump[key].push_back({timestamp, value});
        auto it = upper_bound(ump[key].begin(), ump[key].end(), pair<int, string>(timestamp, ""));
        ump[key].insert(it, {timestamp, value});
    }

    string get(string key, int timestamp) {
        if(ump.count(key) == 0) return "";
        auto it = upper_bound(ump[key].begin(), ump[key].end(), pair<int, string>(timestamp, ""));
        if(it != ump[key].end() && it->first == timestamp) return it->second;
        if(it == ump[key].begin()) return "";
        return (it-1)->second;
    }
private:
    unordered_map<string, vector<pair<int, string>>> ump;
};

/**
 * Your TimeMap object will be instantiated and called as such:
 * TimeMap* obj = new TimeMap();
 * obj->set(key,value,timestamp);
 * string param_2 = obj->get(key,timestamp);
 */

It should be noticed that inside the upper_bound(), the type of pair has to be written explicitly as pair<int, string>, otherwise compiler error will occur.

Another way using a different structure. Pay attention to the property of map.upper_bound().

int _=[](){//increase run spead
 ios::sync_with_stdio(false);
 cin.tie(nullptr);
 return 0;
 }();

class TimeMap {
public:
    /** Initialize your data structure here. */
    TimeMap() {
       
    }
   
    void set(string key, string value, int timestamp) {
        ump[key][timestamp] = value;
    }

    string get(string key, int timestamp) {
        if(ump.count(key) == 0) return "";
        auto it = ump[key].upper_bound(timestamp); //find the upper bound of the key of the map...
        if(it == ump[key].begin()) return "";
        return (--it)->second;
    }

private:
    unordered_map<string, map<int, string>> ump;
};

/**
 * Your TimeMap object will be instantiated and called as such:
 * TimeMap* obj = new TimeMap();
 * obj->set(key,value,timestamp);
 * string param_2 = obj->get(key,timestamp);
 */

Thursday, August 1, 2019

LeetCode 158: Read N Characters Given Read4 II - Call multiple times

https://leetcode.com/problems/read-n-characters-given-read4-ii-call-multiple-times/

The API: int read4(char *buf) reads 4 characters at a time from a file. The return value is the actual number of characters read.

For example, it returns 3 if there is only 3 characters left in the file. By using the read4 API, implement the function int read(char *buf, int n) that reads n characters from the file.

Note: The read function may be called multiple times.

Notes: 

This question asks to use the read function multiple times. Thus, we need to record the position of previous calls. The solution is to use build-in variables: readPos is the current reading position; writePos is the position from the read4 API; and a buff[4] array to record the chars read previously.

The key logic are listed below,

1): when readPos == writePos, it is time to operate another call of read4 API;

2): if not, meaning that there are still some char left in previous call. Then we should start with the buff[4] array;

3): when writePos == 0, it means no more char left, so we should stop and return the actual number of chars read so far;

read4(buff) means store the chars read into buff. buff here serves as a cache from previous read4. Every time for a new call of read(), we need to check if there are chars remaining in the buff.

See the code below,

class Solution {
public:
    int read(char *buf, int n) {
        for (int i = 0; i < n; ++i) {
            if (readPos == writePos) {
                writePos = read4(buff);
                readPos = 0;
                if (writePos == 0) return i;
            }
            buf[i] = buff[readPos++];
        }
        return n;
    }
private:
    int readPos = 0, writePos = 0;
    char buff[4];
};

Wednesday, July 31, 2019

Leetcode 157: Read N Characters Given Read4

https://leetcode.com/problems/read-n-characters-given-read4/

The API: int read4(char *buf) reads 4 characters at a time from a file. The return value is the actual number of characters read.

For example, it returns 3 if there is only 3 characters left in the file. By using the read4 API, implement the function int read(char *buf, int n) that reads n characters from the file.

Note: The read function will only be called once for each test case.

Notes:

This question seems very confusing, but actually it is not a hard problem at all.

The buf is the pointer pointing to the current reading position. We can use a variable to record the total number of char read so far. When it reaches n or no more char left in the file, we should stop the loop.

See the code below:

class solution {
public:
    int read(char *buf, int n) {
        int res = 0;//total number of char read so far;
            while(res < n) {
                int cur = read4(buf + res);//number of chars read from read4 API;
                res += cur;
                if(cur < 4) break;//no more char left;
            }
        return min(res, n);
    }
};