Tuesday, July 15, 2014

[LeetCode] Palindrome Number

Problem Statement (link):
Determine whether an integer is a palindrome. Do this without extra space.
Some hints:
Could negative integers be palindromes? (ie, -1)
If you are thinking of converting the integer to string, note the restriction of using extra space.
You could also try reversing an integer. However, if you have solved the problem "Reverse Integer", you know that the reversed integer might overflow. How would you handle such case?
There is a more generic way of solving this problem.
Analysis:
The hints rule some straightforward ideas you may get.

Generically, we would look at the first (MSB) and the last digit (LSB) of an integer to determine if it's palindrome, then move on to the second MSB and second LSB, and so on... We could implement this method directly.

In the following method, I remove the MSB and LSB each time I compare them, so that every time I only need to look at the MSB and LSB of the updated integer.

Code:
class Solution {
public:
    bool isPalindrome(int x) {
        if (x<0) return false;
        // get num length
        int len=1, t=x;
        while(t/10>=1) {
            t/=10;
            len++;
        }

        // compare first and last digit
        while(x>0) {
            int d=pow(10,len-1);
            if (x%10!=x/d)
                return false;
            int t=x%d;
            x=(t-t%10)/10;
            len-=2;
        }

        return true;
    }
};

Wednesday, June 11, 2014

[LeetCode] Merge Sorted Array

Problem Statement (link):
Given two sorted integer arrays A and B, merge B into A as one sorted array.
Note:
You may assume that A has enough space (size that is greater or equal to m + n) to hold additional elements from B. The number of elements initialized in A and B are m andn respectively.
Analysis:
The obvious way is to insert B's entries into A after each comparison, and shift all the rest of A to right. Repeating this until we reach the end of B. I bet you'll get TLE.

The way to improve is considering about avoid these shifts. As the A[m+1 : m+n] is empty, we could start from the large values and works backwards. See the following implementation. The time complexity is O(m+n) and no extra space is needed.

Code:
class Solution {
public:
    void merge(int A[], int m, int B[], int n) {
        if (n==0) return;
        if (m==0) {
            for (int i=0; i<n; i++)
                A[i]=B[i];
            return;
        }

        int curA=m; int curB=n;
        while(curB>0 && curA>0) {
            if (A[curA-1]>=B[curB-1]) {
                A[curA+curB-1]=A[curA-1];
                curA--;
            }
            else  {
                A[curA+curB-1]=B[curB-1];
                curB--;
            }
        }

        if (curB>0)
            for (int i=0; i<curB; i++)
                A[i]=B[i];
        if (curA>0)
            return;
    }
};



Tuesday, June 10, 2014

[LeetCode] Unique Paths I && II

Unique Paths I

Problem Statement (link):
A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below).
How many possible unique paths are there?

Above is a 3 x 7 grid. How many possible unique paths are there?
Note: m and n will be at most 100.
Analysis:
First, notice that this is a DP problem, the number of paths to reach (i, j) equals to the number of paths to reach (i-1, j) + the number of paths to reach (i, j-1), except the first row and column, where the number of paths are all 1.

It's obvious that we could use a 2D DP to solve it. However, if we consider reusing a 1D DP vector, we could solve it with O(n) time complexity. The time complexity is O(m*n).

Code:
Sol 1: 2D DP
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> res(m, vector<int>(n, 0));
        for (int i=0; i<m; i++)
            res[i][0]=1;
        for (int j=0; j<n; j++)
            res[0][j]=1;

        for (int i=1; i<m; i++)

            for (int j=1; j<n; j++)
                res[i][j]=res[i-1][j]+res[i][j-1];
        return res[m-1][n-1];
    }
};

Sol 2: 1D DP
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<int> res(n, 1);
        for (int i=1; i<m; i++)
            for (int j=1; j<n; j++)
                res[j]=res[j-1]+res[j];
        return res[n-1];
    }
};


Unique Paths II

Problem Statement (link):
Follow up for "Unique Paths":
Now consider if some obstacles are added to the grids. How many unique paths would there be?
An obstacle and empty space is marked as 1 and 0 respectively in the grid.
For example,
There is one obstacle in the middle of a 3x3 grid as illustrated below.
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
The total number of unique paths is 2.
Note: m and n will be at most 100.
Analysis:
The basic idea is same as the previous problem. Just a few special cases that we need to take care of when encounter obstacles.

- For the first row and first column, once we encounter an obstacle, that space and all the following spaces should be set to 0 as we couldn't reach these places;
- For the rest spaces, once we encounter an obstacle, that space should be set to 0; otherwise, we sum up the value in its left and top and put the sum to that space. Note that the summation takes case of the cases where either or both its left and top are 0.

The space complexity is O(n) and time complexity is O(m*n).

