Tuesday, December 6, 2011

No. 25 - Edit Distance


Problem: Implement a function which gets the edit distance of two input strings. There are three types of edit operations: insertion, deletion and substitution. Edit distance is the minimal number of edit operations to modify a string from one to the other.

For example, the edit distance between “Saturday” and “Sunday” is 3, since the following three edit operations are required to modify one into the other:
1.       Saturday → Sturday (deletion of ‘a’)
2.       Sturday→ Surday (deletion of ‘t’)
3.       Surday → Sunday (substitution of ‘n’ for ‘r’)

There is no way to achieve it with fewer than three operations.

Analysis: If a function f(i, j) is defined to indicate the edit difference between the substring of the  first string ending with the jth character and the substring of the second string ending with the ith character. It is obvious that f(i, 0) = i, because when we delete i characters from the substring of the first string ending with the ith character, we get an empty string (it is also the substring of the second string ending with the 0-th string). Similarly, f(0, j) = j.

Let discuss the cases when both i and j are greater than 1. If the ith character of the second string is same as the jth character of the first string, no edit operations are necessary. Therefore, f(i, j) = f(i-1, j-1).

When the jth character of the first string is different from the ith character of the second string, there are three options available: (1) Insert the ith character of second string into the first string. In this case, f(i, j) = f(i-1, j) + 1. (2) Delete the jth character of the first string. In this case, f(i, j) = f(i, j-1) + 1. (3) Replace the jth character of the first string with the ith character of the second string. In this case, f(i, j) = f(i-1, j-1) + 1. What is the final value for f(i, j)? It should be the minimal value of the three cases.

If we draw a table to show the edit distance values f(i, j) between “Saturday” and “Sunday”, it looks like the Table 1.



S
a
t
u
r
d
a
y

0
1
2
3
4
5
6
7
8
S
1
0
1
2
3
4
5
6
7
u
2
1
1
2
2
3
4
5
6
n
3
2
2
2
3
3
4
5
6
d
4
3
3
3
3
4
3
4
5
a
5
4
3
4
4
4
4
3
4
y
6
5
4
4
5
5
5
4
3
Table 1: Edit distance value f(i, j) between “Saturday” and “Sunday”.

The edit distance of two strings is at the right-bottom corner of the table for edit distance values.
A formal equation can be defined for this problem:


It is not difficult to develop code based on the equation above. An edit distance value table can be implemented as a 2-D array. Some sample code is shown as below:

int getEditDistance(char* str1, char* str2)
{
    if(str1 == NULL || str2 == NULL)
        return -1;

    int len1 = strlen(str1);
    int len2 = strlen(str2);

    int** distances = (int**)new int[len2 + 1];
    for(int i = 0; i < len2 + 1; ++ i)
        distances[i] = new int[len1 + 1];

    int editDistance = getEditDistance(str1, str2, distances, len1, len2);

    for(int i = 0; i < len2 + 1; ++ i)
        delete[] distances[i];
    delete[] distances;

    return editDistance;
}

int getEditDistance(char* str1, char* str2, int** distances, int len1, int len2)
{
    for(int i = 0; i < len2 + 1; ++ i)
        distances[i][0] = i;
    for(int j = 0; j < len1 + 1; ++ j)
        distances[0][j] = j;

    for(int i = 1; i < len2 + 1; ++ i)
    {
        for(int j = 1; j < len1 + 1; ++ j)
        {
            if(str1[j - 1] == str2[i - 1])
                distances[i][j] = distances[i - 1][j - 1];
            else
            {
                int deletion = distances[i][j - 1] + 1;
                int insertion = distances[i - 1][j] + 1;
                int substitution = distances[i - 1][j - 1] + 1;
                distances[i][j] = min(deletion, insertion, substitution);
            }
        }
    }

    return distances[len2][len1];
}

int min(int num1, int num2, int num3)
{
    int less = (num1 < num2) ? num1 : num2;
    return (less < num3) ? less : num3;
}

The author Harry He owns all the rights of this post. If you are going to use part of or the whole of this ariticle in your blog or webpages,  please add a reference to http://codercareer.blogspot.com/. If you are going to use it in your books, please contact me (zhedahht@gmail.com) . Thanks.

Monday, November 28, 2011

