Saturday, 24 January 2015

Dynamic Programming - List All Sub-sets with Sum Equal to Zero (Amazon Interview Question)

1. Problem Description
This is an Amazon Interview Qeustion for Dev Leads from careercup. The following is the original problem description of that thread.

"
Given a set of number eg {5,3,1,8, -8,-4} 
Print all subset which will sum up to ZERO 
for eg {3,1,-4} {5,3,-8}, {8,-8} 

Note : size of subset can be max 100 and element can be very big like 13 or 14 digit number
"

2. Analysis
Initially I thought that we could simply use brutal force to compute the POWER set of the given set. Please refer to Dynamic programming - Generate power set given a set. Then sum of the elements of each sub set and compare if the sum is equal to zero. The sub sets with the sum equal to zero are what we are hunting for. But this turns out impossible because the space complexity of the POWER set, which requires exponential space complexity, O(2^N), where N is the size of the given set. According to the problem description N could reach 100. It is impossible to cache the POWER SET.

In this blog I am going to present a brutal force DP solution of with O(2^N) computation complexity and O(N) space complexity, which N is the size of the given set. Now let's take a look at the sub problem.
Sub-problem:
Assume the given size of SET is N. Let's consider a sub-set of previous 0, ..., i-1 elements as Subset(i-1). Now we are encountering the i-th element. What we need to do,
    - Check if Subset(i-1) is what we are hunting for. If the sum of elements is equal to zero, print.
    - Do not take i-th element into this subset and considering the rest of elements in the given set
    - Take the i-the element int this subset and considering the rest of the elements in the given set.

Assumption:
    - Repetitive elements are considered different. You can view the print out are the index of elements. Therefor given a set {0, 0, 0}, it is considered that there are 7 different combinations
      {a0}, {a1}, {a2}, {a0, a1}, {a0, a2},{a1, a2} and {a0, a1, a2}, where a0 = a1 = a2 = 0.

3. C++ Implementation
// ********************************************************************************
// IMPLEMENTATION
// ********************************************************************************
#include <iostream>
#include <iterator>
#include <vector>

using SET = std::vector<int>;

void PrintOutSubSet(const SET& input,
                    const std::vector<size_t>& indicesOfSubset,
                    const size_t sizeOfSubset)
{
    if (sizeOfSubset == 0) {
        return;
    }

    std::cout << "{ " << input[indicesOfSubset[0]];
    for (size_t index = 1; index < sizeOfSubset; ++index) {
        std::cout << "; " << input[indicesOfSubset[index]];
    }
    std::cout << " }" << std::endl;
}

size_t PrintSubSetOfSummOfAllElementsEqualZero_DP(const SET& input,
                                                  const size_t indexOfNext,
                                                  std::vector<size_t>& indicesOfSubset,
                                                  const int sumOfSubset,
                                                  const size_t sizeOfSubset,
                                                  bool alreadyCounted)
{
    size_t count = 0;
    if (sumOfSubset == 0 && !alreadyCounted) {
        PrintOutSubSet(input, indicesOfSubset, sizeOfSubset);
        alreadyCounted = true;
        ++count;
    }

    if (indexOfNext < input.size()) {
        count += PrintSubSetOfSummOfAllElementsEqualZero_DP(input,
                                                            indexOfNext + 1,
                                                            indicesOfSubset,
                                                            sumOfSubset,
                                                            sizeOfSubset,
                                                            alreadyCounted);
   
        indicesOfSubset[sizeOfSubset] = indexOfNext;
        count += PrintSubSetOfSummOfAllElementsEqualZero_DP(input,
                                                            indexOfNext + 1,
                                                            indicesOfSubset,
                                                            sumOfSubset + input[indexOfNext],
                                                            sizeOfSubset + 1,
                                                            false);
    }
    return count;
}

size_t PrintSubSetOfSummOfAllElementsEqualZero(const SET& input)
{
    auto SIZE_OF_INPUT = input.size();
    std::vector<size_t> indicesOfSubset(SIZE_OF_INPUT, 0);
    size_t count = 0;
    for (size_t index = 0; index < SIZE_OF_INPUT; ++index) {
        indicesOfSubset[0] = index;
        count += PrintSubSetOfSummOfAllElementsEqualZero_DP(input,
                                                            index + 1,
                                                            indicesOfSubset,
                                                            input[index],
                                                            1,
                                                            false);
    }

    return count;
}

// ********************************************************************************
// TEST
// ********************************************************************************
#include <cassert>
void TestPrintSubSetOfSummOfAllElementsEqualZero()
{
    {
        const SET testSet = { 5, 3, 1, 8, -8, -4 };
        assert(PrintSubSetOfSummOfAllElementsEqualZero(testSet) == 4);
    }

    {
        const SET testSet = { 0, 0, 0 , 0 };
        assert(PrintSubSetOfSummOfAllElementsEqualZero(testSet) == 15);
    }

    {
        const SET testSet = { -1, 1 };
        assert(PrintSubSetOfSummOfAllElementsEqualZero(testSet) == 1);
    }

    {
        const SET testSet = { -1, 1, -3, 3 };
        assert(PrintSubSetOfSummOfAllElementsEqualZero(testSet) == 3);
    }

    {
        const SET testSet = { -1, 1, -3, 3, -4, 4 };
        assert(PrintSubSetOfSummOfAllElementsEqualZero(testSet) == 9);
    }

    {
        const SET testSet = { -1, 1, -2, 2, -3, 3, -4, 4 };
        assert(PrintSubSetOfSummOfAllElementsEqualZero(testSet) == 25);
    }

    {
        const SET testSet = { -1, 1, -2, 2, -3, 3, -4, 4, -5, 5 };
        assert(PrintSubSetOfSummOfAllElementsEqualZero(testSet) == 75);
    }
}
// ********************************************************************************

