Leetcode 63: Unique Paths II

https://leetcode.com/problems/unique-paths-ii/

Unique Paths II

Total Accepted: 47668 Total Submissions: 169510 Difficulty: Medium

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 3×3 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.

 

My Code:

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        if len(obstacleGrid)==0 or len(obstacleGrid[0])==0:
            return 0
        
        ways = [0] * len(obstacleGrid[0])        
        ways[0] = 1 
        for j in xrange(0, len(obstacleGrid)):
            for i in xrange(0, len(obstacleGrid[0])):
                if obstacleGrid[j][i]:
                    ways[i] = 0
                elif i:  # only handle cells after the first column
                    ways[i] = ways[i] + ways[i-1] 
        
        return ways[-1]

 

Idea:

Following my previous post, you only need 1D array to store intermediate number of ways reaching `cell[j][i]`. Other than that, the only difference is that you need to handle obstacle. It is intuitive to do that though: you set `ways[i]=0` if you see an obstacle, i.e., no way can reach a cell without obstacle.

 

Leetcode 62: Unique Paths

https://leetcode.com/problems/unique-paths/

Unique Paths

Total Accepted: 62277 Total Submissions: 185422 Difficulty: Medium

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?

My Code

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        if m <0 or n < 0:
            return 0
        
        ways = [[0] * n for _ in xrange(m)]
        for i in xrange(0, n):
            ways[0][i] = 1
        for j in xrange(0,m):
            ways[j][0] = 1
        
        for j in xrange(1,m):
            for i in xrange(1,n):
                ways[j][i] = ways[j-1][i] + ways[j][i-1]
        
        return ways[-1][-1]
        

 

Idea

Use very naive idea of Dynamic Programming by creating an m*n matrix ,`ways`. Each cell records the number of ways that can reach there. At the end, just return `ways[-1][-1]`. This requires `O(m*n)` time and `O(m*n)` space.