No. 24 - Intersection of Sorted Arrays


Problem: Please implement a function which gets the intersection of two sorted arrays. Assuming numbers in each array are unique.

For example, if the two sorted arrays as input are {1, 4, 7, 10, 13} and {1, 3, 5, 7, 9}, it returns an intersection array with numbers {1, 7}.

Analysis: An intuitive solution for this problem is to check whether every number in the first array (denoted as array1) is in the second array (denoted as array2). If the length of array1 is m, and the length of array2 is n, its overall time complexity is O(m*n) based on linear search. We have two better solutions.

Solution 1: With O(m+n) Time

It is noticeable that the two input arrays are sorted. Supposing a number number1 in array1 equals to a number number2 in array2, the numbers after number1 in array1 should be greater than the numbers before number2 in array2. Therefore, it is not necessary to compare the numbers after number1 in array1 with numbers before number2 in array2. It improves efficiency since many comparisons are eliminated.

The sample code for this solution is shown below:

void GetIntersection_solution1(const vector<int>& array1,
                     const vector<int>& array2,
                     vector<int>& intersection)
{
    vector<int>::const_iterator iter1 = array1.begin();
    vector<int>::const_iterator iter2 = array2.begin();

    intersection.clear();

    while(iter1 != array1.end() && iter2 != array2.end())
    {
        if(*iter1 == *iter2)
        {
            intersection.push_back(*iter1);
            ++ iter1;
            ++ iter2;
        }
        else if(*iter1 < *iter2)
            ++ iter1;
        else
            ++ iter2;
    }
}

Since it only requires to scan two arrays once, its time complexity is O(m+n).

Solution 2: With O(nlogm) Time

As we know, a binary search algorithm requires O(logm) time to find a number in an array with length m. Therefore, if we search each number of an array with length n from an array with length m, its overall time complexity is O(nlogm). If m is much greater than n, O(nlogm) is actually less than O(m+n). Therefore, we can implement a new and better solution based on binary search in such a situation. 

For instance, the following same code is suitable when array1 is much longer than array2.

/* === Supposing array1 is much longer than array2 === */
void GetIntersection_solution2(const vector<int>& array1,
                     const vector<int>& array2,
                     vector<int>& intersection)
{
    intersection.clear();
   
    vector<int>::const_iterator iter1 = array1.begin();
    while(iter1 != array1.end())
    {
        if(binary_search(array2.begin(), array2.end(), *iter1))
            intersection.push_back(*iter1);
    }
}

The discussion about this problem is included in my book <Coding Interviews: Questions, Analysis & Solutions>, with some revisions. You may find the details of this book on Amazon.com, or Apress.
 
The author Harry He owns all the rights of this post. If you are going to use part of or the whole of this ariticle in your blog or webpages,  please add a reference to http://codercareer.blogspot.com/. If you are going to use it in your books, please contact me (zhedahht@gmail.com) . Thanks. 

Saturday, November 26, 2011

No. 23 - Palindrome Numbers


Problem: Please implement a function which checks whether a number is a palindrome or not. For example, 121 is a palindrome, while 123 is not.

Analysis: Many candidates can get a straight solution which converts the input number into a string first. However, it is not what interviewers expect usually.

Solution 1: Convert a Number into a String

It is easy to check whether a number is palindrome or not: We can check whether the first character and the last one are identical, and then check the second character and the second one from end, and so on. Therefore, we can firstly convert the input number into a string via the function sprintf, and then check whether the string is a palindrome.

This solution can be implemented as the following code:

bool IsPalindrome_solution1(unsigned int number)
{
    const int NUMBER_LENGTH = 20;
    char string[NUMBER_LENGTH];
    sprintf(string, "%d", number);

    return IsPalindrome(string);
}

bool IsPalindrome(const char* const string)
{
    bool palindrome = true;
    if(string != NULL)
    {
        int length = strlen(string);
        int half = length >> 1;

        for(int i = 0; i < half; ++ i)
        {
            if(string[i] != string[length - 1 - i])
            {
                palindrome = false;
                break;
            }
        }
    }

    return palindrome;
}

Usually the solution above is not the one expected by interviewers. One reason is that it is intuitive while interviewers expect something innovative, and the other is that it requires auxiliary memory to store the converted string.

