Monday, 7 September 2015

Functional Programming - Divide Array into Two Equal Parts (Google Interview Question)

1. Problem Description
This is a Google interview question for software engineer from careercup. Here is the original description,


- smarthbehl 10 days ago in United States | Report Duplicate | Flag ".

2. Analysis and Solution
It is a combination problem. A problem like given N cards and pick up any half of cards and find out the one combination with the sum closest to the half of sum of all. In this problem we have to differentiate if N is odd or even. If N = 2m, then the number of combination is m!/N!. Anf if N = 2*m+1, then the number of combination is m!/N! too.

Another way of thinking of this problem is regarding it as an optimization problem. 
    - Objective function: Min(lambda1*X1+lamda2*X2 + ... + lamdaN*XN - HalfOfSum)
    - Where lamda(i) either equal to 0 or 1
       and Sigma(lamda(i)) = m

These two are identical in essence. Here I am going to propose how to solve it via functional programming in C++.
    - n < m,
        * F(n) = F(n-1) + lamda(n)*Xn, where lamda(n) = 1
    - n = m
        * check the sum of F(n) with half of the value of sum of all. Take the min.
        * Exhaust other options of Xn. (in this case n is still equal to m and repeat this case)
        * Flip previous lamda(n-1) = 0 and exhaust Xn-1. n != m anymore and repeat the whole.

In order to limit the case of Xn, Xn-1 ...., we will sort the array first. If we found the F(n) (n = m) and its sum is over the the min value we recorded so far, then skipped the rest Xn. Because it is sorted any smaller value is replaced with larger one and this does not affect the optimization process.

Another one if we find a solution, for instance a F(n) (n = m) and its value is equal to exactly half of the value of sum of all, then we can stop because one of global solution is found. In my implementation there is a special case that all the numbers are integer. Therefore if the difference is less than 1 we can confirm that a global solution is found.     

3. C++ Implementation
// ********************************************************************************
// Implementation
// ********************************************************************************
#include <algorithm>
#include <numeric>
#include <stack>
#include <vector>

using DataArray = std::vector<unsigned int>;
using ResultStack = std::stack<size_t>;

enum class Action
{
    STOP,
    CONTINUE,
    FOUND
};

Action DivideTwoPartsWithEqualSum(const DataArray& input,
                                const size_t last_index,
                                const double sum_of_part,
                                const size_t size_of_part,
                                const size_t size_of_all,
                                ResultStack& result,
                                size_t& result_size,
                                double& sumOfResult,
                                ResultStack& curBestResult,
                                double& sumOfCurBestResult)
{
    if (result_size == size_of_part) {
        if (fabs(sumOfResult - sum_of_part) < fabs(sumOfCurBestResult - sum_of_part)) {
            // track the min solution
            curBestResult = result;
            sumOfCurBestResult = sumOfResult;
        }
        if (fabs(sumOfCurBestResult - sum_of_part) < 1.0) {
            // found the best solution;
            return Action::FOUND;
        }
        else if (sumOfResult > sum_of_part) {
            // stop here because the rest of combination can't be smaller
            return Action::STOP;
        }

        return Action::CONTINUE;
    }

    Action act = Action::CONTINUE;
    const size_t startIndex = result.top() + 1;
    const size_t endIndex = size_of_all - (size_of_part - result_size) + 1;
    for (size_t index = startIndex; index < endIndex; ++index) {
        // select lamda(i) and push in
        result.push(index);
        ++result_size;
        sumOfResult += input[index];
        act = DivideTwoPartsWithEqualSum(input, last_index, sum_of_part,
                                         size_of_part, size_of_all,
                                         result, result_size, sumOfResult,
                                         curBestResult, sumOfCurBestResult);

        if (act == Action::FOUND) {
            break;
        }
        // flip lamda(i) and pop out 
        result.pop();
        --result_size;
        sumOfResult -= input[index];
        if (act == Action::STOP) {
            act = Action::CONTINUE;
        }
    }

    return act;
}