Wednesday, 21 January 2015

Data Structure and Algorithm - Linked List: Palindrome II (Amazon Interview Question)

1. Problem Description
This is an Amazon Interview Question Inerns from careercup. Here is the original description of this thread.

"Write a code to find if a link list is palindrome . Linklist consists variable string at each node. for eg 
a->bcd->ef->g->f->ed->c->ba 

The above given linklist is palindrome and func shd return - True"

2. Solution
I have discussed one solution to detect palindrome in the data structure of linked list on Data Structure and Algorithm - Linked List: Palindrome, where has the data type of a single char and provides a solution of computation complexity of O(N) and space complexity of O(1). Comparing with this problem the difference is the data type (in this problem std::string) that contains varying number of chars.

The solution should be similar with with the one on Data Structure and Algorithm - Linked List: Palindrome, except the way to partition the linked list into two halves.
Here is the pseudo
    - Traverse the list to find the total number of chars
    - Traverse the list again to find the linked list node that contains the mid character
    - Split the linked to two
        *) The left half: from the head to the linked list node that has the mid character
        *) The right half: the rest of the linked list
    - Reverse the left half.
    - Compare two lists if they are holding the same data and capture the result
    - Reverse the left half
    - combine the two lists together
    - Return the result of comparison
The solution has computation complexity of O(N) and space complexity of O(1). Keep in mind that this solution should be working for the problem on Data Structure and Algorithm - Linked List: Palindrome as well only a bit of less efficiently to find the node to split into two halves.

3. C++ Implementation
Based on the implementation of Data Structure and Algorithm - Linked List: Palindrome, a template specialization function of type "std::string" is defined and implemented as the solution of this interview question.
// ********************************************************************************
// IMPLEMENTATION
// ********************************************************************************
class LinkedListPalindromeDetector
{
public:
    // ......
    template<typename T>
    static bool IsPalindrome(LinkedListElement<T>* head)
    {
        // ......
    }


    //http://www.careercup.com/question?id=4826931360956416
    template<>
    static bool IsPalindrome(LinkedListElement<std::string>* head)
    {
        // 1. empty list
        if (head == nullptr) {
            return false;
        }

        using LinkedListString = LinkedListElement<std::string>*;
        LinkedListString cur = head;

        // 2. not empty list
        size_t totalCharacters = 0;
        while (cur != nullptr) {
            totalCharacters += cur->data.size();
            cur = cur->next;
        }

        cur = head;
        LinkedListString prev = cur;
        size_t numOfCharactersToPrev = 0;
        size_t numOfCharactersToCur = 0;
        auto oddNumOfCharacters = (totalCharacters & 1) > 0;
        size_t halfSize = oddNumOfCharacters?
                          ((totalCharacters >> 1) + 1) : (totalCharacters >> 1);
        while (numOfCharactersToCur < halfSize) {
            numOfCharactersToPrev = numOfCharactersToCur;
            numOfCharactersToCur += cur->data.size();
            prev = cur;
            cur = cur->next;
        }

        // split the LinkedList
        prev->next = nullptr;
        auto leftHalf = head;
        auto rightHalf = cur;

        // reverse the 2nd half
        LL_Reverse(&leftHalf);

        std::string* leftStr = &(leftHalf->data);
        leftHalf = leftHalf->next;
        std::string* rightStr = leftStr;
        size_t leftStrIndex = halfSize - numOfCharactersToPrev;
        size_t rightStrIndex = leftStrIndex;
        oddNumOfCharacters ? leftStrIndex -= 1 : leftStrIndex = leftStrIndex;

        bool match = true;
        do{
            if (rightStrIndex >= rightStr->size()) {
                if (rightHalf == nullptr) {
                    match = false;
                    break;
                }
                rightStr = &(rightHalf->data);
                rightHalf = rightHalf->next;
                rightStrIndex = 0;
            }

            if (leftStrIndex == 0) {
                if (leftHalf == nullptr) {
                    match = false;
                    break;
                }
                leftStr = &(leftHalf->data);
                leftHalf = leftHalf->next;
                leftStrIndex = leftStr->size();
            }

            while (leftStrIndex > 0 && rightStrIndex < rightStr->size()) {
                if ((*leftStr)[leftStrIndex - 1] != (*rightStr)[rightStrIndex]) {
                    match = false;
                    break;
                }

                --leftStrIndex;
                ++rightStrIndex;
            }

            if (!match) {
                break;
            }
        } while (leftHalf != nullptr || rightHalf != nullptr);

        // reverse first half
        auto prevCopy = prev;
        LL_Reverse(&prev);
        // merge two halfs
        prevCopy->next = cur;

        return match;
    }

};

#endif
// ********************************************************************************
// TEST
// ********************************************************************************
using LinkedListString = LinkedListElement<std::string>;
void TestLinkedListPalindromWithStringCase1()
{
    LinkedListString* head = nullptr;
    assert(LinkedListPalindromeDetector::IsPalindrome(head) == false);
}

