Leetcode 66: Plus One

https://leetcode.com/problems/plus-one/

Given a non-negative number represented as an array of digits, plus one to the number.

The digits are stored such that the most significant digit is at the head of the list.

 

Code

class Solution(object):
    def plusOne(self, digits):
        """
        :type digits: List[int]
        :rtype: List[int]
        """
        for i in xrange(len(digits)-1, -1, -1):
            if digits[i] == 9:
                digits[i] = 0
                if i == 0:
                    digits.insert(0, 1)
            else:
                digits[i] = digits[i] + 1
                break
            
        return digits

 

Idea

Straightforward.

Leetcode 110: Balanced Binary Tree

https://leetcode.com/problems/balanced-binary-tree/

Given a binary tree, determine if it is height-balanced.

For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.

Please check the clear definition of this problem: https://leetcode.com/discuss/59/different-definitions-balanced-result-different-judgments

 

Code 1

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def isBalanced(self, root):
        """
        :type root: TreeNode
        :rtype: bool
        """
        return self.helper(root) != -1
    
    def helper(self, root):
        if not root:
            return 0
        
        left_depth = self.helper(root.left)
        right_depth = self.helper(root.right)
        if left_depth >= 0 and right_depth >= 0 and abs(left_depth-right_depth) <=1:
            return max(left_depth, right_depth)+1
        else:
            return -1

Idea

Use dfs to get depth of every node. Whenever one node has inbalancing left and right subtrees, it returns -1 as an indicator. Note how different code 1 is with code 2. In code 1, every node is traversed exactly once therefore code takes O(N) time. In code 2, O(NlogN) time is needed because the nodes on the same level of the binary tree need calling O(N)  times `self.getHeight(root)` in total.

 

Code 2:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    # @param {TreeNode} root
    # @return {boolean}
    def isBalanced(self, root):
        if not root:
            return True

        return abs(self.getHeight(root.left) - self.getHeight(root.right)) < 2 and self.isBalanced(root.left) and self.isBalanced(root.right)

    def getHeight(self, root):
        if not root:
            return 0

        return 1 + max(self.getHeight(root.left), self.getHeight(root.right))

 

Reference

https://leetcode.com/discuss/22898/the-bottom-up-o-n-solution-would-be-better

Leetcode 68: Text Justification

https://leetcode.com/problems/text-justification/

Given an array of words and a length L, format the text such that each line has exactly L characters and is fully (left and right) justified.

You should pack your words in a greedy approach; that is, pack as many words as you can in each line. Pad extra spaces ' ' when necessary so that each line has exactly Lcharacters.

Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line do not divide evenly between words, the empty slots on the left will be assigned more spaces than the slots on the right.

For the last line of text, it should be left justified and no extra space is inserted between words.

For example,
words: ["This", "is", "an", "example", "of", "text", "justification."]
L: 16.

Return the formatted lines as:

[
   "This    is    an",
   "example  of text",
   "justification.  "
]

Note: Each word is guaranteed not to exceed L in length.

click to show corner cases.

Corner Cases:

  • A line other than the last line might contain only one word. What should you do in this case?
    In this case, that line should be left-justified.

 

Code

class Solution(object):
    def fullJustify(self, words, maxWidth):
        """
        :type words: List[str]
        :type maxWidth: int
        :rtype: List[str]
        """
        res = []
        cur = 0                   # number of characters (without spaces) in current line 
        cur_l = []                # word list for current line
        i = 0
        
        while i < len(words):
            w =  words[i]
            if len(w) <= maxWidth - cur - len(cur_l):       # More words can be included in current line
                cur_l += [w]
                cur += len(w)
                i += 1
                if  i != len(words):                        # continue if the word is not the last word
                    continue
            
            if len(cur_l)==1:                               # This line only contains one word
                 res += [cur_l[0] + ' '*(maxWidth-cur)]
            else:
                if i == len(words):              # only left justified
                    line = ' '.join(cur_l)  
                    line += ' ' * (maxWidth - len(line))
                else:
                    line = cur_l[0]              # left-right justified
                    for ii, ww in enumerate(cur_l[1:], start=1):
                        if i != len(words):  
                            line += ' ' * ((maxWidth-cur)/(len(cur_l)-1)) 
                            if (maxWidth-cur) % (len(cur_l)-1) >= ii:
                                line += ' '
                            line += ww

                res += [line]
                
            if i != len(words):
                cur_l = []
                cur = 0
    
        return res

 