double DivideToTwoPartsWithEqualSum(const DataArray& input,
                                    DataArray& result)
{
    if (input.empty()) {
        return -1.0;
    }

    const size_t len = input.size();
    const size_t size_of_part = len >> 1;
    const double sum_of_all = std::accumulate(input.begin(), input.end(), 0.0);

    const double sum_of_part = sum_of_all/2;
    const size_t last_index = (len & 1) > 0 ? (size_of_part + 2) : (size_of_part + 1);
    DataArray inputCopy(input);
    ResultStack tempResult;
    size_t size_of_stack;
    double sum_of_stack;
    ResultStack bestResult;
    double sumOfBestResult;
    std::sort(inputCopy.begin(), inputCopy.end()); // sort
    for (size_t index = 0; index < last_index; ++index) {
        tempResult.push(index);
        size_of_stack = 1;
        sum_of_stack = inputCopy[index];
        if (DivideTwoPartsWithEqualSum(inputCopy, last_index,
                                       sum_of_part, size_of_part, len,
                                       tempResult, size_of_stack,
                                       sum_of_stack, bestResult, 
                                       sumOfBestResult) == Action::FOUND) {
            break;
        }
        tempResult.pop();
    }

    result.clear();
    result.reserve(size_of_part);
    while (!bestResult.empty())
    {
        result.push_back(inputCopy[bestResult.top()]);
        bestResult.pop();
    }

    return sumOfBestResult;
}

// ********************************************************************************
// Test
// ********************************************************************************
void TestCases
{
    DataArray result;
    // solution: 1, 8, 9
    assert(DivideToTwoPartsWithEqualSum(DataArray{ 1, 2, 3, 6, 8, 9, 7 }, result) == 18.0);
    // solution: 5, 7, 62
    assert(DivideToTwoPartsWithEqualSum(DataArray{ 5, 7, 50, 8, 9, 62 }, result) == 74);
    // solution: 10, 20
    assert(DivideToTwoPartsWithEqualSum(DataArray{ 1, 10, 11, 18, 20 }, result) == 30);
    // solution: 1, 10, 13
    assert(DivideToTwoPartsWithEqualSum(DataArray{ 1, 7, 8, 9, 10, 13 }, result) == 24);
}

Sunday, 30 August 2015

Find the Number of Paths from Source to Sink - Google Interview Question

1. Problem Description
This is a Google interview question from careercup. Here is the original description,
"
There is a graph which has 1 source and 1 sink(destination node) 

In between those 2 nodes there are n levels of nodes. At each level there are exactly m nodes.
Every node of level i is connected to every node of level i+1 (All edges go in forward direction)

Find total no. of paths between source and sink.


This was basic question. Then he tweaked it by adding few more edges.
Now there were some additional edges. Now sdges can go from any lower level to any higher level. That is not just from i to i+1..