void TestLinkedListPalindromWithStringCase2()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("ABCDEFGFEDCBA"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == true);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase3()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("AABCDEFGFEDCBBD"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == false);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase4()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("ABCDEFFEDCBA"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == true);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase5()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("AABCDEFFEDCBAB"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == false);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase6()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("GHIJKJIHGFEDCBA"));
    LL_PushFront(&head, std::string("EF"));
    LL_PushFront(&head, std::string("CD"));
    LL_PushFront(&head, std::string("B"));
    LL_PushFront(&head, std::string("A"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == true);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase7()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("GHIJKJIHGFEDCBA"));
    LL_PushFront(&head, std::string("EF"));
    LL_PushFront(&head, std::string("CD"));
    LL_PushFront(&head, std::string("B"));
    LL_PushFront(&head, std::string("B"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == false);

    LL_Delete(&head);
}


void TestLinkedListPalindromWithStringCase8()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("GHIJKJIHGFEDCBA"));
    LL_PushFront(&head, std::string("EF"));
    LL_PushFront(&head, std::string("CD"));
    LL_PushFront(&head, std::string("B"));
    LL_PushFront(&head, std::string("A"));
    LL_PushFront(&head, std::string("A"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == false);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase9()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("XYZ"));
    LL_PushFront(&head, std::string("123"));
    LL_PushFront(&head, std::string("MNLOP"));
    LL_PushFront(&head, std::string("789"));
    LL_PushFront(&head, std::string("AABCDEFFEDCBAA"));
    LL_PushFront(&head, std::string("87"));
    LL_PushFront(&head, std::string("9"));
    LL_PushFront(&head, std::string("LNM"));
    LL_PushFront(&head, std::string("PO"));
    LL_PushFront(&head, std::string("321"));
    LL_PushFront(&head, std::string("ZYX"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == true);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase10()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("XYZ"));
    LL_PushFront(&head, std::string("123"));
    LL_PushFront(&head, std::string("MNLOP"));
    LL_PushFront(&head, std::string("789"));
    LL_PushFront(&head, std::string("AABCDEFGFEDCBAA"));
    LL_PushFront(&head, std::string("87"));
    LL_PushFront(&head, std::string("9"));
    LL_PushFront(&head, std::string("LNM"));
    LL_PushFront(&head, std::string("PO"));
    LL_PushFront(&head, std::string("321"));
    LL_PushFront(&head, std::string("ZYX"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == true);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase11()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("XYZ"));
    LL_PushFront(&head, std::string("123"));
    LL_PushFront(&head, std::string("MNLOP"));
    LL_PushFront(&head, std::string("789"));
    LL_PushFront(&head, std::string("AABCDEFFEDCBAA"));
    LL_PushFront(&head, std::string("87"));
    LL_PushFront(&head, std::string("9"));
    LL_PushFront(&head, std::string("LNM"));
    LL_PushFront(&head, std::string("PO"));
    LL_PushFront(&head, std::string("321"));
    LL_PushFront(&head, std::string("zYX"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == false);

    LL_Delete(&head);
}

void TestLinkedListPalindromWithStringCase12()
{
    LinkedListString* head = nullptr;
    LL_PushFront(&head, std::string("XYz"));
    LL_PushFront(&head, std::string("123"));
    LL_PushFront(&head, std::string("MNLOP"));
    LL_PushFront(&head, std::string("789"));
    LL_PushFront(&head, std::string("AABCDEFGFEDCBAA"));
    LL_PushFront(&head, std::string("87"));
    LL_PushFront(&head, std::string("9"));
    LL_PushFront(&head, std::string("LNM"));
    LL_PushFront(&head, std::string("PO"));
    LL_PushFront(&head, std::string("321"));
    LL_PushFront(&head, std::string("ZYX"));

    assert(LinkedListPalindromeDetector::IsPalindrome(head) == false);

    LL_Delete(&head);
}
// ********************************************************************************

Saturday, 17 January 2015

Dynamic Programming - Stock Best Buy and Sell Problem: Google Interview Question

1. Problem Description
This is a Google Interview Question for Software Engineer/Developer from careercup. The following is the original description of the thread. 

"given a vector of integers, v[i] represent the stock price on day i. Now you may do at most K transactions. you must sell your stock before you buy it again and that means you can NOT have two stocks at the same time. write a program to find max profit you can get."

2. Mathematical Analysis
As some of contributors on this thread have pointed out, the problem is not very clear and people have different understanding, for instance if holding stock initially, what one transaction means and so on. Here I would like to make the following assumptions and be absolutely clear about what I am discussing in this blog entry
Assumptions:
    - Don't hold stack at the beginning
    - Hold cash only at the beginning
    - One transaction: buy-then-sell
    - K transaction: do buy-then-sell K times
    - Hold cash only and no stocks at the end of day
    - Data V - v[i] represents the price/ticks in the timeline.

Let's give the data a time scale, for instance intra-day stock ticks. (Obviously this falls into a technical trading techniques). When the price goes down to the local bottom, this is a buying opportunity. And when the price goes up to the local top, this is a selling opportunity. Combine this local bottom and local top together is an opportunity to make some profit. Or in this question the price going from the local bottom to the local top is a candidate of transaction to make profit. 

As the price fluctuates, each bottom-top rising is the candidate of transaction to make profit. Assume that the data given shows N opportunities to make profit and we are given K transactions. The problem is clearly to make the most of the profit of K transactions out of N opportunities. And this is maximal optimization problem.