Idea

Lots of conditions to be considered. See comments for explanation. To me there seems not to be an intelligently challenging problem.

Leetcode 109: Convert Sorted List to Binary Search Tree

https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/

Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST.

 

Code

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def sortedListToBST(self, head):
        """
        :type head: ListNode
        :rtype: TreeNode
        """
        if head is None:
            return None
        
        prev_slow_pt = None    
        slow_pt = head
        fast_pt = head
        while fast_pt.next and fast_pt.next.next:
            fast_pt = fast_pt.next.next
            prev_slow_pt = slow_pt
            slow_pt = slow_pt.next
        
        root = TreeNode(slow_pt.val)
        root.right = self.sortedListToBST(slow_pt.next)
        if prev_slow_pt:
            prev_slow_pt.next = None
            root.left = self.sortedListToBST(head)
        
        return root
        

 

Idea

To find the middle element in a linkedlist, it is a typical way to set two pointers, a slow pointer and a fast pointer. Let the fast one moves two elements forward every time while the slow one only moves one element forward each time. When the fast one reaches the end of the linkedlist, the slow pointer stops at the middle element of the linkedlist.

The slow pointer becomes the root of the BST. Then, all elements before the slow pointer become the left tree and all elements after the slow pointer become the right tree. The problem thus can be solved by smaller problems.

Note that I also maintain `prev_slow_pt` as a pointer pointing to the element right before the `slow_pt`. This is useful when you want to process only the elements from `head` to `prev_slow_pt` to form the left tree. (See line 33 – 35). If `prev_slow_pt` is None, it means `slow_pt` hasn’t moved at all and stayed at the `head` element. In this case, the left tree should be None.

In total, the time complexity is O(NlogN) where N is the number of elements in the linkedlist.

 

Idea II

However, repeatedly scanning the linkedlist using two pointers causes some waste in time. Another version that is more thrift in time would be using inorder traversal to construct the BST.

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def sortedListToBST(self, head):
        """
        :type head: ListNode
        :rtype: TreeNode
        """
        size = 0
        self.head = head
        while head is not None:
            size += 1
            head = head.next
        return self.helper(0, size)
        
    def helper(self, start, end):
        '''
        start: inclusive
        end: exclusive
        '''
        if start >= end:
            return None
        mid = start + (end-start-1) / 2
        left = self.helper(start, mid)
        root = TreeNode(self.head.val)
        self.head = self.head.next
        right = self.helper(mid+1, end)
        root.left = left
        root.right = right
        return root
        

Since, every node is traversed exactly once, the time complexity is O(N). If you consider stacks used in recursion, the space complexity is O(logN)

Leetcode 106: Construct Binary Tree from Inorder and Postorder Traversal

https://leetcode.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/

Given inorder and postorder traversal of a tree, construct the binary tree.

Note:
You may assume that duplicates do not exist in the tree.

 

Code

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def buildTree(self, inorder, postorder):
        """
        :type inorder: List[int]
        :type postorder: List[int]
        :rtype: TreeNode
        """
        if inorder:
            root = TreeNode(postorder.pop(-1))
            idx = inorder.index(root.val)
            root.right = self.buildTree(inorder[idx+1:], postorder)
            root.left = self.buildTree(inorder[:idx], postorder)
            return root

 

Idea