can be from i to i+2 or i +3 so on...
(such bouncing edges were added only in the n levels not from or to (to src or sink)
Now find number of paths?
- student1234 2 days ago in United States | Report Duplicate | Flag "

2. Analysis and Solution
It is very difficult to visualize it in mind until starting to drawing some diagrams with simple example first. Here I start from simple example and induce the general solution.

Take a look at the simple example n = 1, 2 with m nodes.
Level 1 and Level 2 (n = 1, 2) with nodes


Take a look at the general example with n levels and m modes.
n levels and m nodes







































Take a look at the extra connection between any level.
A Jump Connection between Level i and Level j






































Summary of the solution:
    - f(0) = 1
    - f(1) = m
    - f(n) = m*f(n-1) = pow(m, n);
    - f(i, j) = f(i-1) * f(m-j) = pow(m, n+i-j-1), where i < j and i and j <= n.(a jump connection i, j)




Tuesday, 21 July 2015

Data Structure and Algorithm - Find Tree Path Given A Sum (Amazon Interview Question)

1. Problem Description
- emptycup 15 days ago in India | Report Duplicate | Flag 
"

2. Analysis
This is a typical  tree search problem. Both BFS and DFS can be used. Assuming that everyone is clear, BFS and DFS are nearly using the same code except that BFS uses queue to take the children nodes and DFS uses stacks to take the children node. It is easy to distinguish this because BFS will traverse the children at lower level to the deep level but DFS will traverse from the deepest level upwards. 

For this particular problem the only change we need to do comparing with normal tree traversal is to take the sum value towards its direct parent together with its node pointer into the queue or stack for BFS and DFS respectively.

3. C++ Implementation
Find the number of path only
- Assume that all the values are all positive
// ********************************************************************************
#include <queue>
#include <stack>

struct TreeNode
{
    int value;
    TreeNode* left;
    TreeNode* right;
};

struct NodeSumPair
{
    TreeNode* node;
    size_t    sum; // sum from root to its parent node
    NodeSumPair(TreeNode* curNode, size_t curSum)
        : node(curNode)
        , sum(curSum)
    {}
};

size_t FindNumOfPathsDFS(TreeNode* root, size_t sum)
{
    std::stack<NodeSumPair> curStack;
    size_t curSum = 0;
    curStack.push(NodeSumPair(root, curSum));
    size_t result = 0;
    while (!curStack.empty()) {
        const NodeSumPair& curPair = curStack.top();
        if (curPair.node) {
            curSum = curPair.sum + curPair.node->value;
            if (curSum == sum) {
                ++result; // continue because of all positive numbers
                continue;
            }
            else if (curSum > sum) {
                continue;
            }

            curStack.push(NodeSumPair(curPair.node->right, curSum));
            curStack.push(NodeSumPair(curPair.node->left, curSum));
        }
        curStack.pop();
    }

    return result;
}

size_t FindNumOfPathsBFS(TreeNode* root, size_t sum)
{
    std::queue<NodeSumPair> curQueue;
    size_t curSum = 0;
    curQueue.push(NodeSumPair(root, curSum));
    size_t result = 0;
    while (!curQueue.empty()) {
        NodeSumPair curPair = curQueue.front();
        if (curPair.node) {
            curSum = curPair.sum + curPair.node->value;
            if (curSum == sum) {
                ++result; // continue because of all positive numbers
                continue;
            }
            else if (curSum > sum) {
                continue;
            }

            curQueue.push(NodeSumPair(curPair.node->left, curSum));
            curQueue.push(NodeSumPair(curPair.node->right, curSum));
        }
        curQueue.pop();
    }
    
    return result;
}
// ********************************************************************************

Return the path as well
- Assume all positive number
- Change the tree structure to take parent node in the tree node
// ********************************************************************************
#include <queue>
#include <stack>

struct TreeNode
{
    int value;
    TreeNode* parent; // go upwards to find the path
    TreeNode* left;
    TreeNode* right;
};

struct NodeSumPair
{
    TreeNode* node;
    size_t    sum;
    NodeSumPair(TreeNode* curNode, size_t curSum)
        : node(curNode)
        , sum(curSum)
    {}
};

std::vector<TreeNode*> FindNumOfPathsDFS(TreeNode* root, size_t sum)
{
    std::stack<NodeSumPair> curStack;
    size_t curSum = 0;
    curStack.push(NodeSumPair(root, curSum));
    std::vector<TreeNode*> result;
    while (!curStack.empty()) {
        const NodeSumPair& curPair = curStack.top();
        if (curPair.node) {
            curSum = curPair.sum + curPair.node->value;
            if (curSum == sum) {
                result.push_back(curPair.node); // node found
                continue;
            }
            else if (curSum > sum) {
                continue;
            }

            curStack.push(NodeSumPair(curPair.node->right, curSum));
            curStack.push(NodeSumPair(curPair.node->left, curSum));
        }
        curStack.pop();
    }

    return result;
}

std::vector<TreeNode*> FindNumOfPathsBFS(TreeNode* root, size_t sum)
{
    std::queue<NodeSumPair> curQueue;
    size_t curSum = 0;
    curQueue.push(NodeSumPair(root, curSum));
    std::vector<TreeNode*> result;
    while (!curQueue.empty()) {
        NodeSumPair curPair = curQueue.front();
        if (curPair.node) {
            curSum = curPair.sum + curPair.node->value;
            if (curSum == sum) {
                result.push_back(curPair.node); // node found
                continue;
            }
            else if (curSum > sum) {
                continue;
            }

            curQueue.push(NodeSumPair(curPair.node->left, curSum));
            curQueue.push(NodeSumPair(curPair.node->right, curSum));
        }
        curQueue.pop();
    }
    
    return result;
}
// ********************************************************************************

Return the path and no assumption
- The value can be any negative, 0 or positive
// ********************************************************************************
#include <queue>
#include <stack>

struct TreeNode
{
    int value;
    TreeNode* parent;
    TreeNode* left;
    TreeNode* right;
};

struct NodeSumPair
{
    TreeNode* node;
    size_t    sum;
    NodeSumPair(TreeNode* curNode, size_t curSum)
        : node(curNode)
        , sum(curSum)
    {}
};

std::vector<TreeNode*> FindNumOfPathsDFS(TreeNode* root, size_t sum)
{
    std::stack<NodeSumPair> curStack;
    size_t curSum = 0;
    curStack.push(NodeSumPair(root, curSum));
    std::vector<TreeNode*> result;
    while (!curStack.empty()) {
        const NodeSumPair& curPair = curStack.top();
        if (curPair.node) {
            curSum = curPair.sum + curPair.node->value;
            if (curSum == sum) {
                result.push_back(curPair.node);
            }
            // continue traversal 
            curStack.push(NodeSumPair(curPair.node->right, curSum));
            curStack.push(NodeSumPair(curPair.node->left, curSum));
        }
        curStack.pop();
    }

    return result;
}

std::vector<TreeNode*> FindNumOfPathsBFS(TreeNode* root, size_t sum)
{
    std::queue<NodeSumPair> curQueue;
    size_t curSum = 0;
    curQueue.push(NodeSumPair(root, curSum));
    std::vector<TreeNode*> result;
    while (!curQueue.empty()) {
        NodeSumPair curPair = curQueue.front();
        if (curPair.node) {
            curSum = curPair.sum + curPair.node->value;
            if (curSum == sum) {
                result.push_back(curPair.node);
            }
            // continue traversal
            curQueue.push(NodeSumPair(curPair.node->left, curSum));
            curQueue.push(NodeSumPair(curPair.node->right, curSum));
        }
        curQueue.pop();
    }
    
    return result;
}
// ********************************************************************************

Friday, 19 June 2015

Data Structure and Algorithm - Find the Maximal Non-Overlapping Subset (Microsoft Interview Question)

1. Problem Description
This is a Microsoft interview question for interns from careercup. Here is the original thread,

Find a maximum subset of S such that no pair of intervals in S' overlaps ?"

2. Analysis
Given a set S = {(starti, endi)} where i is from 1 to n, the task is to find the largest subset whose elements are not overlapped with each other. For instance {(1, 3), (2, 5), (4, 6), (7, 8)}, the largest subset is {(1, 3), (4, 6), (7, 8)}. As the original problem description is not very clear about the two ends. In this case in this blog entry assume that the interval has open two ends - exclusive for lower bound and upper bound. 

what does the result, S', look like?
All the elements in S' should not overlapp. Assuming that the size of S' as m, we sort the lower bounds and upper bounds as
    - {LB1, LB2, LB3, .... LBm} and
    - {UB1, UB2, UB3 .... UBm}
The following property should hold:
    - LB1 <  LB2 < LB3 ... < LBm
    - UB1 <  UB2 < UB3 ... < UBm
    - LB1 < UB1 <= LB2 < UB2 <= LB3 ... <= LBm < UBm
Here UBi <= LBi+1, it allows "=" because two ends are excluded. For instance (1, 3) and (3, 4) are regarded as not overlapped.

At the same time S' has to be one of subsets that have the largest elements. For instance {(1, 4), (3, 5), (7, 8)},  there are two solutions: {(1, 4), (7, 8)} or {(3, 5), (7, 8)}. S' has to be one of these solutions.
How to find (one of) the largest solution(s), we will discus later in this section.

How to detect the overlap between intervals?

The basic idea is to sort the lower bounds and upper bounds, find out how many the interval's lower bound and upper bound sweep through, and calculate the difference - which represents how many this interval overlaps with other intervals.

When find out the number of overlap of each interval, the sum of the number of overlap is always an even number. Assume the first interval overlaps with other intervals as OV1, represent the rest together as {OV1, OV2, ..., OVn}, 
    - sigma(OVi) is an even number, where i is from 1 to n,
Each overlap is counted once by two intervals.It is symmetric. For instance {(1, 4), (2, 3), these two intervals are overlapped. Each interval has an overlap number of 1 and sum them together as 2.

Now let's take another look at S' - the properties it holds. All its elements do not overlap with each other. Therefore the following statement holds
    - sigma(OVi) = 0, where i is from 1 to m and OVi >= 0.

How to guarantee S' the largest?
Let's take a look where we start and where we finish. Start with S with sigma(OVi) >= 0 (even number), where i's range is [1, n] and end with S' with sigma(OVj) = 0, where j's range is [1, m]. The objective function is quite simple max(m), It is equivalent to find the largest subset S' whose elements do not overlap.

Redefine the object function as 
    - min(n - m), where n is a const number given to the size of S
Rather to find the "m", the idea is to remove the least elements/intervals in S to reach S' and use steepest descent algorithm as guider to remove intervals from S.

How does it work with steepest descent?  
Now we know how to use sweep line algorithm to find the number of overlap for each intervals as OV1, OV2, ..., OVn for S and sigma(OVi) as SGn. Start with the interval with max(OVi),  as MAXOVn and remove it from S. Then the left set as Sn-1 have total overlap as SGn - 2*MAXOVn. Then find the number of overlap for Sn-1 and remove the intervals with maximal the number of overlap. Repeat this process until the sum of overlaps reach 0 (reach S' with sigma(OVj) = 0).
    - The cost function of steepest descent: the number of overlap of interval
    - The step of steepest descent: 1 - each time remove one interval

3. Solution
pseudo-code
1. Sort the lower bounds and upper bounds as LB and UB
2. Use sweep line algorithm to detect the number of overlap for each interval
    2.1 Track the interval with largest number of overlap during this process
    2.2 Track the sum of overlap
    2.3 For the interval with 0 overlap
        2.3.1 Remove interval's lower/upper bound from LB and UB
        2.3.2 Move the interval to S'
        2.3.3 Remove it from S 
3. Check sum of overlap
    3.1 Go to Step 4 if it is more than 0
    3.2 Go to Step 6 if it is equal to 0
4. steepest descent: remove the interval with the largest number of overlap from S
5. Remove its lower/upper bound from LB and UB and go to Step 2
6. S' is the result and terminate the program

Solution complexity
Sorting lower bound and upper bound
    - O(NlogN)
Sweep line algorithm to find the number of interval
    - O(NlogN)
    - Should be less than this because interval with 0 overlap removed from LB and UB
Repeat sweep line algorithm for N-M times as
    - N*logN + (N-1)*log(N-1) + ... + (N-M)*log(N-M)
    - Should be less than this because interval with 0 overlap removed from LB and UB
Space complexity
    - O(N)

Example

Example: find the maximal non-overlapping subset

Monday, 8 June 2015

C++11 Features - Mutex and Deadlock

This is more or less a reading note of Chapter 3 of "Concurrency IN ACTION - Practical Multithreading" by Anthony Williams. As I have discussed threading - deadlock and how to ensure one instance in singleton in my previous blog entries, I cherry-pick up a few things that would be beneficial for these two issues in C++11 features.

1. std::lock_guard and std::mutex
This is one of C++ RAII techniques to solve one serious issue. I have discussed a few concrete scenarios that use RAII to prevent serious issues like memory leak, left open file handler and etc in this long entry, RAII.

We are facing the same dangers when designing multithreading code, for instance not unlocking the mutex in some corner code path or not unlocking the mutex when exception thrown. Unlocking the mutex will surely cause deadlock when try to acquire mutex in the next call. std::lock_guard is the solution to prevent these issues. It uses RAII techniques and the mutex associated with it will be automatically unlocked when std::lock_guard object goes out of scope.

C++11 also provides a few variants that are combined with other C++11 features. std::unique_lock to combine with the std::move feature and std::shared_lock to combine the resource management and shared owership.

2. std::shared_mutex and std::shared_lock
This pair are perfect for reader-writer mutex. If anyone familiar with QT mutex or boost mutex, understanding them is not a big deal. It is perfectly designed for one writer thread and multiple reader threads. In order to remove the bottleneck of reading threads, it should just give green lights to reader threads if shared resource is not acquired by the writer thread. Otherwise wait until the writer thread finishes its business.

3. std::lock
This is the biggest cherry I found in this chapter. I have discussed how to avoid deadlock of multiple level/nested locking access in Threading - Deadlock. The techniques are to avoid the circle locking in the design stage and to always lock multiple mutexes in the certain order.

std::lock provides a solution to lock multiple mutexes all-in-once. It takes N locks as parameters and always lock them at once. It is a nothing-or-all operation. Either all N locks are locked at once or none of them is locked if attempt fail.

4. std::call_once
I have discussed the difficulty that we are facing to make sure the uniqueness of singleton in Design pattern - Singleton pattern. Now C++11 provides the exact tool to achieve this. It guarantees that the function passed into it will be only invoked once in multithreading applications.

It is particular important for lazy initialization of C++. Multiple threads race to initialize the data shared between them when they first time come into use. std::call_once will make the lazy initialization in multithreading much easier to be done.

5. Other thing worth mentioning
Still no solution to the deadlock caused by the mismanagement of threading. For instance two worker threading waiting for the counterpart to join. An excellent example (linked list operation) is provided to explain that design thread-safe code is not about the function itself but also its API design.

Thursday, 4 June 2015

Data Structure and Algorithm - Find the Overlapping Tasks of Job Schedules (Amazon Interview Question)

1. Problem Description
This is an Amazon interview question for software develop engineer from careercup. Here is the original thread,

"
Given set of job schedules with start and end time, write a function that returns indexes of overlapping sets. 

for ex :-
input -> [1,2][5,6][1,5][7,8][1,6]
return -> [0,1,2,4]".
2. Analysis
My solution to this problem is to find out all the overlapping intervals and then find each overlapping intervals its crossing tasks. In the above example the overlapping intervals are: [1, 6] and [7, 8]. Then find out which tasks they come cross. [1, 6] crosses the first, second, third and fifth tasks. and [7, 8] crosses the 4th task.

To find out how many overlapping intervals is very close to this Google interview question. The only difference is that this problem does not have CONTINUOUS case as the Google interview question. For example [1, 5] and [6, 9] in this case they are not overlapped but they are in Google interview question.

As I introduced the sweep line algorithm in this blog entry, Data Structure and Algorithm - Sweep Line Algorithm and I also use it solve this Google interview question. Here it works too to find out the overlapping intervals.

Problem:
Given N intervals, [X1, Y1], [X2, Y2], ..., [Xn, Yn], where
    - Xi and Yi are all integers with i's range from 1 to N, and
    - the intervals are all inclusive on both ends.

Properties of the Sorted Lower and Upper Bounds:
According to the blog entry, Data Structure and Algorithm - Sweep Line Algorithm, we group the lower bounds and the upper bounds, {X1, X2, ..., Xn} and {Y1, Y2, ..., Yn}. Now we sort both lower bounds and upper bounds in the ascending order as,
    - Lower bounds: {L1, L2, ..., Ln}
    - Upper bounds: {U1, U2, ..., Un}
Because the lower and upper bounds are sorted, the following properties hold,
    - L1 <= L2 <= ... <= Ln
    - U1 <= U2 <= ... <= Un
    - Li <= Ui, for any given i between 1 to N
    - L1 <= L2 <= ... <= Li <= Ui, for any given i betwen 1 to N

Detect the Overlap
Given the sorted lower and upper bounds,
    - Lower bounds: { L1, l2, ...,   Li, Li+1, ...., Ln}
    - Upper bounds: { U1, U2, ..., Ui, Ui+1, ..., Un]

Let's consider if Ui and Li+1 are overlapped, Based on the properties of the sorted lower and upper bounds, Ui is the largest value of {L1, U1, L2, U2, ..., Li, Ui} and Li+1 is the least value of { Li+1, Ui+1, Li+2, and Ui+2, ..., Ln, Un}. Therefore let's examine their relationship,
    - If Li+1 > Ui, then there is NO OVERLAP here,
        * An interval with upper bound of Ui should stop here.
        * A new interval with the lower bound of Li+1 should start here
    - Otherwise there is a OVERLAP

How to find out what tasks the overlapped intervals cross?
Use a hash map to contain the overlapped intervals. Use lower bound of the overlapped intervals as the key and a vector as the value to save the tasks that it crossed.
    - Key: int (lower bounds of overlapped intervals)
    - Value: std::vector<size_t> (to save the indices of tasks that this overlapped interval crosses)

Sort the lower bounds of all overlapped intervals as LB. Then go through the tasks. Search the lower bound of each task lb in LB and find the first value that, ul', is less or equal t ul. Then save the index of this task into the hash map with key value of ul'.

3. Solution
Example:
Let's take a look at the example: [1, 2], [5, 6], [1, 5], [7, 8], [1, 6]
The sorted lower and upper bounds are,
    - Lower bounds: {1, 1, 1, 5, 7}
    - Upper bounds: {2, 5, 6, 6, 8}
Here is the algorithm runs
    - Take 1 as the lower bound of the first interval
    - Ui = 2, Li+1 = 1, where  i = 1. it is a OVERLAP case and continue
    - Ui = 5, Li+1 = 1, where i = 2, it is a OVERLAP case and continue
    - Ui = 6, Li+1 = 5, where i = 3, it is a OVERLAP case and continue
    - Ui = 6, Li+1 = 7, where i = 4, it is NOT a OVERLAP case
        * The first interval stops her as [1, 6]
        * The second interval starts here with lower bound as 7
    - Reach the end, construct the second interval with the upper bound of Un, [7, 8]
Two overlapped intervals generated as [1, 6] and [7, 8]
Insert key 1 with empty vector and key 7 with empty vector into hash map,
Order the overlapped interval's lower bounds as {1, 7}. Go through the lower bound of each interval and find the first value (in this case {1, 7}) which it is equal to or greater than.
    - Task [1, 2]: the 1st value 1 >= 1 is 1. then append 0 into the value of Key 1 of hash map
    - Task [5, 6]: the 1st value 5 >= 1 is 1. then append 1 into the value of Key 1 of hash map
    - Task [1, 5]: the 1st value 1 >= 1 is 1. then append 2 into the value of Key 1 of hash map
    - Task [7, 8]: the 1st value  7 >= 7 is 7. then append 3 into the value of Key 7 of hash map
    - Task [1, 6]: the 1st value 1>= 1 is 1. then append 4 into the value of Key 1 of hash map
Go through the hash map,
    [0, 1, 2, 4] in one group
    [3] in one group

Pseudo-code:
    - 1. Group lower bounds and upper bounds as {X1, ..., Xn} and {Y1, ..., Yn}
    - 2. Sort lower and upper bounds in ascending order as {L1, ..., Ln} and {U1, ..., Un}
    - 3. Take LowerBound = L1 as the lower bound of the first interval
    - 4. Compare Li+1 and Ui where i ranges from 1 to N-1
        * 4.1 if Li+1 > Ui, then 
                 construct the interval with Ui as the upper bound, [LowerBound, Ui]
                 update LowerBound = Li+1
                 go to Step 4.3
        * 4.2 if i = N - 1, then
                 construct the interval with Un as the upper bound, [LowerBound, Un]
                 go to Step 5
        * 4.3 Increment i by 1 and repeat Step 4
    - 5. Sort the lower bounds of overlapped interval as LB
    - 6. construct hash map <int, std::vector<size_t>> with LB and empty vectors
    - 7. Go through the tasks list
        * Find the first value x which is <= the lower bound of this task
        * hashMap[x].push_back(indexOfTask)
        * Repeat 7 until exhaust the task list
    - 8. Go through the hash map. The value (std::vector) of each entry are grouped.         

This solution has O(NlogN) computation complexity and O(N) space complexity.


Saturday, 30 May 2015

Data Structure and Algorithm - Trie and Palindrome (Google Interview Question)

1.Problem Description


2. Analysis
Initially I was only thinking about the case given the example list - all the elements/strings have the same length. A simple solution is to use hash map.
    - Use the string as the key and the frequency of it appearing in the list as the value.
    - Inset the first element and set the frequency as 1.
    - From the second string on, try to find its reverse string in the hash map.
        * If found, then there is palindrome and increment the counter by the frequency.
        * Insert the string into the hash map
            ~ If already in the hasp map, then increment the frequency by 1
            ~ Otherwise insert and set the frequency as 1

Unfortunately this algorithm won't work for strings with varying length, for instance {a, aa, aaa, aaaa}, - the elements can have different length. In this example {a, aa, aaa, aaaa} all the combinations are palindrome and in total it has 6 combinations. The hash map solution does not work any more because all the strings are different keys.

Let's examine what properties they share.
The strings have the same length. It is quite simple. The reverse of the second string should be equal to the first string.
The strings have different length. Assume two string with size L1, L2, and L1 > l2. Then after reverse one of them. Then the first L2 should be same and the rest of L1-l2 should be palindrome as well. In the above example
    - "a" and "aaaa", the reverse of  "aaaa" is "aaaa".
        * The red part is the same and blue part is palindrome as well. 

In summary for any two given string s1 and s2 with length of L1 and L2, to decide if s1s2 is a palindrome follows, (s2' is the reverse string of s2)
    - Case 1: L1 = L2, then  s1 = s2'
    - Case 2: L1 >  l2  then s1.substr(0, L2) = s2' and s1.substr(L2 + 1, L1 - L2) is palindrome too.
    - Case 3: L1 < L2, then s1 = s2'.substr(0, L1) and s2.substr(L1 + 1, L2 - L1) is palindreom too.
3. Solution
Some contributors mentioned using Trie structure/algorithm to solve this issue. But none of them proposed how the data structure looks like exactly and how to do the searching/traversing and insertion operations.

Data Structure
In this case I assume all the strings given are made of letters only and are either in upper case or in lower case. Based on my original Trie data structure and  the fact that there might be duplicates of strings in the list according to the problem description, the data structure is modifies as,

constexpr unsigned char NUMBER_OF_LETTERS = 26
struct DictionaryTrie {
char oneLetter;
        size_t frequency;
DictionaryTrie* children[NUMBER_OF_LETTERS];
DictionaryTrie() {
str = NULL;
for (int i = 0; i != NUMBER_OF_LETTERS; ++i) {
children[i] = NULL;
}
}
};
Each node only takes one letter, "oneLetter" as data and records how many times this string appears in the list as "frequency".

How to insert a string into Trie?
When a string is inserted into Trie, only the last letter's "frequency" set to indicate how many times this string appears in the list. For instance,
    - "abc" inserted into a empty Trie as,
        * root->(a, 0)->(b, 0)->(c, 1)
    - "abcd" inserted into this Trie
        * root->(a, 0)->(b, 0)->(c, 1)->(d, 1)
    - "abc" inserted into the Trie again
        * root->(a, 0)->(b, 0)->(c, 2)->(d, 1)
    - "abef" inserted into the Trie
        * root->(a, 0)->(b, 0)->(c, 2)->(d, 1)
                                         |->(e, 0)->(f, 1)

Pseudo-code
So far we understand how the string is inserted into the Trie and how to decide if the combination of two strings (Section 2) is a palindrome. Here is the pseudo-code:
1. Initialize counter as 0
2. Insert the first string of the list into an empty Trie
3. Take the next string S and its reverse string S'
4. Traverse the Trie with S'
    4.1 If found a node with frequency > 0, then check S'  (check Section 2)
        - Increment counter by frequency and go to Step 5, if reach the end of S' too.
                                                                                          : Case 1 in Section 2
        - Check if the rest of S' is palindrome, if does not reach the end of S'
                                                                                          : Case 2 in Section 2
            * If yes, then increment counter by frequency and go to Step 4
            * If no, then go to Step 4
    4.2 If reach the end of S', record node X                      : Case 3 in Section 2
        - 4.2.1 Continue traverse the children of X
            * If found a node Y with frequency > 0, then
                ~ If between X and Y is palindrome,  increment counter by frequency and go to Step 4.2.1
                ~ If between X and Y is not palindrome, go to Step 4.2.1
            * Reach the end of Trie, go to Step 5
5. Insert S into Trie
    - If not exist, insert it and set the last letter's frequency as 1
    - If exist. increment the last letter's frequency by 1
6. Repeat Step 3,4 and 5 until exhaust the list

4. Example
Here is an example to show how the algorithm works.
    - The black color is the Trie structure
    - The red color is the newly inserted new string (tracing back to the root node) : Step 5
    - The green color is to detect the palindrome: Step 4
        * The reverse string is to compare with the Trie before inserting it
        * For instance in Step 5: "aabb" is to compare with the Trie in Step 4
    - The blue color is indicate the result
    - Case 3: 2, 3, 4
    - Case 1: 6 @ (a, 1) of level 1 (Trie at 5)
    - Case 2: 6 @ (a, 1) of level 2 (Trie at 5)
       
Example 1

5. Complexity
I don't think O(nk) is achievable and the worst case will be (n*(k^2)). Because there is a special case like this {a, aa, aaa, aaaa, aaaaa}.
When the algorithm does with "aaaaa", it will have to decide if "aaaa", "aaa", "aa" and "a" are palindrome. This operation is quite expensive as O(k^2).