The input data V represents the price/ticks in the time line. And assume along the time V presents N opportunities to make profit. Let's represent each opportunity (the candidate of transactions) with a bottom price/tick and top price/tick as (Bi, Ti), where i is within [0, N-1]. They are constrained on time, Time at B0 is earlier than Time at T0, which is earlies than B1, which is earlier than T1, .... Except this constraint B0 < T0, T0 > B1, B1 < T1, T1 > B2, ......

Analysis:
When K >= N, then it is simple because this is maximization problem and we would like to take each opportunities to take profit. So the maximal profit is Sigman(Ti-Bi), where i is from 0 to N-1.

When K < N, then it is not that simple as some contributors suggested that simply pick up the most K profitable opportunities from N and then this problem can be solved by heap sort with K top values. Why is it not simple? Because when K < N, the neighbor opportunities can be combined together to become even bigger opportunities, which makes more profit than both individually. For instance, N=2 opportunities (1, 3) and (2, 5), and K=1 transaction. Then the profit we can make is not 5-2=3, but 5-1=4. In this example we need to find global bottom and top to make the most profit. Therefore when K < N, we have to explore all the combinations of all possible merging of neighbor opportunities to get the most profit. For instance N opportunities and K=N-1 transactions, we need to check each combination of 2 neighbor opportunities.
    - Merge 0 and 1 to get N-1 opportunities  and N-1 transactions
    - Merge 1 and 2 to get N-1 opportunities  and N-1 transactions 
    ......
    - Merge N-2 and N-1 to get N-1 opportunities  and N-1 transactions
    - Take the maximal value above N-1 combinations.

3. Dynamic Programming Solution
Here I would like to discuss how to solve the above K<N case  via dynamic programming. Assume that we have N opportunities (Bi, Ti), where i is within [0, N-1] and have K transactions where K is less than N.
    - F(N, K) = T0 - B0 + F(N-1, K-1), take first opportunity as one transaction
    - F(N, K) = Max(T0, T1) - Min(B0, B1) + F(N-2, K-1), take the first two opportunities combined 
        as one opportunity
    - F(N, K) = Max(T0, T1, T2) - Min(B0, B1, B2) + F(N-3, K-1), take the fist 3 opportunities 
        combined as one opportunity
    ......
    - F(N, K) = Max(Tj) - Min(Bj) + F(N-K+1, K-1), take the first (N-K+1) opportunities combined as 
        one opportuinity, where j is within [0, N-K]
And this is an maximization problem and the maximal value above should be returned.
    - F(N, K) = Max{Max(Tj) - Min(Bj) + F(N-i, K-1)},
                       where i is within [0, N-K] and j is within [0, i-1]

Corner case:
When K >= N, return sum of profits of all opportunities.