Solution 2: Compose a Reversed Number

As we know, it is easy to get digits from right to left via / and % operators. For example, digits in the number 123 are 3, 2 and 1. We can construct a reversed number 321 with these three digits. And then we check whether the reverse number is identical to the original one. If it is, the original number is a palindrome.

Its corresponding code is shown below:

bool IsPalindrome_solution2(unsigned int number)
{
    int reversed = 0;
    int copy = number;

    while(number != 0)
    {
        reversed = reversed * 10 + number % 10;
        number /= 10;
    }

    return (reversed == copy);
}

The discussion about this problem is included in my book <Coding Interviews: Questions, Analysis & Solutions>, with some revisions. You may find the details of this book on Amazon.com, or Apress.
 
The author Harry He owns all the rights of this post. If you are going to use part of or the whole of this ariticle in your blog or webpages,  please add a reference to http://codercareer.blogspot.com/. If you are going to use it in your books, please contact me (zhedahht@gmail.com) . Thanks. 

Saturday, November 19, 2011

No. 22 - Turning Number in an Array


Problem: Turning number is the maximum number in an array which increases and then decreases. This kind of array is also named unimodal array. Please write a function which gets the index of the turning number in such an array.

For example, the turning number in array {1, 2, 3, 4, 5, 10, 9, 8, 7, 6} is 10, so its index 5 is the expected output.

Analysis: As we know, the binary search algorithm is suitable to search a number in a sorted array. Since the input array for this problem is partially sorted, we may also have a try with binary search.

Let us try to get the middle number in an array. The middle number of array {1, 2, 3, 4, 5, 10, 9, 8, 7, 6} is 5 (the fourth number). It is greater than its previous number 4, and less than its next number 10, so it is in the increasing sub-array. Therefore, numbers before 5 can be discarded in the next round of search.

The remaining numbers for the next round of search are {5, 10, 9, 8, 7, 6}, and the number 9 is in the middle of them. Since 9 is less than is previous number 10 and greater than its next number 8, it is in the decreasing sub-array. Therefore, numbers after 9 can be discarded in the next round of search.

The remaining numbers for the next round of search are {5, 10, 9}, and the number 10 is in the middle. It is noticeable that number 10 is greater than its previous number 5 and greater than its next number 9, so it is the maximum number. That is to say, the number 10 is the turning number in the input array.

We can see the process above is actually a classic binary search. Therefore, we can implement the required function based on binary search algorithm, as listed below:

int TurningNumberIndex(int* numbers, int length)
{
    if(numbers == NULL || length <= 2)
        return -1;

    int left = 0;
    int right = length - 1;
    while(right > left + 1)
    {
        int middle = (left + right) / 2;
        if(middle == 0 || middle == length - 1)
            return -1;

        if(numbers[middle] > numbers[middle - 1] &&
            numbers[middle] > numbers[middle + 1])
            return middle;
        else if(numbers[middle] > numbers[middle - 1] &&
            numbers[middle] < numbers[middle + 1])
            left = middle;
        else
            right = middle;
    }

    return -1;
}

The discussion about this problem is included in my book <Coding Interviews: Questions, Analysis & Solutions>, with some revisions. You may find the details of this book on Amazon.com, or Apress.
 
The author Harry He owns all the rights of this post. If you are going to use part of or the whole of this ariticle in your blog or webpages,  please add a reference to http://codercareer.blogspot.com/. If you are going to use it in your books, please contact me (zhedahht@gmail.com) . Thanks. 

Monday, November 14, 2011

No. 21 - Push and Pop Sequences of Stacks


Problem: Given two integer sequences, one of which is the push sequence of a stack, please check whether the other sequence is a corresponding pop sequence or not. 

For example, if 1, 2, 3, 4, 5 is a push sequence, 4, 5, 3, 2, 1 is a corresponding pop sequence, but the sequence 4, 3, 5, 1, 2 is not.

Analysis: An intuitive thought on this problem is to create an auxiliary stack. We push the numbers in the first sequence one by one, and try to pop them out according to the order in the second sequence.