The space can be reduced by observing that you are either updating `ways` row by row or column by column. This means you only need to keep O(m) or O(n) space in memory all the time. Below is the updated code (not in Python) (https://leetcode.com/discuss/38353/0ms-5-lines-dp-solution-in-c-with-explanations):

class Solution {
    int uniquePaths(int m, int n) {
        if (m > n) return uniquePaths(n, m);
        vector<int> cur(m, 1);
        for (int j = 1; j < n; j++)
            for (int i = 1; i < m; i++)
                cur[i] += cur[i - 1]; 
        return cur[m - 1];
    }
}; 

 

Another idea is to derive `ways[-1][-1]` using combination formula: you always need `n+m-2` steps to reach (m,n) from (1,1) by either going down or right. Among the `n+m-2` steps, you need exactly `m-1` steps going down. Therefore ways[-1][-1] = $latex C_{n+m-2}^{m-1}$. The code implementing this idea (https://leetcode.com/discuss/9110/my-ac-solution-using-formula):

class Solution {
    public:
        int uniquePaths(int m, int n) {
            int N = n + m - 2;// how much steps we need to do
            int k = m - 1; // number of steps that need to go down
            double res = 1;
            // here we calculate the total possible path number 
            // Combination(N, k) = n! / (k!(n - k)!)
            // reduce the numerator and denominator and get
            // C = ( (n - k + 1) * (n - k + 2) * ... * n ) / k!
            for (int i = 1; i <= k; i++)
                res = res * (N - k + i) / i;
            return (int)res;
        }
    };

 

Leetcode 153: Find Minimum in Rotated Sorted Array

https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/

Find Minimum in Rotated Sorted Array

Total Accepted: 63536 Total Submissions: 186885 Difficulty: Medium

Suppose a sorted array is rotated at some pivot unknown to you beforehand.

(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

Find the minimum element.

You may assume no duplicate exists in the array.

My original code:

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        
        if nums is None or len(nums) == 0:
            return None
        if len(nums)== 1:
            return nums[0]
        
        
        left, right = 0, len(nums)-1
        
        while left < right-1:
            mid = (left+right)/2
            if nums[left] < nums[mid] and nums[mid] > nums[right]:
                left = mid
            elif nums[left] > nums[mid] and nums[mid] < nums[right]:
                right = mid
            else:
                left = right = 0
                
        return min(nums[left], nums[right])

My original idea:

A rotated array has a property: if you do binary search by having `left`, `mid` and `right` pointers, if  nums[left] < nums[mid] and nums[mid] > nums[right], that means the left part is intact and the minimum has been rotated to `nums[mid:right+1]`. Similarly, if nums[left] > nums[mid] and nums[mid] < nums[right]  , that means the right part is intact and the minimum has been rotated to `nums[0:mid+1]`. However, in the code above, I didn’t set `left` and `right` to `mid+1` and `mid-1` as in standard binary search because I was afraid that after excluding the current element `nums[mid]`, I may let the whole subarray being without rotation. I thought my algorithm will not work if the array is in a correct format. For example, if we always set `left` to `mid+1`:

input: [3412]

iteration 0: left=0, right=3, mid=1

iteration 1: left=2, right=3. Now nums[left:right+1] is [12] which has correct order

 

But later I find I can safely set `left` and `right` to `mid+1` and `mid` to not exclude the minimum in `nums[left:right+1]`. As long as the minimum is in `nums[left:right+1]`, the algorithm can finally find the minimum. This can be done by:

setting `left` to mid+1 if nums[mid] > nums[right]. 

setting `right` to mid if nums[left] > nums[mid].

So the more concise code can be (https://leetcode.com/discuss/63514/9-line-python-clean-code): 

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        i = 0
        j = len(nums) - 1
        while i < j:
            m = i + (j - i) / 2
            if nums[m] > nums[j]:
                i = m + 1
            else:
                j = m
        return nums[i]

 

Leetcode 281: Zigzag Iterator

https://leetcode.com/problems/zigzag-iterator/

Zigzag Iterator

Total Accepted: 1714 Total Submissions: 4685 Difficulty: Medium

Given two 1d vectors, implement an iterator to return their elements alternately.

For example, given two 1d vectors:

v1 = [1, 2]
v2 = [3, 4, 5, 6]

By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: [1, 3, 2, 4, 5, 6].

Follow up: What if you are given k 1d vectors? How well can your code be extended to such cases?

Clarification for the follow up question – Update (2015-09-18):
The “Zigzag” order is not clearly defined and is ambiguous for k > 2 cases. If “Zigzag” does not look right to you, replace “Zigzag” with “Cyclic”. For example, given the following input:

[1,2,3]
[4,5,6,7]
[8,9]

It should return [1,4,8,2,5,9,3,6,7].

 

My verbose code:

class ZigzagIterator(object):

    def __init__(self, v1, v2):
        """
        Initialize your data structure here.
        :type v1: List[int]
        :type v2: List[int]
        """
        self.v1 = v1
        self.v2 = v2
        self.pointer = 0
        if len(v1) != 0:
            self.l = 0  
        else:
            self.l = 1
        self.x = 0

    def next(self):
        """
        :rtype: int
        """
        n = None
        if self.l == 0:
            n = self.v1[self.x]
            if self.x < len(self.v2):
                self.l = 1
                # keep self.x same
            else:
                # keep self.l same
                self.x += 1
        else: 
            n = self.v2[self.x]
            if self.x < len(self.v1) - 1:
                self.l = 0
                self.x += 1
            else:
                self.x += 1
        
        self.pointer += 1
        return n
        
        
    def hasNext(self):
        """
        :rtype: bool
        """
        return self.pointer < len(self.v1)+len(self.v2)

# Your ZigzagIterator object will be instantiated and called as such:
# i, v = ZigzagIterator(v1, v2), []
# while i.hasNext(): v.append(i.next())

 

However, there are more elegant codes which leverage Python generator (https://leetcode.com/discuss/57997/python-o-1-space-solutions):

class ZigzagIterator(object):

    def __init__(self, v1, v2):
        self.vals = (v[i] for i in itertools.count() for v in (v1, v2) if i < len(v))
        self.n = len(v1) + len(v2)

    def next(self):
        self.n -= 1
        return next(self.vals)

    def hasNext(self):
        return self.n > 0

Here, self.vals = (v[i] for i in itertools.count() for v in (v1, v2) if i < len(v))  uses `itertools.count()` as a generator to generate infinite integer `i`. For each `i`, if `i < len(v)` then the list `self.vals` will add v[i]. This essentially constitutes a zigzag output in the initialization. However, since `itertools.count()` is just a generator, `self.vals` don’t occupy the memory: every element in `self.val` will be yielded whenever you call `next(self.vals)`. 

 

 

Leetcode 173: Binary Search Tree Iterator

https://leetcode.com/problems/binary-search-tree-iterator/

Binary Search Tree Iterator

Total Accepted: 29015 Total Submissions: 95018 Difficulty: Medium

Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of a BST.

Calling next() will return the next smallest number in the BST.

Note: next() and hasNext() should run in average O(1) time and uses O(h) memory, where h is the height of the tree.

Credits:
Special thanks to @ts for adding this problem and creating all test cases.

Code:

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

class BSTIterator(object):
    def __init__(self, root):
        """
        :type root: TreeNode
        """
        self.stack = []
        self.pushAll(root)
        print [s.val for s in self.stack]

    def hasNext(self):
        """
        :rtype: bool
        """
        return self.stack 

    def next(self):
        """
        :rtype: int
        """
        n = self.stack.pop()
        self.pushAll(n.right)
        return n.val
        
    def pushAll(self, node):
        while node:
            self.stack += [node]
            node = node.left
        
# Your BSTIterator will be called like this:
# i, v = BSTIterator(root), []
# while i.hasNext(): v.append(i.next())

 

Idea

First, load `self.stack` from root to the leftmost leaf. This requires O(log n) memory and time. Then, when you call `next()`, you pop the top element from `self.stack`, which is guaranteed to be smallest. Before you return this, you also need to load this element’s right child’s path to its leftmost child into `self.stack`. To do so, you call `self.pushAll(node.right)`. 

 

 

Leetcode 57: Insert Interval

https://leetcode.com/problems/insert-interval/

Insert Interval

Total Accepted: 43188 Total Submissions: 196069 Difficulty: Hard

Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary).

You may assume that the intervals were initially sorted according to their start times.

Example 1:
Given intervals [1,3],[6,9], insert and merge [2,5] in as [1,5],[6,9].

Example 2:
Given [1,2],[3,5],[6,7],[8,10],[12,16], insert and merge [4,9] in as [1,2],[3,10],[12,16].

This is because the new interval [4,9] overlaps with [3,5],[6,7],[8,10].

Code

# Definition for an interval.
# class Interval(object):
#     def __init__(self, s=0, e=0):
#         self.start = s
#         self.end = e

class Solution(object):
    def insert(self, intervals, newInterval):
        """
        :type intervals: List[Interval]
        :type newInterval: Interval
        :rtype: List[Interval]
        """
        if not intervals:
            return [newInterval]
        strt_idx = self.binary_search(intervals, newInterval.start)        
        end_idx = self.binary_search(intervals, newInterval.end)
        
        if strt_idx ==0 and newInterval.end < intervals[0].start:    # Condition 1
            intervals[0:1] = [newInterval, intervals[0]]
        elif newInterval.start > intervals[strt_idx].end:            # Condition 2
            intervals[strt_idx:end_idx+1] = [intervals[strt_idx], 
                                             Interval(newInterval.start, max(intervals[end_idx].end, newInterval.end))]
        else:                                                        # Condition 3
            intervals[strt_idx:end_idx+1] = [Interval(min(intervals[strt_idx].start, newInterval.start), 
                                             max(intervals[end_idx].end, newInterval.end))]
        return intervals

    def binary_search(self, intervals, t):
        left, right = 0, len(intervals)-1
        while left <= right:
            mid = (left + right) / 2
            if t > intervals[mid].start:
                left = mid + 1
            elif t < intervals[mid].start:
                right = mid - 1
            else:
                return mid
        
        # return index where t >= intervals[index].start
        return left-1 if left - 1 >=0 else 0
        


Idea

Do two rounds of binary search to determine the rightmost interval in `intervals` whose start $latex \leq$ `newInterval.start`. Also determine the rightmost interval whose start $latex \leq$ `newInterval.end`. Then you have three situations to deal with:

ii

Leetcode 159: Longest Substring with At Most Two Distinct Characters

https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/

Longest Substring with At Most Two Distinct Characters

Total Accepted: 4982 Total Submissions: 16108 Difficulty: Hard

Given a string, find the length of the longest substring T that contains at most 2 distinct characters.

For example, Given s = “eceba”,

T is “ece” which its length is 3.

Code

class Solution(object):
    def lengthOfLongestSubstringTwoDistinct(self, s):
        """
        :type s: str
        :rtype: int
        """
        end = dict()
        max_len = 0
        start = 0
        for i,c in enumerate(s):
            if len(end) <2:
                end = i
            else: # len(end) == 2
                if c in end.keys():
                    end = i
                else:
                    end_early_c = min(end.keys(), key=lambda x: end[x])
                    start = end[end_early_c] + 1
                    end.pop(end_early_c, None)
                    end = i
                
            l = i - start + 1
            if l > max_len:
                max_len = l
                
        return max_len

 

Idea

Use a dictionary `end` to record at which indices the previous two distinct characters are last seen. Then, when a new character appears which are not in `end.keys()`, the substring satisfying our rules can only start after one of the two existing characters, whichever is seen more earlier.

for example, `aaabbbaac`. When you see `c`, you can only start after `bb`.  

Leetcode 230: Kth Smallest Element

https://leetcode.com/problems/kth-smallest-element-in-a-bst/

Kth Smallest Element in a BST

Total Accepted: 20098 Total Submissions: 63388 Difficulty: Medium

Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.

Note:
You may assume k is always valid, 1 ≤ k ≤ BST’s total elements.

Follow up:
What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? How would you optimize the kthSmallest routine?

Hint:

  1. Try to utilize the property of a BST.
  2. What if you could modify the BST node’s structure?
  3. The optimal runtime complexity is O(height of BST).

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 kthSmallest(self, root, k):
        """
        :type root: TreeNode
        :type k: int
        :rtype: int
        """
        for v in self.inorder(root):
            if k == 1:
                return v
            else:
                k -= 1
                
    def inorder(self, root):
        if root:
            for v in self.inorder(root.left):
                yield v
            yield root.val
            for v in self.inorder(root.right):
                yield v
        

 

Idea 

We use depth first in order traverse of BST to read element from the smallest to the largest. We can count when each element is read, until we read k element. Here we use `yield` instead of `return`  in the inorder function to be more memory thrifty. 

Locating the smallest element will take O(log N) time, plus you read O(k) element before you return.

 

Reference

https://leetcode.com/discuss/44731/pythonic-approach-with-generator

Leetcode 56: Merge Interval

https://leetcode.com/problems/merge-intervals/

Merge Intervals

Total Accepted: 47467 Total Submissions: 206089 Difficulty: Hard

Given a collection of intervals, merge all overlapping intervals.

For example,
Given [1,3],[2,6],[8,10],[15,18],
return [1,6],[8,10],[15,18].

Code

# Definition for an interval.
# class Interval(object):
#     def __init__(self, s=0, e=0):
#         self.start = s
#         self.end = e

class Solution(object):
    def merge(self, intervals):
        """
        :type intervals: List[Interval]
        :rtype: List[Interval]
        """
        r = []
        for i in sorted(intervals, key=lambda x: x.start):
            if r and i.start <= r[-1].end:
                r[-1].end = max(i.end, r[-1].end)
            else:
                r += [i]
        return r
        

 

Idea

First, sort intervals based on their start points (Achieve in O(nlogn) time). Then, one by one examining whether the current start point is behind or in front of the last effective interval’s end point. The following plot shows the specific rules for update:

interval

 

 

Heapsort basic idea

Just recap the basic idea of heapsort (return an ascending order):

  1. first you build max heap right on the original array. You don’t need to create an additional O(N) space. Instead, just make sure child node index i is always less than parent node (i – 1)/2 by necessary swaps between index i and index (i-1)/2. 
  2. after step 1, you will have a binary tree (heap), such that parent node is always larger than child node. Therefore, the root (first) element is the largest element.
  3. Now, you swap the first element and the last element so that the largest element now goes to the end of the array. At this point the heap property (child node always less than parent node) is broken.
  4. In the range from index 0 to the index right before the last swapped element, re-assure the heap property by swapping elements from the first element if necessary.
  5. Redo step 4 until the range shrinks to only contain index 0. Now the larger an element is, the latter it is in the array.

You don’t need additional space. Swap always happens in place. So heapsort takes O(1) space. Step 1 is called “heapify” which takes O(n) time (see proof [3] [4]). In step 4, for every element being swapping to the front, it needs O(logn) swaps in the worst case. Ovrall, heapsort takes O(nlogn) time. 

Reference:

[1] https://en.wikipedia.org/wiki/Heapsort

[2] http://stackoverflow.com/questions/8938375/an-intuitive-understanding-of-heapsort

[3] http://stackoverflow.com/questions/9755721/how-can-building-a-heap-be-on-time-complexity

[4] https://www.cs.bgu.ac.il/~ds122/wiki.files/Presentation09.pdf