4. Complexity Analysis
When K >= N, it takes linear time to compute the sum of profits of all opportunities. Let's take a look at the case of K < N.
    - K = N-1; combine two opportunities next to each other, we have N-1 opportunities and K=N-1
        N' = (N-1), then K = N' and it's a corner case
    - K = N-2,
        * Combine 2 opportunities next to each other, we have N-1 opportunities and K = N-2,
            N'=(N-1) and K = N'-1         
        * Now combine 2 neighbor opportunities out of N' into one opportunity, we have N'-1 
            opportunities. N'' = (N'-1) = (N-2) = K, It's a corner case now
        * There are repetitive cases. For instance combine 1,2 and 3 together have two choice, 1, 2 
            first then 3, or 2, 3 first and then 1.
        * So all together we need to examine (N-1)(N'-1)/2 = (N-1)(N-2)/2 possible choice to find the 
            maximal return.
   - K = N- 3,
        * Combine opportunities in 3 steps, (N-1)(N-2)(N-3)
        * Repetitive cases:  combine 1, 2, 3, 4 together, then we have 6 choices
            > 1,2 first, then 3 and then 4
            > 3, 4 first then 2 and then 1
            > 1, 2 first then 3, 4 second, then combine two combinations 
            > 3, 4 first then 1, 2 second, then combine two combinations
            > 2, 3 first then 1 and then 4
            > 2, 3 first then 4 and then 1
         * Complexity: (N-1)(N-2)(N-3)/6 = (N-1)(N-2)(N-3)/(3*2*1)
    ......(use induction theory)
    - K = N-X. The complexity will be (N-1)(N-2)...(N-X)/X! = N!/(N*(N-X-1)!*X!) 
        = N!/(N*(K-1)!*(N-K)!) = (K/N)*(N!/(K!*(N-K)!) = K/N * C(N,K), where
        C(N, K) is called combination with value of N!/(K!*(N-K)!) (See combination[1][2])

The above analysis also provides good evidence to use caching technique to prevent the repetitive cases computing again.

5. C++ Implementation
    - Dynamic programming solution
    - Use caching technique
// ********************************************************************************
// IMPLEMENTATION
// ********************************************************************************
#include <limits>
#include <numeric>
#include <unordered_map>
#include <vector>

struct BuySellEntry
{
    BuySellEntry()
    {}

    BuySellEntry(size_t buy, size_t sell) :
        ticksToBuy(buy), ticksToSell(sell)
    {}

    BuySellEntry(const BuySellEntry& other) :
        ticksToBuy(other.ticksToBuy),
        ticksToSell(other.ticksToSell)
    {}

    BuySellEntry(const BuySellEntry&& other) :
        ticksToBuy(std::move(other.ticksToBuy)),
        ticksToSell(std::move(other.ticksToSell))
    {}

    size_t ticksToBuy = 0;
    size_t ticksToSell = 0;
};

struct HashKeyPair
{
    HashKeyPair(size_t i, size_t k) :
        index(i), kAttemp(k)
    {}

    bool operator==(const HashKeyPair& rhs) const {
        return index == rhs.index && kAttemp == rhs.kAttemp;
    }

    size_t index{ std::numeric_limits<size_t>::max() };
    size_t kAttemp{ std::numeric_limits<size_t>::min() };
};

struct HashKeyPairHashFunc
{
    size_t operator()(const HashKeyPair& hKey) const {
        return std::hash<size_t>()(hKey.index) ^ 
               std::hash<size_t>()(hKey.kAttemp);
    }

    bool operator()(const HashKeyPair& lhs, const HashKeyPair& rhs) const {
        return lhs == rhs;
    }
};

using HashMap = std::unordered_map<HashKeyPair, size_t, HashKeyPairHashFunc, HashKeyPairHashFunc>;

std::vector<BuySellEntry> DetectBuySellEntries(const std::vector<size_t> ticks, 
                                               std::vector<size_t>& profit)
{
    const auto sizeOfData = ticks.size();
    std::vector<BuySellEntry> BSE;
    if (sizeOfData == 0) {
        return BSE;
    }

    BSE.reserve(sizeOfData);
    profit.clear();
    profit.reserve(sizeOfData);
    size_t buy;
    for (size_t index = 1; index < sizeOfData; ++index) {
        if (ticks[index] > ticks[index - 1]) {
            buy = ticks[index - 1];
            for (; index < sizeOfData; ++index) {
                if (index == (sizeOfData - 1) || ticks[index] > ticks[index + 1]) {
                    BSE.push_back(BuySellEntry{ buy, ticks[index] });
                    profit.push_back(ticks[index] - buy);
                    break;
                }
            }
        }
    }

    return BSE;
}

size_t BestBuySell_DP(const std::vector<BuySellEntry>& BSE,
                      const std::vector<size_t>& profit,
                      const size_t startIndex,
                      const size_t kAttemps,
                      HashMap& cache)
{
    if (BSE.empty() || kAttemps == 0 || startIndex == BSE.size()) {
        return 0;
    }

    if (kAttemps >= (BSE.size() - startIndex)) {
        return std::accumulate(profit.begin() + startIndex, profit.end(), 0);
    }

    // cached?
    const HashKeyPair key = { startIndex, kAttemps };
    {
        auto foundCIter = cache.find(key);
        if (foundCIter != cache.end()) {
            return foundCIter->second;
        }
    }

    // DP
    auto minBuyTick = std::numeric_limits<size_t>::max();
    auto maxSellTick = std::numeric_limits<size_t>::min();
    auto totalProfit = std::numeric_limits<size_t>::min();
    size_t tempProfit;
    for (auto index = startIndex; index < BSE.size(); ++index) {
        if (BSE[index].ticksToBuy < minBuyTick) {
            minBuyTick = BSE[index].ticksToBuy;
        }
        if (BSE[index].ticksToSell > maxSellTick) {
            maxSellTick = BSE[index].ticksToSell;
        }

        tempProfit = maxSellTick - minBuyTick +
            BestBuySell_DP(BSE, profit, index + 1, kAttemps - 1, cache);
        if (tempProfit > totalProfit) {
            totalProfit = tempProfit;
        }
    }

    // cache it
    cache.insert(HashMap::value_type(key, totalProfit));

    return totalProfit;
}

size_t BestBuySell(const std::vector<size_t>& ticks, const size_t kAttemps)
{
    if (ticks.empty() || kAttemps == 0) {
        return 0;
    }

    std::vector<size_t> profit;
    auto BSE = DetectBuySellEntries(ticks, profit);
    HashMap cache;

    return BestBuySell_DP(BSE, profit, 0, kAttemps, cache);
}
// ********************************************************************************
// TEST
// ********************************************************************************
#include <cassert>
void TestCases()
{
    {
        std::vector<size_t> ticks = { 1, 2, 3, 4, 5, 6, 7 };
        // BuySellEntries: (1, 7)
        assert(BestBuySell(ticks, 0) == 0);
        assert(BestBuySell(ticks, 1) == 6);
        assert(BestBuySell(ticks, 2) == 6);
    }
    
    {
        std::vector<size_t> ticks = { 1, 2, 3, 4, 2, 6, 7 };
        // BuySellEntries: (1, 4), (2, 7)
        assert(BestBuySell(ticks, 0) == 0);
        assert(BestBuySell(ticks, 1) == 6);
        assert(BestBuySell(ticks, 2) == 8);
        assert(BestBuySell(ticks, 3) == 8);
    }

    {
        std::vector<size_t> ticks = { 1, 2, 3, 4, 2, 6, 7, 2, 4, 6};
        // BuySellEntries: (1, 4), (2, 7), (2, 6)
        assert(BestBuySell(ticks, 0) == 0);
        assert(BestBuySell(ticks, 1) == 6);
        assert(BestBuySell(ticks, 2) == 10);
        assert(BestBuySell(ticks, 3) == 12);
        assert(BestBuySell(ticks, 4) == 12);
    }

    {
        std::vector<size_t> ticks = { 1, 2, 3, 4, 2, 6, 7, 2, 4, 6 , 1, 5, 9};
        // BuySellEntries: (1, 4), (2, 7), (2, 6), (1, 9)
        assert(BestBuySell(ticks, 0) == 0);
        assert(BestBuySell(ticks, 1) == 8);
        assert(BestBuySell(ticks, 2) == 14);
        assert(BestBuySell(ticks, 3) == 18);
        assert(BestBuySell(ticks, 4) == 20);
        assert(BestBuySell(ticks, 5) == 20);
    }

    {
        std::vector<size_t> ticks = { 4, 3, 2, 5, 4, 6, 7, 1, 7, 8, 5, 6 };
        // BuySellEntries: (2, 5), (4, 7), (1, 8), (5, 6)
        assert(BestBuySell(ticks, 0) == 0);
        assert(BestBuySell(ticks, 1) == 7);
        assert(BestBuySell(ticks, 2) == 12);
        assert(BestBuySell(ticks, 3) == 13);
        assert(BestBuySell(ticks, 4) == 14);
        assert(BestBuySell(ticks, 5) == 14);
    }
}
// ********************************************************************************

Bibliography:
[1] Donald E. Knuth "The Art of Computer Programming", Volume 1, Fundamental Algorithms, Third Edition
[2] Donald E. Knuth "The Art of Computer Programming", Volume 2, Seminumerical Algorithms, Third Edition

C++11 Features - std::array

std::array has the memory footprint of c-style array, but with std::vector's interface. Like the c-style array and unlike std::vector, std::arrary allocates the objects on the stack. The underline objects exists within the object of std::array. The motivation behind std::array I think has at least 3 significant benifits,
    - Save the memory comparing with std::vector
    - Provide STL interface
    - Performance optimization
        * Memory allocated on stack as c-style array
        * No memory fragment as on stack
        * No performance penalty of dynamic malloc/free lock between threads

1. std::array vs. c-style array
The major difference between might be that std::array provides STL interface. One of worthing mentioning is the pair of rbegin()/rend() to reversely traverse the array.

Except this difference std::array takes all the advantage and disadvantage of c-style array.
Advantages like:
    - Memory allocated on stack
    - No performance penalty from memory fragment, and locking and multi-threading application

Disadvantage (of c-style array):
    - The typename it takes should not have polymophisim
        * std::array<Base, 3> myBaseArr = {Derived(), Base(), Derived()};
        * Compilation errors and object slicing
    - The typename should be POD type
        * Data type provides default constructor
        * Or initialized with initializer-list with user-defined constructor
    - Worse syntax than c-style of array in multiple dimensional array
        * int threeDimArr[5][6][7]
        * std::array<std::array<std::array<int, 7>, 6>, 5> threeDimStdArr;

2. std::array vs. std::vector
The key difference between std::array and std::vector is that the underline objects on std::array is with the object of std::array, however on std::array is dynamic allocated on the heap. The potential performance issues are the memory fragment and the malloc/free lock between threads. (The threads have separated stacks but may or may not have separated heaps. It depends on OS. Unix and Windows share the heap between threads and Symbian doesn't.)

Another important difference is the memory size it takes. std::array is no greater than std::vector. std::array works only when the size of the container is known in advance however std::vector can be expanding as the size grows. The potential memory reallocation causes performance penalty and invalidates all the iterators. In order to reduce performance impact normally std::vector allocates more memory than it actually needs. However std::array won't have this issue, because its usage will be only limited to the scenarios that the size is knowing in advance.

So in order to have out-of-scope life, std::array objects have to be created on the heap. However std::vector objects on the stack can be easily moved around with const complexity to have out-of-scope life in C++11. Because the underline objects are allocated on heap in std::vector. The operations like std::move and std::swap takes const complexity, if the typename objects support std::swap and std::move operation.. However std::array will take linear complexity to do these two operations no matter what typename objects are, which are just as expensive as std::copy.

3. User cases
I have not seen many legitimate user cases that replaces c-style array with std::array, because I can't see many obvious benefits. I can see one case,
    - Need to use algorithm in the reverse order. For instance traverse reversely.

However I can definitely see one case that replace std::vector with std::array, when the size of std::vector is known in advance and the object is an automatic variable and is not returned. Benefits like,
    - Keep the same interface for algorithms
    - Less memory used
    - Performance gain from no memory fragment and malloc/free locking between threads

Performance gain could be an especially true case if the containers are used in performance hot spot.

4. Performance on Microsoft Visual Studio Express 2013
As I discussed the performance issue of std::vector and did comparison between it and c-style array. Please see C++ Performance - Array vs. std::vector. I am curious about how the performance of std::array will be as it have nearly identical interface as std::vector but the same memory footprint as c-style array.

This is the implementation of std::array::operator[] in  Visual Studio
// ********************************************************************************
reference operator[](size_type _Pos)
{ // subscript mutable sequence
 #if _ITERATOR_DEBUG_LEVEL == 2
if (_Size <= _Pos)
_DEBUG_ERROR("array subscript out of range");

 #elif _ITERATOR_DEBUG_LEVEL == 1
_SCL_SECURE_VALIDATE_RANGE(_Pos < _Size);
 #endif /* _ITERATOR_DEBUG_LEVEL */

_Analysis_assume_(_Pos < _Size);

return (_Elems[_Pos]);
}

const_reference operator[](size_type _Pos) const
{ // subscript nonmutable sequence
 #if _ITERATOR_DEBUG_LEVEL == 2
if (_Size <= _Pos)
_DEBUG_ERROR("array subscript out of range");

 #elif _ITERATOR_DEBUG_LEVEL == 1
_SCL_SECURE_VALIDATE_RANGE(_Pos < _Size);
 #endif /* _ITERATOR_DEBUG_LEVEL */

_Analysis_assume_(_Pos < _Size);

return (_Elems[_Pos]);
}

// ********************************************************************************

The implementation shows that std::array as a standard container in Visual Studio has the all the sanity checking of accessing the data as the rest. It have two levels of debugging level setting. Based on the investigation of C++ Performance - Array vs. std::vector, I believe that std::array should have the similar performance as std::vector without considering the performance penalty caused by memory allocation/free and locking from heap and stack.

Really look forward to seeing some performance optimization implemented by compiler. So in a word for some performance hot spot c-style array is still the winner.

Bibliography:
[1] Scotts Meyer, "Effective Mordern C++", O'REILLY, 2014 First Release
[2] Bjarne Stroustrup, "The C++ Programming Language", C++11, 4th Edition
[3] C++ Reading Club @ IG Group

Saturday, 10 January 2015

Dynamic Programming - Minimize the SUM of Difference between Two Arrays with Constraint : Google Interview Question

1. Problem Description
This is a Google Interview Question for Software Developer/Engineer from carrercup. Here is the original problem description from the thread.

"Given an integer array, adjust each integers so that the difference of every adjcent integers are not greater than a given number target. 

If the array before adjustment is A, the array after adjustment is B, you should minimize the sum of |A[i]-B[i]| 


You can assume each number in the array is a positive integer and not greater than 100 


Given [1,4,2,3] and target=1, one of the solutions is [2,3,2,3], the adjustment cost is 2 and it's minimal. Return 2."

2.Analysis
The problem requires to find an array B which has the same size as A and has the minimal sum of distance between each element at the same position of A and B. Each element of B is constraint to the condition of its distance to its two neighbors (except the top and end) not greater than a given target distance.

Let's restate in mathematical term. Assume array A with size of N and array B has the same size and the minimal optimization problem can be stated as,
Objective function: Min(Sigma(|Ai - Bi|)), 
                                where i is within [0, N-1]
Constraint: |Bj - Bj-1| <= TARGET,
                   where j is within [1, N-1]
Known: A and TARGET

3. Solution
This is an optimization problem and a few questions need to answer.
    1. The search space
    2. The constraints
    3. How to search (heuristic rules or brutal force)

Search space:
The search space is easy to find out. Given the array A the search space for the each element of Bi should be limited between the minimal value of A and the maximal value of A, [MinA, MaxA]. Because we are computing value of absolute value of Ai - Bi. Any Bi over the maximal value of A or under the minimal value of A can always be found a replacement between the minimal and maximal value of A, because absolute value works out the same result from two directions.

Constraint and Computation Complexity:
Now we worked out the search space. If not considering any effect of the constraint on the search space, then we would have exponential computation complexity. Suppose the difference between minimal and maximal value of A is M (equal to MaxA - MinA + 1). For N elements, then we have M power of N possible combinations, O(M^N). This is very bad if we decide to use brutal force to solve this problem. Therefore let's examine how the constraints on B shape the search space.

The constraint is |Bi-1 - Bi| <= TARGET. For a given value of Bi-1, as X, then what is the search space of Bi. Based on the constraint the search space of Bi is [max(X - TARGET, MinA), min(X+TARGET, MaxA)], given the value of Bi-1 as X. Therefore for each given value of Bi-1, Bi has maximally 2*TARGET choices, more precisely (min[X+TARGET, MaxA] - max(X-TARGET, MinA) + 1), as Y. Then the complexity will be O(M*Y^(N-1)), where Y is not greater than M.
Explanation:
    B0 has M choices
    B1 has Y choices for any given value of B0
    B2 has Y choices for any given value of B1
    ......

This is still not good enough. Let's take another look what has been searched during two value X and X+1 of Bi-1.
    Bi-1 = X: the choices of Bi is [max(X - TARGET, MinA), min(X+TARGET, MaxA)]
    Bi-1 = X+1: the choices of Bi is [max(X + 1 - TARGET, MinA), min(X+TARGET + 1, MaxA)]
*) When both upper bound  are clampped to MaxA, then Bi has the same search space, when the value of Bi-1 increase from X to X+1.
*) Otherwise Bi has one extra choice, (X+TARGET+1), when the value of Bi-1 increases from X to X+1.
So caching the result will hugely boost the performance.

As this is an optimization problem, optimizer solvers are good choices, for example usingMatlab. Here I would like to use brutal force to solve this issue with dynamic programming. It does not have to be dynamic programming. The reason why I chose it is because it is simply easier to formulate the problem and use the caching techniques to save the searched space.
The sub-problem:
    - F(n, j) = | A0 - Xj| + F(n-1, j) ,
          where n is size of Array A
                     Xj is one of values of B0, which is bounded in the search space [MinA, MaxA]
                     F(n-1) = Sigma(|Ak - Bk|), where k is within [1, N-1], and B1 is bounded as
                                                                      [Max(B0-TARGET, MinA), Min(B0+TARGET, MaxA)]
    - Min(F(n, j)), when F(n, j) is computed with Xj within [MinA, MaxA]

Corner case:
The sum of absolute value of difference will be zero, when (MaxA-MinA) <= TARGET, because simply take B equal to A, which meets the constraint and sum of absolute values can't be less than 0.

4. C++ Implementation
* Dynamic programming
* Caching technique

// ********************************************************************************
// IMPLEMENTATION
// ********************************************************************************
#include <algorithm>
#include <limits>
#include <unordered_map>

#include <cmath>

struct HashKeyPair
{
    HashKeyPair(size_t i, ptrdiff_t v) :
        index(i), value(v)
    {}
    size_t index; // index of i, |Ai - Bi|
    ptrdiff_t value; // searching value of Bi

    bool operator==(const HashKeyPair& hkp) const {
        return index == hkp.index &&
               value == hkp.value;
    }
};

struct HashKeyPairHashFunc
{
    size_t operator()(const HashKeyPair& hkp) {
        return std::hash<size_t>()(hkp.index) ^
               std::hash<ptrdiff_t>()(hkp.value);
    }

    bool operator()(const HashKeyPair& hkp1, const HashKeyPair& hkp2) const {
        return hkp1 == hkp2;
    }

};
typedef size_t MinSigmaOfAbsDiff;
typedef std::unordered_map<HashKeyPair, MinSigmaOfAbsDiff,
                           HashKeyPairHashFunc, HashKeyPairHashFunc> HashMap;

ptrdiff_t GetMinAbsValue(const ptrdiff_t ai, const ptrdiff_t target, const ptrdiff_t val)
{
    if (ai > (val + target)) {
        return ai - val - target;
    }
    else if (ai < (val - target)) {
        return val - target - ai;
    }

    return 0;
}

ptrdiff_t MinAbsDiff_DP(const std::vector<ptrdiff_t>& AI,
                     const ptrdiff_t target,
                     const ptrdiff_t minAi,
                     const ptrdiff_t maxAi,
                     const size_t curIndex,
                     const ptrdiff_t value,
                     HashMap& cache)
{
    if (curIndex == (AI.size() - 1)) {
        return GetMinAbsValue(*AI.rbegin(), target, value);
    }

    const HashKeyPair key = { curIndex, value };
    {
        // cached already?
        HashMap::const_iterator foundCIter = cache.find(key);
        if (foundCIter != cache.end()) {
            return foundCIter->second;
        }
    }

    // DP
    auto minSigmaOfAbsDiff = std::numeric_limits<ptrdiff_t>::max();
    ptrdiff_t tempMinSigmaOfAbsDiff;
    for (ptrdiff_t tempVal = value - target; tempVal <= (value + target); ++tempVal) {
        if (tempVal <= maxAi && tempVal >= minAi) {
            tempMinSigmaOfAbsDiff = abs(tempVal - AI[curIndex])
                    + MinAbsDiff_DP(AI, target, minAi, maxAi, curIndex + 1, tempVal, cache);
            if (minSigmaOfAbsDiff > tempMinSigmaOfAbsDiff) {
                minSigmaOfAbsDiff = tempMinSigmaOfAbsDiff;
            }
        }
    }

    // Cache it
    cache.insert(std::make_pair(key, minSigmaOfAbsDiff));

    return minSigmaOfAbsDiff;
}

ptrdiff_t MinAbsDiff(const std::vector<ptrdiff_t>& AI, const ptrdiff_t target)
{
    auto minVal = *(std::min_element(AI.begin(), AI.end()));
    auto maxVal = *(std::max_element(AI.begin(), AI.end()));

    if ((maxVal - minVal) <= target) {
        return 0;
    }

    HashMap cache;
    auto minSigmaOfAbsDiff = std::numeric_limits<ptrdiff_t>::max();
    ptrdiff_t temp;
    for (ptrdiff_t val = minVal; val <= maxVal; ++val) {
        temp = abs(AI[0] - val) +
               MinAbsDiff_DP(AI, target, minVal, maxVal, 1, val, cache);
        if (minSigmaOfAbsDiff > temp) {
            minSigmaOfAbsDiff = temp;
        }
        if (minSigmaOfAbsDiff == 0) {
            return 0;
        }
    }

    return minSigmaOfAbsDiff;
}

// ********************************************************************************
// TEST
// ********************************************************************************
#include <cassert>
void TestCases()
{
    {
        std::vector<ptrdiff_t> testVec = { 50, 50, 50, 50, 50, 50, 50, 50, 50, 50};
        ptrdiff_t target = 5;
        assert(MinAbsDiff(testVec, target) == 0);
    }

    {
        std::vector<ptrdiff_t> testVec = { 48, 53, 51, 50, 49, 53, 52, 48, 49, 50 };
        ptrdiff_t target = 5;
        assert(MinAbsDiff(testVec, target) == 0);
    }

    {
        std::vector<ptrdiff_t> testVec = { 56, 50};
        ptrdiff_t target = 5;
        assert(MinAbsDiff(testVec, target) == 1);
    }

    {
        std::vector<ptrdiff_t> testVec = { 56, 50, 50 };
        ptrdiff_t target = 5;
        assert(MinAbsDiff(testVec, target) == 1);
    }

    {
        std::vector<ptrdiff_t> testVec = { 56, 50, 50, 56 };
        ptrdiff_t target = 5;
        assert(MinAbsDiff(testVec, target) == 2);
    }

    {
        std::vector<ptrdiff_t> testVec = { 55, 77, 52, 61, 39, 6, 25, 60, 49, 47 };
        ptrdiff_t target = 10;
        assert(MinAbsDiff(testVec, target) == 75);
    }

    {
        std::vector<ptrdiff_t> testVec = { 94, 35, 29, 55 };
        ptrdiff_t target = 10;
        assert(MinAbsDiff(testVec, target) == 65);
    }

    {
        std::vector<ptrdiff_t> testVec = { 97, 73, 56, 56, 93, 55, 29, 47, 90, 36 };
        ptrdiff_t target = 3;
        assert(MinAbsDiff(testVec, target) == 157);
    }
}
// ********************************************************************************