Take the sequence 4, 5, 3, 2, 1 as an example to analyze the process to push and pop. The first number to be popped is 4, so we need to push it into a stack. The pushing order is defined in the first sequence, where there are numbers 1, 2 and 3 prior to 4. Therefore, numbers 1, 2, and 3 are pushed into a stack before 4 is pushed. At this time, there are 4 numbers in a stack, which are 1, 2, 3 and 4, with 4 on top. When 4 is popped, numbers 1, 2 and 3 are left. The next number to be popped is 5, which is not on top of stack, so we have to push numbers in the first sequence into stack until 5 is pushed. When number 5 is on top of a stack, we can pop it. The next three numbers to be popped are 3, 2 and 1. Since these numbers are on top of a stack before pop operations, they can be popped directly. The whole process to push and pop is summarized in Table 1.

Step
Operation
Stack Status
Popped
Step
Operation
Stack Status
Popped
1
Push 1
1

6
Push 5
1, 2, 3, 5

2
Push 2
1, 2

7
Pop
1, 2, 3
5
3
Push 3
1, 2, 3

8
Pop
1, 2
3
4
Push 4
1, 2, 3, 4

9
Pop
1
2
5
Pop
1, 2, 3
4
10
Pop

1
Table 1: The process to push and pop with a push sequence 1, 2, 3, 4, 5 and pop sequence 4, 5, 3, 2, 1

Let us continue to analyze another pop sequence 4, 3, 5, 1, 2. The process to pop the first number 4 is similar to the process above. After the number 4 is popped, 3 is on the top of stack and it can be popped. The next number to be popped is 5. Since it is not on top, we have to push numbers in the first sequence until the number 5 is pushed. The number 5 can be popped when it is pushed onto the top of a stack. After 5 is popped out, there are only two numbers 1 and 2 left in stack. The next number to be popped is 1, but it is not on the top of stack. We have to push numbers in the first sequence until 1 is pushed. However, all numbers in the first sequence have been pushed. Therefore, the sequence 4, 3, 5, 1, 2 is not a pop sequence of the stack with push sequence 1, 2, 3, 4, 5. The whole process to push and pop is summarized in Table 2.

Step
Operation
Stack Status
Popped
Step
Operation
Stack Status
Popped
1
Push 1
1

6
Pop
1, 2
3
2
Push 2
1, 2

7
Push 5
1, 2, 5

3
Push 3
1, 2, 3

8
Pop
1, 2
5
4
Push 4
1, 2, 3, 4

The next number to be popped is 1, which is neither on the top of stack, nor in the remaining numbers of push sequence.
5
Pop
1, 2, 3
4
Table 1: The process to push and pop with a push sequence 1, 2, 3, 4, 5 and pop sequence 4, 3, 5, 1, 2

According to the analysis above, we get a solution to check whether a sequence is a pop sequence of a stack or not. If the number to be popped is currently on top of stack, just pop it. If it is not on the top of stack, we have to push remaining numbers into the auxiliary stack until we meet the number to be popped. If the next number to be popped is not remaining in the push sequence, it is not a pop sequence of a stack. The following is some sample code based on this solution:

bool IsPopOrder(const int* pPush, const int* pPop, int nLength)
{
    bool bPossible = false;

    if(pPush != NULL && pPop != NULL && nLength > 0)
    {
        const int* pNextPush = pPush;
        const int* pNextPop = pPop;

        std::stack<int> stackData;

        while(pNextPop - pPop < nLength)
        {
            // When the number to be popped is not on top of stack,
            // push some numbers in the push sequence into stack
            while(stackData.empty() || stackData.top() != *pNextPop)
            {
                // If all numbers have been pushed, break
                if(pNextPush - pPush == nLength)
                    break;

                stackData.push(*pNextPush);

                pNextPush ++;
            }

            if(stackData.top() != *pNextPop)
                break;

            stackData.pop();
            pNextPop ++;
        }

        if(stackData.empty() && pNextPop - pPop == nLength)
            bPossible = true;
    }

    return bPossible;
}

The discussion about this problem is included in my book <Coding Interviews: Questions, Analysis & Solutions>, with some revisions. You may find the details of this book on Amazon.com, or Apress.
 
The author Harry He owns all the rights of this post. If you are going to use part of or the whole of this ariticle in your blog or webpages,  please add a reference to http://codercareer.blogspot.com/. If you are going to use it in your books, please contact me (zhedahht@gmail.com) . Thanks.