Code:
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int> > &obstacleGrid) {
        int m=obstacleGrid.size();
        int n=obstacleGrid[0].size();
        vector<int> res(n, 0);

        // initialize using first row
        for (int j=0; j<n; j++) {
            if (obstacleGrid[0][j]==1)
                break;
            res[j]=1;
        }

        for (int i=1; i<m; i++) {
            for (int j=0; j<n; j++) {
                // assign the fist element
                if (j==0 && (obstacleGrid[i][0]==1 || res[0]==0)) {
                    res[0]=0;
                    continue;
                }
                else if (j==0 && obstacleGrid[i][0]==0) {
                    res[0]=1;
                    continue;
                }

                // the rest
                if (obstacleGrid[i][j]==0)
                    res[j]=res[j-1]+res[j];
                if (obstacleGrid[i][j]==1)
                    res[j]=0;
            }
        }
        return res[n-1];
    }
};



[LeetCode] Path Sum I && II

Path Sum I
Problem Statement (link):
Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.
For example:
Given the below binary tree and sum = 22,
              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1
return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
Analysis:
This problem is a variation of tree traversal. We DFS the binary tree to find if any root-to-leaf path has the same value sum. We continue searching until we find a path.

Code:
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool hasPathSum(TreeNode *root, int sum) {
        if (root==NULL) return false;
        return trav(root, 0, sum);
    }

    bool trav(TreeNode *node, int pathSum, int sum) {
        bool result = false;
        pathSum += node->val;
        if (node->left==NULL && node->right==NULL)
            if (pathSum==sum)
                return true;
        if (node->left!=NULL)
            result = trav(node->left, pathSum, sum);
        if (!result && node->right!=NULL)
            result = trav(node->right, pathSum, sum);
        return result;
    }
};


Path Sum II
Problem Statement (link):
Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum.
For example:
Given the below binary tree and sum = 22,
              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1
return
[
   [5,4,11,2],
   [5,8,4,5]
]
Analysis:
The idea is basically the same as the previous problem. But instead of updating the value sum, we store the nodes as we traverse down the tree. When we reach the leaf, we check if the sum value of all nodes in the path equals the expected sum, if so, we push the path to out vector.

It is important to pop_back the node after we push_back it into path vector. This ensures that the path vector only stores the nodes in the current path, and thus we could reuse the path vector as we traverse. This is a common trick in tree traversal.

Code:
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int> > pathSum(TreeNode *root, int sum) {
        vector<vector<int> > out;
        if (root==NULL) return out;
        vector<int> path;
        path.push_back(root->val);
        recur(root, sum, out, path);
        return out;
    }
    void recur(TreeNode *node, int sum, vector<vector<int>> &out, vector<int> &path) {
        if (node->left==NULL && node->right==NULL) {
            int temp=0;
            for (int i=0; i<path.size(); i++) temp+=path[i];
            if (temp==sum) out.push_back(path);
            return;
        }

        if (node->left!=NULL) {
            path.push_back(node->left->val);
            recur(node->left, sum, out, path);
            path.pop_back();
        }
        if (node->right!=NULL) {
            path.push_back(node->right->val);
            recur(node->right, sum, out, path);
            path.pop_back();
        }
    }
};


[LeetCode] Flatten Binary Tree to Linked List

Problem Statement (link):
Given a binary tree, flatten it to a linked list in-place.
For example,
Given
         1
        / \
       2   5
      / \   \
     3   4   6
The flattened tree should look like:
   1
    \
     2
      \
       3
        \
         4
          \
           5
            \
             6
Analysis:
Observe the flattened tree closely, we find that it's basically a pre-order traversal transformation. However, what's more than that is we need to link the tree properly, as in-place transformation is required.

The idea is: before we traverse the left sub-tree of a node, we use a pointer to save the right child, because when we finished left sub-tree traversal, the root node's right child would be the current left child. Additionally, we would need another pointer prev to store the previous visited node, as later when we need to link the node to it's previous node.

Code:
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    // Recursive preorder traversal
    void flatten(TreeNode *root) {
        TreeNode *prev = NULL;
        recur(root, prev);
    }
    void recur(TreeNode *node, TreeNode *&prev) {
        if (node==NULL) return;
        TreeNode *saveRightNode = node->right;

        if (prev!=NULL) {
            prev->right=node;
            prev->left=NULL;
        }
        prev=node;
        recur(node->left, prev);
        recur(saveRightNode, prev);
    }
};



[LeetCode] Populating Next Right Pointers in Each Node II

Problem Statement (link):
Follow up for problem "Populating Next Right Pointers in Each Node".
What if the given tree could be any binary tree? Would your previous solution still work?
Note:
You may only use constant extra space.
For example,
Given the following binary tree,
         1
       /  \
      2    3
     / \    \
    4   5    7
After calling your function, the tree should look like:
         1 -> NULL
       /  \
      2 -> 3 -> NULL
     / \    \
    4-> 5 -> 7 -> NULL
Analysis:
The idea is similar to the previous problem I. The difference is that we couldn't simply connect nodes in the same layer easily. We need an extra pointer prev to record the previous visited node on the same layer. See the following iterative solution for details.

Code:
/**
 * Definition for binary tree with next pointer.
 * struct TreeLinkNode {
 *  int val;
 *  TreeLinkNode *left, *right, *next;
 *  TreeLinkNode(int x) : val(x), left(NULL), right(NULL), next(NULL) {}
 * };
 */