It is similar to the last post (https://czxttkl.com/?p=1358). Since now you are given postorder list, the root is always at the last element of `postorder`. Besides, the second to the last element is the root of the right tree. Therefore, you should first process `root.right` then `root.left`.

Leetcode 105: Construct Binary Tree from Preorder and Inorder Traversal

https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

Given preorder and inorder traversal of a tree, construct the binary tree.

Note:
You may assume that duplicates do not exist in the tree.

Code

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def buildTree(self, preorder, inorder):
        if inorder:
            ind = inorder.index(preorder.pop(0))
            root = TreeNode(inorder[ind])
            root.left = self.buildTree(preorder, inorder[0:ind])
            root.right = self.buildTree(preorder, inorder[ind+1:])
            return root
        

 

Idea

The root of a preorder list is always `preorder[0]`. You need to find the index of the root in inorder list. After that, you can divide the problem into two smaller problems.

 

I used to use a more memory consuming version (using a lot of slicing). This version ended up with Memory Limit Exceeded exception. Therefore, we should avoid using slicing as much as possible. (See my another post talking about slicing in python: https://czxttkl.com/?p=1353)

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def buildTree(self, preorder, inorder):
        """
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: TreeNode
        """
        root = self.work(preorder, inorder)
        return root
    
    def work(self, preorder, inorder):
        assert len(preorder) == len(inorder)
        if len(preorder) == 0:
            return None
            
        root = TreeNode(preorder[0])
        for idx, num in enumerate(inorder):
            if num == root.val:
                root.left = self.work(preorder[1:idx+1], inorder[:idx])
                root.right = self.work(preorder[idx+1:], inorder[idx+1:])
        return root

 

slicing in numpy array and list

For normal list in Python, slicing copies the references without copying underlying contents. (See the fact that`id(a[1])` and `id(b[0])` are identical below.)

>>> a = [1,2,3]
>>> b = a[1:3]
>>> a
[1, 2, 3]
>>> b
[2, 3]
>>> id(a[1])
25231680
>>> id(b[0])
25231680
>>> b[0] = 999
>>> a
[1, 2, 3]
>>> b
[999, 3]

 

For numpy array, slicing doesn’t copy anything: it just returns a view of the original array. Therefore, changes made in the sliced array is essentially made in the original array.

>>> a = numpy.array([1,2,3])
>>> b = a[1:3]
>>> a
array([1, 2, 3])
>>> b
array([2, 3])
>>> id(a[1])
140093599322520
>>> id(b[0])
140093599322520
>>> b[0]=999
>>> a
array([  1, 999,   3])
>>> b
array([999,   3])

 

Refs:

http://stackoverflow.com/questions/5131538/slicing-a-list-in-python-without-generating-a-copy

http://stackoverflow.com/questions/3485475/can-i-create-a-view-on-a-python-list

Leetcode 123: Best Time to Buy and Sell Stock III

https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/

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).

 

Code

import sys

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        lowest_price = sys.maxint
        profit1 = 0
        max_left_after_profit1 = -sys.maxint
        profit_total = 0
        
        for p in prices:
            lowest_price = min(p, lowest_price)
            profit1 = max(profit1, p - lowest_price)
            max_left_after_profit1 = max(max_left_after_profit1, profit1 - p)
            profit_total = max(profit_total, p + max_left_after_profit1)
        
        return profit_total

 

Idea

Use `profit1` to denote the profit gained from the first transaction. This is similar to what we achieved in the easier problem: https://czxttkl.com/?p=1342. Then `max_left_after_profit1` is the maximum money left in your pocket after you finish the first transaction (buy and sell) and the second buy. 

This method achieves O(N) time complexity and O(1) space.

 

You can also use DP to solve this problem. See the idea here: https://czxttkl.com/?p=990

 

 

Leetcode 122: Best Time to Buy and Sell Stock II

https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/

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).

Code

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        max_profit = 0
        for i in range(1, len(prices)):
            diff = prices[i] - prices[i-1]
            if diff > 0:
                max_profit += diff
                
        return max_profit

 

Idea

Since you can make as many transactions you want, every positive price interval can be one transaction that make profit.

 

 

Leetcode 121: Best Time to Buy and Sell Stock

https://leetcode.com/problems/best-time-to-buy-and-sell-stock/

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.

 

Code

import sys

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        min_price = sys.maxint
        max_profit = 0
        for price in prices:
            min_price = min(min_price, price)
            max_profit = max(max_profit, price - min_price)
        
        return max_profit

 

Idea

`min_price` is the minimum price so far. The maximum profit must happen when `current price – min_price` is max.

 

Variant

If you are given difference array of prices, what would you do?
https://leetcode.com/discuss/48378/kadanes-algorithm-since-mentioned-about-interviewer-twists