class Solution {
public:
    void connect(TreeLinkNode * root) {
        while(root!=NULL) {
            TreeLinkNode *curr=root;
            TreeLinkNode *prev=NULL;

            while (curr!=NULL) { // traverse each layer
                if (curr->left!=NULL) {
                    if (prev!=NULL)
                        prev->next=curr->left;
                    prev=curr->left;
                }
                if (curr->right!=NULL) {
                    if (prev!=NULL) 
                        prev->next=curr->right;
                    prev=curr->right;
                }
                curr=curr->next;
            }

            // find starting node in next layer
            while(root!=NULL) {
                if (root->left!=NULL) {
                    root=root->left;
                    break;
                }
                if (root->right!=NULL) {
                    root=root->right;
                    break;
                }
                root=root->next;
            }
        }
    }
};


[LeetCode] Best Time to Buy and Sell Stock I && II && III

Best Time to Buy and Sell Stock I
Problem Statement (link):
Say you have an array for which the ith element is the price of a given stock on day i.
If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.
Analysis:
The max profit at day i is the difference between prices[i] and the min price before day i. Thus, the max profit until day i is the max among all days before and including day i. We traverse the prices vector and store the min value we found so far, and calculate the max profit of day i in use of the min, update the overall  max profit if new max is larger.

This algorithm has O(n) in time complexity and with constant space complexity.

Code:
class Solution {
public:
    int maxProfit(vector<int> &prices) {
        if (prices.empty() || prices.size()==1) return 0;
        int profit = 0;
        int minPrice = prices[0];
        int len = prices.size();
        for (int i=0; i<len; i++) {
            if (prices[i]<minPrice)
                minPrice = prices[i];
            else
                profit = max(profit, prices[i]-minPrice);
        }
        return profit;
    }
};


Best Time to Buy and Sell Stock II
Problem Statement (link):
Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
Analysis:
The problem is not well-stated, judging from the answer, it implies that you are allowed to sell and buy stock at the same day.

With that, the solution is simple. We make a profit by buying at day i and sell at day j as long as i<j and prices[i]<prices[j]. Thus, we simply traverse the vector and accumulate all the differences between day pairs.

This algorithm has O(n) in time complexity and with constant space complexity.

Code:
class Solution {
public:
    int maxProfit(vector<int> &prices) {
        if (prices.empty() || prices.size()==1) return 0;
        int profit = 0;
        for (int i=0; i<prices.size()-1; i++) {
            if (prices[i+1]>prices[i])
                profit += prices[i+1]-prices[i];
        }
        return profit;
    }
};


Best Time to Buy and Sell Stock III
Problem Statement (link):
Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most two transactions.
Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
Analysis:
As we may complete two transaction, the first thought is to separate the vector into two parts and find max profit for each parts. Sol 1 is the implementation of this algorithm. However, it yields TLE in a large test case.

If we think about the previous algorithm, we notice that we scanned many entries multiple times, which piles up to the time complexities. Sol 2 is an O(n) algorithm. In this algorithm, we first scan from left to right to get a vector of max profit that ends at each day i in O(n) time; then we scan from right to left to get another vector of max profit that starts at each day i in O(n) time; finally, we calculate the max profit overall by finding out the day that separate two transactions.

Code:
Sol 1: O(n^2) - TLE
class Solution {
public:
    int maxProfit(vector<int> &prices) {
        if(prices.empty() || prices.size()==1) return 0;
        int profit1 = 0;    // max profit from 0:i
        int profit2 = 0;    // max profit from i:n
        int min1 = 0;
        int min2 = 0;
        int n = prices.size();
        int profit = 0;
        for (int i=0; i<n; i++) {
            // From 0-i
            min1 = prices[0];
            for (int p=0; p<=i; p++){
                if (prices[p]<min1) min1=prices[p];
                else profit1 = max(profit1, prices[p]-min1);
            }
            min2 = prices[i];
            for (int p=i; p<n; p++){
                if (prices[p]<min2) min2=prices[p];
                else profit2 = max(profit2, prices[p]-min2);
            }
            
            profit = max(profit, profit1+profit2);
        }
        return profit;
    }
};

Sol 2: O(n)
class Solution {
public:
    int maxProfit(vector<int> &prices) {
        if (prices.empty() || prices.size()==1) return 0;
        int minp=prices[0]; int profit=0; int maxProfit=0;
        vector<int> left; vector<int> right;

        // traverse from left to right
        for (int i=0; i<prices.size(); i++) {
            if (prices[i]<minp) minp=prices[i];
            else profit=max(profit, prices[i]-minp);
            left.push_back(profit);
        }

        // traverse from right to left
        int maxp=prices[prices.size()-1]; profit=0;
        for (int i=prices.size()-1; i>=0; i--) {
            if (prices[i]>maxp) maxp=prices[i];
            else profit=max(profit,maxp-prices[i]);
            right.push_back(profit);
        }

        // combine the two vectors to find the split index s.t. profit is maximized
        for (int i=0; i<prices.size(); i++) {
            maxProfit=max(maxProfit, left[i]+right[prices.size()-i-1]);
        }
        return maxProfit;
    }
};