Home/Coding Guide/LeetCode Classic Problems
⌂ Main Page
Coding Interview Preparation Guide

LeetCode Classic Problems

Seventeen numbered LeetCode classics — two sum, add two numbers, longest palindromic substring, edit distance, word break, and more — each with multiple solution approaches.

Challenges on this page

01LeetCode 1: Two Sum

Source notebook: 001._two_sum.ipynb

1. Two Sum

Problem:

Given an array of integers and a target value, find the two numbers in the array that add up to the target.

You may assume that each input has exactly one solution, and the same element may not be used twice.

> Example:

Given nums = [2, 7, 11, 15], target = 9

Because nums[0] + nums[1] = 2 + 7 = 9
we return [0, 1]

Difficulty: Easy

This problem is fairly easy; the only thing to watch out for is that the same element may not be used twice.

It is also worth asking: can we find the two numbers with a single pass through the array?

Let's look at how to solve this problem.

Approach 1

Python
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        # loop over the list nums
        for num_one in nums:
            # check whether target - num_one is also in nums, and that the two numbers are not the same element
            if target - num_one in nums and num_one is not target-num_one:
                # return the indices of the two numbers
                return [nums.index(num_one), nums.index(target - num_one)]
            
nums = [4, 3, 5, 15]
target = 8
s = Solution()
print(s.twoSum(nums, target))
Output
[1, 2]

Approach 2

    2         7        11    15
    not seen  found
lookup {2:0} [0, 1]

The overall idea:

Python
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        # create the lookup dictionary
        lookup = {}
        # enumerate yields each element together with its index
        for i, num in enumerate(nums):
            if target - num in lookup:
                return [lookup[target - num],i]
            lookup[num] = i
        return []
    
nums = [4, 3, 5, 15]
target = 8
s = Solution()
print(s.twoSum(nums, target))
Output
[1, 2]

Approach 3

For example, let's first look at the incorrect version:

Python
class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        dict1 = {}
        for k, i in enumerate(nums):
            dict1[i] = k
            if target - i in dict1 and i is not target - i:
                return [dict1[target - i], dict1[i]]

nums = [3, 3]
target = 6
s = Solution()
print(s.twoSum(nums, target))
Output
None

The code above is buggy: for identical elements [3, 3] with target = 6 it returns None, although it should return [0, 1]. So the membership check there is incorrect.

The version below is the correct one:

Python
class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        for k, i in enumerate(nums):
            if target - i in nums[k + 1:]:
                return [k, nums[k + 1:].index(target - i) + k + 1]

nums = [3, 3]
target = 6
s = Solution()
print(s.twoSum(nums, target))       
Output
[0, 1]

Summary

There are certainly even better solutions out there — contributions are welcome. Here's to good work and a good future!

02LeetCode 2: Add Two Numbers

Source notebook: 002._add_two_numbers.ipynb

2. Add Two Numbers

Difficulty: Medium

Problem:

Original links:

Description:

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each node contains a single digit. Add the two numbers and return the sum as a new linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Example:

Input:  (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Reason: 342 + 465 = 807

Solutions

Since adding two digits can produce a carry, the addition itself is trivial — the key point is to account for the carry from the previous digit and propagate it into the next digit's computation.

Approach 1

We first create an empty (dummy) head node that stays fixed, and grow the list by appending new nodes at the tail. Traverse the common part of both lists, at each step adding the corresponding digits plus the carry and appending the result to the output list. After the common part is exhausted, process the remainder of the longer list the same way.

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

class Solution:     
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        # assign p1, p2 = l1, l2 so our operations don't mutate the original lists
        p1, p2 = l1, l2
        # create an empty list for the result, with a head node and a tail node
        head = ListNode(0)
        tail = head
        # carry holds the carry value
        carry = 0
        # process the common part of the two lists, i.e. while both are non-empty
        while p1 and p2:
            # sum of the current digits
            num = p1.val + p2.val + carry
            # greater than 9 -> carry one into the next digit
            if num > 9:
                num -= 10
                carry = 1
            else:
                carry = 0
            # append a node
            tail.next = ListNode(num)
            # advance the tail node
            tail = tail.next
            # advance both lists in their common part
            p1 = p1.next
            p2 = p2.next
        # take the remainder of the longer list, not yet processed above
        if p2:
            # if p2 is longer, assign its remainder to p1 so we only have to handle p1
            p1 = p2
        # now process the remainder of the longer list
        while p1:
            # for this digit, remember to account for a possible carry
            num = p1.val + carry
            if num > 9:
                num -= 10
                carry = 1
            else:
                carry = 0
            # append a node
            tail.next = ListNode(num)
            tail = tail.next
            
            # advance the list we are processing
            p1 = p1.next
        # if a carry remains at the end, allocate one more node
        if carry:
            # create a ListNode with val 1, then advance the tail
            tail.next = ListNode(1)
            tail = tail.next
        # all sums and carries handled; terminate the list
        tail.next = None
        # return the head of the list
        return head.next # drop the dummy head node initialized to 0

la = ListNode(2)
la.next = ListNode(4)
la.next.next = ListNode(3)

lb = ListNode(5)
lb.next = ListNode(6)
lb.next.next = ListNode(4)

s = Solution()
ss = s.addTwoNumbers(la, lb)
print(ss.val)
print(ss.next.val)
print(ss.next.next.val)
print(ss.next.next.next) 
Output
7
0
8
None

Therefore we can also solve this recursively, breaking it into the following cases:

Approach 2

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

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        # special cases
        if l1 == None:
            return l2
        if l2 == None:
            return l1
        # if the sum is less than 10, no carry is needed
        if l1.val + l2.val < 10:
            l3 = ListNode(l1.val + l2.val)
            l3.next = self.addTwoNumbers(l1.next, l2.next)
        # sum is 10 or more: carry needed
        elif l1.val + l2.val >= 10:
            l3 = ListNode(l1.val + l2.val - 10)
            tmp = ListNode(1)
            tmp.next = None
            # recursive call
            l3.next = self.addTwoNumbers(l1.next, self.addTwoNumbers(l2.next ,tmp))
        return l3
    
la = ListNode(2)
la.next = ListNode(4)
la.next.next = ListNode(3)

lb = ListNode(5)
lb.next = ListNode(6)
lb.next.next = ListNode(4)

s = Solution()
ss = s.addTwoNumbers(la, lb)
print(ss.val)
print(ss.next.val)
print(ss.next.next.val)
print(ss.next.next.next)
Output
7
0
8
None

03LeetCode 5: Longest Palindromic Substring

Source notebook: 005._longest_palindromic_substring.ipynb

005. Longest Palindromic Substring

Difficulty: Medium

Problem

Original links

Description

Given a string s, find the longest palindromic substring in s. You may assume the maximum length of s is 1000.

Example 1:
Input:  "babad"
Output: "bab"
Note:   "aba" is also a valid answer.

Example 2:
Input:  "cbbd"
Output: "bb"

Solutions

Approach 1

A string that reads the same left-to-right and right-to-left is called a palindrome, e.g. aba or abba.

The first idea is the most naive one: treat each character in turn as the center of a palindrome and expand outward in both directions, finding the longest palindrome centered at that character. This requires handling odd-length and even-length palindromes separately. The next key detail is boundary handling — make sure indices never go out of range. When the substring already includes the first or last character and is still a palindrome, the indices step one position past each edge and must be compensated for.

Take abba as an example: it has an even number of characters, and the 1st equals the last, the 2nd equals the 2nd-from-last, ..., the Nth equals the Nth-from-last.

Take aba as an example: it has an odd number of characters; excluding the middle character, the 1st equals the last, ..., the Nth equals the Nth-from-last.

So, having found a candidate substring of length len1, we test whether it satisfies: 1st char = last char, 2nd = 2nd-from-last, ..., Nth = Nth-from-last — i.e. we compare characters pairwise from both ends toward the middle. If after [length/2] comparisons all pairs match (the i-th equals the i-th from the end), the substring must be a palindromic string. And clearly this holds whether the length is odd or even, since for odd lengths the middle character trivially equals itself.

The problem thus reduces to finding a substring whose first character equals its last character (call it a candidate substring), and then checking the remaining characters.

Let's look at the code below:

Python
class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        if not s:
            return
        n = len(s)
        if n == 1:
            return s
        # Left index of the target substring
        l = 0
        # Right index of the target substring
        r = 0
        # Length of the longest palindromic substring for now 
        m = 0
        # Length of the current substring
        c = 0
        # Whether the substring contains the first character or last character
        # and is palindromic
        b = True
        # i indexes a character of the string
        for i in range(0, n):
            # odd-length case
            # j tests whether the j characters on both sides of position i form a palindrome; n-i keeps us inside the end of the string, i+1 keeps us from crossing the start
            for j in range(0, min(n - i, i + 1)):
                # expanding around center i: if characters differ, it is not a palindrome, stop comparing
                if (s[i - j] != s[i + j]):
                    b = False
                    break
                else:
                    # if expanding j characters around center i is a palindrome, record its current length in c
                    c = 2 * j + 1
            # compare c with m, the longest palindrome length so far; if c > m, take c as the new longest length, otherwise keep m
            if (c > m):
                # set the new palindrome's left index; b is True == 1, False == 0
                l = i - j + 1 - b
                # set the new palindrome's right index
                r = i + j + b
                # store the new longest palindrome length in m
                m = c
            b = True
            # even-length case
            # i+1 keeps us from crossing the start of the string, n-i-1 from passing its end
            for j in range(0, min(n - i - 1, i + 1)):
                # even case: if the mirrored positions match it is a palindrome, otherwise it is not
                if (s[i - j] != s[i + j + 1]):
                    b = False
                    break
                else:
                    c = 2 * j + 2
            if (c > m):
                l = i - j + 1 - b
                r = i + j + 1 + b
                m = c
            b = True
        # return the final palindrome
        return s[l:r]
    
s = Solution()
nums = "acbcd"
print(s.longestPalindrome(nums))
Output
cbc

The code above is our reference version — the logic is quite clear. The version below is Lisanaaa's polished revision of it; the overall idea is the same, so feel free to compare the two.

Python
class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)

        m,l,r = 0,0,0

        for i in range(n):
            # odd case
            for j in range(min(i+1,n-i)):
                if s[i-j] != s[i+j]:
                    break
                if 2*j + 1 > m :
                    m = 2 * j + 1
                    l = i-j
                    r = i+j


            if i+1 < n and s[i] == s[i+1]:
                for j in range(min(i+1,n-i-1)):
                    if s[i-j] != s[i+j+1]:
                        break
                    if 2 * j + 2 > m :
                        m = 2*j +2
                        l = i-j
                        r = i+j+1


        return s[l:r+1]
    
s = Solution()
nums = "acbcd"
print(s.longestPalindrome(nums))
Output
cbc

Approach 2

This is an obvious dynamic-programming idea.

Python
class Solution2(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        def lcs(s1, s2):
            m = [[0] * (1 + len(s2)) for i in range(1 + len(s1))]
            longest, x_longest = 0, 0
            for x in range(1, 1 + len(s1)):
                for y in range(1, 1 + len(s2)):
                    if s1[x - 1] == s2[y - 1]:
                        m[x][y] = m[x - 1][y - 1] + 1
                        if m[x][y] > longest:
                            longest = m[x][y]
                            x_longest = x
                    else:
                        m[x][y] = 0
            return s1[x_longest - longest: x_longest]

        return lcs(s, s[::-1])
    
s_2 = Solution2()
nums = "acbcd"
print(s_2.longestPalindrome(nums))
Output
cbc

A classic dynamic-programming problem.

Pseudocode:

LCSuff(S1...p, T1...q) = LCS(S1...p1, T1...q-1) if S[p] = T[q] else 0

Reference implementation:

https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Longest_common_substring#Python_2

However, the dynamic-programming solution above still times out.

We assumed that s[::-1] alone would be fast enough.

Important caveat:

This method is buggy. Consider the string abcxgcba: reversed it is abcgxcba. The two share a common substring, yet it contains no palindrome. The fix:

we check if the substring's indices are the same as the reversed substring's original indices. If it is, then we attempt to update the longest palindrome found so far; if not, we skip this and find the next candidate.

My take on the fix:

original  reversed
ABXYBA    ABYXBA

The computed substring indices are 0:2, but s1[0:2] and s2[0:2] are identical positions, so it fails.
Likewise the common substring indices s[4:6] and s2[4:6] coincide — also invalid.

Whereas for ABAD and DABA:

the substring indices are 0:3 in one and 1:4 in the other, so there is no problem.

Approach 3

Manacher's algorithm (nicknamed the "race car" algorithm from its Chinese homophone)

Manacher's algorithm adds two auxiliary variables id and mx, where id is the center of the longest palindromic substring found so far and mx = id + P[id] — i.e. the right boundary of that palindrome. The key result:

Let j = 2 * id - i, i.e. j is the mirror of i about id.

In the program below my P array stores, for each character taken as a palindrome center, the length of that palindrome (excluding the center character itself).

A small example: for the original string 'qacbcaw' the longest palindromic substring is obviously 'acbca'.

So in the final code, max_i is index 8 corresponding to character 'b', start = (max_i - P[max_i] - 1) / 2 = 1, and the final output is s[1:6], i.e. 'acbca'.

Python
class Solution3(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        def preProcess(s):
            if not s:
                return ['^', '&']
            T = ['^']
            for i in s:
                T += ['#', i]
            T += ['#', '$']
            return T
        T = preProcess(s)
        P = [0] * len(T)
        id, mx = 0, 0
        for i in range(1, len(T)-1):
            j = 2 * id - i
            if mx > i:
                P[i] = min(P[j], mx-i)
            else:
                P[i]= 0
            while T[i+P[i]+1] == T[i-P[i]-1]:
                P[i] += 1
            if i + P[i] > mx:
                id, mx = i, i + P[i]
        max_i = P.index(max(P))    # index of the center of the longest palindromic substring
        start = int((max_i - P[max_i] - 1) / 2)
        res = s[start: start + P[max_i]]
        return res
    
s_3 = Solution3()
nums = "acbcd"
print(s_3.longestPalindrome(nums))   
Output
cbc

When you run the code, the result may differ from the expected output, but both answers are valid for that input, so submit with confidence. Also check out problem 647, which can be solved with the same algorithm.

04LeetCode 7: Reverse Integer

Source notebook: 007._Reverse_Integer.ipynb

7. Reverse Integer

Difficulty: Easy

Problem

Original links

Description

Given a 32-bit signed integer, reverse its digits.

Example 1:
Input:  123
Output: 321

Example 2:
Input:  -123
Output: -321

Example 3:
Input:  120
Output: 21

Note:
Assume the environment can only store 32-bit signed integers, whose range is [-2^31, 2^31 - 1]. Under this assumption, return 0 when the reversed integer overflows.

Solutions

Approach 1

Python
class Solution:
    def reverse(self, x):
        """
        :type x: int
        :rtype: int
        """
        # for negatives, remember to work with the absolute value
        flag = 1
        x_1 = 0
        if x < 0:
            flag = -1
            x = int(str(abs(x))[::-1])
            x_1 = x * flag
        else:
            flag = 1
            x = int(str(x)[::-1])
            x_1 = x * flag
        if x_1 > 2**31-1 or x_1 < -2**31:
            return 0
        else:
            return x_1

s = Solution()
x = 120
y = -456
print(s.reverse(x))
print(s.reverse(y))
Output
21
-654

Approach 2

Python
class Solution:
    def reverse(self, x):
        """
        :type x: int
        :rtype: int
        """
        num = 0
        flag = 1
        if x > 0:
            flag = 1
        else:
            flag = -1
        while x != 0:
            num = num * 10 + x % (10 * flag)
            x = int(x / 10)
        if num > 2**31-1 or num < -2**31:
            return 0
        else:
            return num
s = Solution()
x = 120
y = -456
print(s.reverse(x))
print(s.reverse(y))
Output
21
-654

05LeetCode 9: Palindrome Number

Source notebook: 009._Palindrome_Number.ipynb

9. Palindrome Number

Difficulty: Easy

Problem

Original links

Description

Determine whether an integer is a palindrome. A palindrome reads the same forward (left to right) and backward (right to left).

Example 1:

Input:  121
Output: true

Example 2:

Input:  -121
Output: false
Explanation: read left to right it is -121; read right to left it is 121-. Therefore it is not a palindrome.

Example 3:

Input:  10
Output: false
Explanation: read right to left it is 01. Therefore it is not a palindrome.

Follow-up:

Can you solve it without converting the integer to a string?

Solutions

Approach 1

Python
class Solution:
    def isPalindrome(self, x):
        """
        :type x: int
        :rtype: bool
        """
        # negatives are never palindromes; rule them out
        if x < 0:
            return False
        # convert to str, reverse, convert back to int and compare: equal means palindrome, unequal means not
        elif x != int(str(x)[::-1]):
            return False
        else:
            return True
        
s = Solution()
x = 2345
y = 121
print(s.isPalindrome(x))
print(s.isPalindrome(y))
Output
False
True

Approach 2

Python
class Solution:
    def isPalindrome(self, x):
        """
        :type x: int
        :rtype: bool
        """
        # handle special cases:
        # 1. negatives are never palindromes
        # 2. if the last digit is 0, the first digit would also have to be 0 for a palindrome; only 0 itself qualifies
        if x < 0 or (x % 10 == 0 and x is not 0):
            return False
        revertNumber = 0
        while x > revertNumber:
            revertNumber = revertNumber * 10 + x % 10
            x /= 10
        # when the digit count is odd, revertedNumber/10 removes the middle digit.
        # e.g. for input 12321, at the end of the while loop x = 12 and revertedNumber = 123;
        # the middle digit never affects palindromicity (it always equals itself), so we can simply drop it.
        return x == revertNumber or x == revertNumber/10

s = Solution()
x = 2345
y = 121
print(s.isPalindrome(x))
print(s.isPalindrome(y))
Output
False
True

06LeetCode 10: Regular Expression Matching

Source notebook: 010._regular_expression_matching.ipynb

010. Regular Expression Matching

Difficulty: Hard

Problem

Original links

Description

Given an input string (s) and a pattern (p), implement regular-expression matching with support for '.' and '*'.

'.' matches any single character.
'*' matches zero or more of the preceding element.
The match must cover the entire input string (s), not a partial substring.

Notes:
 - s may be empty and contains only lowercase letters a-z.
 - p may be empty and contains only lowercase letters a-z plus the characters . and *.

Example 1:
Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" cannot match the whole string "aa".

Example 2:
Input:
s = "aa"
p = "a*"
Output: true
Explanation: '*' matches zero or more of the preceding element, 'a'. Repeating 'a' once gives "aa".

Example 3:
Input:
s = "ab"
p = ".*"
Output: true
Explanation: ".*" matches zero or more ('*') of any character ('.').

Example 4:
Input:
s = "aab"
p = "c*a*b"
Output: true
Explanation: 'c' can be repeated zero times and 'a' repeated once, matching "aab".

Example 5:
Input:
s = "mississippi"
p = "mis*is*p*."
Output: false

Solutions

Approach 1

Brute force. Credit to Lisanaaa for working this one out — I'd racked my brain over a brute-force solution without covering all the cases. Let's see how he solved it.

"." is easy to handle. The hard part is "": it never appears alone — it always pairs with the preceding letter or ".". Viewed as a pair "X", its behavior is: match zero occurrences, or match a run of consecutive "X". A useful trick for the brute-force attempt is to match from the back toward the front.

The brute-force solution does get accepted (AC).

Case analysis:

Python
class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        def helper(s, i, p, j):
            if j == -1:
                return i == -1
            if i == -1:
                if p[j] != '*':
                    return False
                return helper(s, i, p, j-2)
            if p[j] == '*':
                if p[j-1] == '.' or p[j-1] == s[i]:
                    if helper(s, i-1, p, j):
                        return True
                return helper(s, i, p, j-2)
            if p[j] == '.' or p[j] == s[i]:
                return helper(s, i-1, p, j-1)
            return False

        return helper(s, len(s)-1, p, len(p)-1)
    
s = 'abc'
p = 'a*abc'
ss = Solution()
print(ss.isMatch(s, p))
Output
True

Approach 2

Dynamic programming.

The DP optimization feels a lot like edit distance. Beyond mastering recursion, a key skill for learning DP is being able to draw the table.

Draw a table to see the state:

            c   *   a   *   b
        0   1   2   3   4   5
    0   1   0   1   0   1   0
a   1   0   0   0   1   1   0
a   2   0   0   0   0   1   0
b   3   0   0   0   0   0   1

A few subtle / error-prone points — the table uses 1-based strings. The initial setup:

More code-like:

AC code. Note that the table above uses 1-based strings for convenience; when writing the actual code remember the string is 0-based (though you can also pad the front to simplify indexing).

Python
class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        m, n = len(s), len(p)
        dp = [ [0 for i in range(n+1)] for j in range(m+1)]

        dp[0][0] = 1

        # init the first line
        for i in range(2,n+1):
            if p[i-1] == '*':
                dp[0][i] = dp[0][i-2]

        for i in range(1,m+1):
            for j in range(1,n+1):
                if p[j-1] == '*':
                    if p[j-2] != s[i-1] and p[j-2] != '.':
                        dp[i][j] = dp[i][j-2]
                    elif p[j-2] == s[i-1] or p[j-2] == '.':
                        dp[i][j] = dp[i-1][j] or dp[i][j-2]

                elif s[i-1] == p[j-1] or p[j-1] == '.':
                    dp[i][j] = dp[i-1][j-1]

        return dp[m][n] == 1 
    
s = 'abc'
p = 'a*abc'
ss = Solution()
print(ss.isMatch(s, p))
Output
True

My dynamic-programming solution follows roughly the same idea as above.

Straight to the state-transition equations. Define some variables first: the string being matched is s, the pattern is p. The DP array dp[i][j] holds whether the first j characters of p match the first i characters of s (true on success, false on failure).

Finally, the boundary conditions. Initially i runs from 1 to len(s) and j from 1 to len(p). Clearly dp[0][0] is true and everything else false — but that boundary condition is not sufficient. For example, in isMatch("aab", "cab"), dp[1][3] must be derived from dp[0][2], so we need more boundary handling: a leading '*'-pair can match the empty string. Therefore I start i from 0 and also add the i=0 case to the second rule, dp[i][j] = dp[i][j-2].

Python
class Solution:
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        dp = []
        dp = [[False for i in range(len(p)+1)] for j in range(len(s)+1)]
        dp[0][0] = True
#         print(dp)
        for i in range(len(s)+1):
            for j in range(1, len(p)+1):
                if i > 0 and (s[i-1] == p[j-1] or p[j-1] == '.'):
#                     print("1111111",i)
#                     print("2222222",j)
                    dp[i][j] = dp[i-1][j-1]
#                     print("3333333",dp[i-1][j-1])
                if p[j-1] == '*':
                    if i == 0 or (s[i-1] != p[j-2] and p[j-2] != '.'):
#                         print("4444444",i)
#                         print("5555555",j)
                        dp[i][j] = dp[i][j-2]
#                         print("6666666",dp[i][j-2])
                    else:
                        dp[i][j] = dp[i-1][j] or dp[i][j-1] or dp[i][j-2]
#                         print("7777777",i)
#                         print("8888888",j)
#         print(dp)                       
        return dp[len(s)][len(p)]
    
s = 'aa'
p = 'a*'
ss = Solution()
print(ss.isMatch(s, p))
Output
True

07LeetCode 13: Roman To Integer

Source notebook: 013._Roman_to_Integer.ipynb

013. Roman to Integer

Difficulty: Easy

Problem

Original links

Description

Roman numerals are represented by seven symbols: I, V, X, L, C, D and M.

Symbol        Value
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

For example, 2 is written as II in Roman numerals — two ones side by side. 12 is written as XII, i.e. X + II. 27 is written as XXVII, i.e. XX + V + II.

Roman numerals are usually written largest to smallest from left to right. However, there are exceptions: 4 is not IIII but IV — a 1 before a 5, meaning 5 minus 1, i.e. 4. Likewise 9 is written IX. This subtraction rule applies in exactly six cases:

I can be placed before V (5) and X (10) to make 4 and 9.
X can be placed before L (50) and C (100) to make 40 and 90.
C can be placed before D (500) and M (1000) to make 400 and 900.

Given a Roman numeral, convert it to an integer. The input is guaranteed to be in the range 1 to 3999.

Example 1:
Input:  "III"
Output: 3

Example 2:
Input:  "IV"
Output: 4

Example 3:
Input:  "IX"
Output: 9

Example 4:
Input:  "LVIII"
Output: 58
Explanation: L = 50, V = 5, III = 3.

Example 5:
Input:  "MCMXCIV"
Output: 1994
Explanation: M = 1000, CM = 900, XC = 90, IV = 4.

Solutions

Approach 1

Python
class Solution(object):
    def romanToInt(self, s):
        """
        :type s: str
        :rtype: int
        """
        lookup = {
            'M': 1000,
            'D': 500,
            'C': 100,
            'L': 50,
            'X': 10,
            'V': 5,
            'I': 1
        }
        res = 0
        for i in range(len(s)):
            if i > 0 and lookup[s[i]] > lookup[s[i-1]]:
                res = res + lookup[s[i]] - 2 * lookup[s[i-1]]
            else:
                res += lookup[s[i]]
        return res
    
s = Solution()
string = "MCMXCIV"
print(s.romanToInt(string))
Output
1994

A possible refinement: extract the dictionary lookup into its own function, which reads more elegantly.

Python
def getNum(x):
    return {"I":1,
       "V":5,
       "X":10,
       "L":50,
       "C":100,
       "D":500,
       "M":1000}.get(x)

class Solution:   
    def romanToInt(self, s):
        """
        :type s: str
        :rtype: int
        """
        Num = {
            'M': 1000,
            'D': 500,
            'C': 100,
            'L': 50,
            'X': 10,
            'V': 5,
            'I': 1
        }
        result = 0
        for i in range(len(s)):
            if i > 0 and Num[s[i]] > Num[s[i-1]]:
                result = result + Num[s[i]] - 2 * Num[s[i-1]]
            else:
                result += Num[s[i]]
        return result
    
s = Solution()
string = "MCMXCIV"
print(s.romanToInt(string))
Output
1994

08LeetCode 20: Valid Parentheses

Source notebook: 020._valid_parentheses.ipynb

20. Valid Parentheses

Difficulty: Easy

Problem

Original links

Description

Given a string containing only the characters '(', ')', '{', '}', '[' and ']', determine whether the string is valid.

A string is valid if:

1. Every opening bracket is closed by the same type of bracket.
2. Opening brackets are closed in the correct order.

Note that the empty string is considered valid.

Example 1:
Input:  "()"
Output: true

Example 2:
Input:  "()[]{}"
Output: true

Example 3:
Input:  "(]"
Output: false

Example 4:
Input:  "([)]"
Output: false

Example 5:
Input:  "{[]}"
Output: true

Solutions

Approach 1

We only need to match three pairs: "(" -> ")", "[" -> "]", "{" -> "}".

The essential idea here is the stack: push every opening bracket, pop on every closing bracket, and check the correspondence.

Things to verify:

Python
class Solution:
    def isValid(self, s):
        """
        :type s: str
        :rtype: bool
        """
        LEFT = {'(', '[', '{'}  # opening brackets
        RIGHT = {')', ']', '}'}  # closing brackets
        stack = []  # create a stack
        for brackets in s:  # iterate over the whole input string
            if brackets in LEFT:  # if the current character is an opening bracket
                stack.append(brackets)  # push the opening bracket
            elif brackets in RIGHT:  # if it is a closing bracket
                if not stack or not 1 <= ord(brackets) - ord(stack[-1]) <= 2:
                    # stack is empty, e.g. ()]
                    # or closing minus opening char code is not between 1 and 2 (no pairing)
                    return False  # return False
                stack.pop()  # pop the opening bracket
        return not stack  # True if the stack is empty, otherwise False
    
s = Solution()
print(s.isValid("([[])[]{}"))
print(s.isValid("([])[]{}"))
Output
False
True

Same as Approach 1, but an easier-to-follow version:

Python
class Solution(object):
    def isValid(self, s):
        """
        :type s: str
        :rtype: bool
        """
        leftP = '([{'
        rightP = ')]}'
        stack = []
        for char in s:
            if char in leftP:
                stack.append(char)
            if char in rightP:
                if not stack:
                    return False
                tmp = stack.pop()
                if char == ')' and tmp != '(':
                    return False
                if char == ']' and tmp != '[':
                    return False       
                if char == '}' and tmp != '{':
                    return False
        return stack == []
    
s = Solution()
print(s.isValid("([[])[]{}"))
print(s.isValid("([])[]{}"))
Output
False
True

Approach 2

Python
class Solution:
    def isValid(self, s):
        """
        :type s: str
        :rtype: bool
        """
        if len(s) % 2 == 1:
            return False

        index = 0
        stack = [i for i in s]
        map1 = {"(": ")", "[": "]", "{": "}"}

        while len(stack) > 0:
            # check whether the index runs past the boundary
            if index >= len(stack)-1:
                return False
    
            b = stack[index]
            e = stack[index+1]

            if b not in map1.keys():
                return False
            elif e in map1.keys():
                index += 1
            elif map1[b] == e:
                stack.pop(index+1)
                stack.pop(index)
                index = 0 if index-1<0 else index-1
            else:
                return False

        return stack == []
    
s = Solution()
print(s.isValid("([[])[]{}"))
print(s.isValid("([])[]{}"))
Output
False
True

Approach 3

Python
l_d = {
    '{': -3,
    '(': -2,
    '[': -1,
    ']': 1,
    ')': 2,
    '}': 3,
}
    
class Solution:
    def isValid(self, s):
        l_s = []
        for c_r in s:
            if len(l_s) == 0:
                l_s.append(c_r)
                continue

            c_l = l_s[-1]

            if l_d[c_l] + l_d[c_r] == 0 and l_d[c_l] < 0:
                l_s.pop()
            else:
                l_s.append(c_r)

        if len(l_s) == 0:
            return True
        else:
            return False
        
s = Solution()
print(s.isValid("([[])[]{}"))
print(s.isValid("([])[]{}"))
Output
False
True

09LeetCode 32: Longest Valid Parentheses

Source notebook: 032._longest_valid_parentheses.ipynb

032. Longest Valid Parentheses

Difficulty: Easy

Problem

Original links

Description

Given a string containing only '(' and ')', find the length of the longest substring of valid (well-formed) parentheses.

Example 1:
Input:  "(()"
Output: 2
Explanation: the longest valid substring is "()"

Example 2:
Input:  ")()())"
Output: 4
Explanation: the longest valid substring is "()()"

Solutions

Approach 1

Note: careful — this problem has a trap right out of the gate. The statement glosses over a case such as "(())", where the valid-parentheses substring has length 4, not an error.

Seeing this longest-valid-parentheses problem immediately reminded me of an earlier one — LeetCode 20 — whose solution uses a stack to decide whether a bracket sequence pairs up successfully. We can carry the stack idea over here.

Whenever we meet an opening bracket, or a closing bracket that cannot be paired, we push its index onto the stack; brackets that do pair up are popped. What remains on the stack are the indices of unpaired brackets. We can then measure the gaps between those indices to obtain the maximum paired-parentheses length. Here we first traverse the string once, then traverse the non-empty stack. Keep firmly in mind that the non-empty stack holds the indices of the brackets that failed to match — every matched bracket has already been popped.

Python
class Solution:
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        # create a list used as a stack
        stack = []
        # first pass over the string s
        for i in range(len(s)):
            # if the current character is a closing bracket, check the stack for a matching opening bracket
            if s[i] == ')':
                # if the stack is non-empty and its top index points at an opening bracket in s
                if stack and s[stack[-1]] == '(':
                    # pop the top element and pair it with the current closing bracket
                    stack.pop()
                    # simply continue
                    continue
            stack.append(i)
        # maximum length so far
        max_length = 0
        # initialized to the length of s
        next_index = len(s)
        # traverse the non-empty stack
        while stack:
            # index into s stored at the top of the stack
            cur_index = stack.pop()
            # compute the next candidate valid-parentheses length
            cur_length = next_index - cur_index - 1
            # compare with the stored max_length and keep the larger
            max_length = max(cur_length, max_length)
            # set the right boundary for the next segment and loop until the stack is empty
            next_index = cur_index
        # at the end, next_index is the length of the leading valid segment; return the larger of it and max_length
        return max(next_index, max_length)
            
    
s = Solution()
print(s.longestValidParentheses("()(())"))
print(s.longestValidParentheses("()()(()"))
Output
6
4

Approach 2

Solve it with dynamic programming.

This one was devised by Pianke — the idea is genuinely elegant. Reference: http://www.cnblogs.com/George1994/p/7531574.html

  1. Use a dp array where dp[i] holds the length of the longest valid parentheses substring ending at index i. For example, dp[3] = 2 means the longest valid substring ending at index 3 has length 2.
  2. Clearly dp[i] and dp[i-1] are related: - When the character at index i is "(" — i.e. s[i] == '(' — obviously dp[i] contributes nothing new: dp[i] = dp[i-1] + 0, i.e. nothing to do. - When the character at index i is ")" — i.e. s[i] == ')' — if there is a '(' just before the longest valid substring represented by dp[i-1] that pairs with s[i], then dp[i] = dp[i-1] + 2, and we can keep extending further back (if the earlier part chains on as well).
Python
class Solution(object):
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        if len(s) == 0:
            return 0
        dp = [0 for i in range(len(s))]
        for i in range(1, len(s)):
            if s[i] == ')':
                left = i - 1 - dp[i-1]
                if left >= 0 and s[left] == '(':
                    dp[i] = dp[i-1] + 2
                    if left > 0: # check whether the part before `left` chains onto this match
                        dp[i] += dp[left-1]
        return max(dp)

s = Solution()
print(s.longestValidParentheses("()(())"))
print(s.longestValidParentheses("()()(()"))
Output
6
4

10LeetCode 53: Maximum Subarray

Source notebook: 053._maximum_subarray.ipynb

53. Maximum Subarray

Difficulty: Medium

Problem

Original links

Description

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum, and return that sum.

Example:

Input:  [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: the contiguous subarray [4,-1,2,1] has the largest sum, 6.

Follow-up:
If you have an O(n) solution, try the subtler divide-and-conquer approach.

Solutions

Approach 1

Python
class Solution:
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # initialize to negative infinity
        ans = float('-inf')
        # this loop controls the length of the subarray
        for i in range(1, len(nums)):
            # this loop controls the starting position of the subarray
            for j in range(len(nums)-i):
                big = 0
                big = sum(nums[j : j+i])
                if big > ans:
                    ans = big
        return ans

s = Solution()
nums_1 = [-2,1,-3,4,-1,2,1,-5,4]
nums_2 = [1, -1]
print(s.maxSubArray(nums_1))
print(s.maxSubArray(nums_2))
Output
6
1

Approach 2

Python
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = len(nums)
        maxSum = [nums[0] for i in range(n)]
        for i in range(1,n):
        	maxSum[i] = max(maxSum[i-1] + nums[i], nums[i])
        return max(maxSum)
    
s = Solution()
nums_1 = [-2,1,-3,4,-1,2,1,-5,4]
nums_2 = [1, -1]
print(s.maxSubArray(nums_1))
print(s.maxSubArray(nums_2))
Output
6
1

Approach 3

Kadane's Algorithm — see Wikipedia. The common variant resets negative sums to 0; here it needs a small modification. Reference: http://algorithms.tutorialhorizon.com/kadanes-algorithm-maximum-subarray-problem/

start:
    max_so_far = a[0]
    max_ending_here = a[0]

loop i= 1 to n
  (i) max_end_here = Max(arrA[i], max_end_here+a[i]);
  (ii) max_so_far = Max(max_so_far,max_end_here);

return max_so_far

The AC code follows:

Python
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = len(nums)
        maxSum , maxEnd = nums[0], nums[0]
        
        for i in range(1,n):
            maxEnd = max(nums[i],maxEnd + nums[i])
            maxSum = max(maxEnd,maxSum)
        return maxSum
    
s = Solution()
nums_1 = [-2,1,-3,4,-1,2,1,-5,4]
nums_2 = [1, -1]
print(s.maxSubArray(nums_1))
print(s.maxSubArray(nums_2))
Output
6
1

Approach 4

See CLRS p. 71 — divide and conquer, with pseudocode there.

The maximum subarray sum has three possible locations: the left half, the right half, or straddling the midpoint.

Slower, but accepted; complexity O(NlogN).

Python
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        def find_max_crossing_subarray(nums, low, mid, high):
            left_sum = float('-inf')
            sum = 0
            for i in xrange(mid,low-1,-1):
                sum = sum + nums[i]
                if sum > left_sum:
                    left_sum = sum

            right_sum = float('-inf')
            sum = 0
            for j in range(mid+1,high+1):
                sum = sum + nums[j]
                if sum > right_sum:
                    right_sum = sum

            return left_sum + right_sum

        def find_max_subarray(nums,low,high):
            if low == high: 
                return nums[low]
            else:
                mid = (low + high) / 2
                left_sum = find_max_subarray(nums, low, mid)
                right_sum = find_max_subarray(nums,mid+1,high)
                cross_sum = find_max_crossing_subarray(nums,low,mid,high)
                # print left_sum, right_sum, cross_sum
                # print mid, low, high
                return max(left_sum, right_sum, cross_sum)

        return find_max_subarray(nums, 0, len(nums)-1)

11LeetCode 62: Unique Paths

Source notebook: 062._unique_paths.ipynb

062. Unique Paths

Difficulty: Medium

Problem

Original links

Description

A robot is located at the top-left corner of an m x n grid (marked "Start" in the diagram below).

The robot can only move either down or right at any point in time. It is trying to reach the bottom-right corner of the grid (marked "Finish").

How many unique paths are there?

Note: m and n are at most 100.

Example 1:
Input:  m = 3, n = 2
Output: 3
Explanation:
Starting from the top-left corner there are 3 paths to the bottom-right corner.
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right

Example 2:
Input:  m = 7, n = 3
Output: 28

For example, the picture below shows a 3 x 7 grid. How many possible paths are there?

Solutions

Approach 1

The mathematical route.

At heart this is combinatorics: the walk takes m + n - 2 steps in total, of which m - 1 go one direction — so it is simply choosing m - 1 out of m + n - 2, a factorial problem. Easy! This method beats 99.97%.

Bonus: the math module ships a factorial function; just import math and call it.

Python
class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        def factorial(num):
            res = 1
            for i in range(1, num+1):
                res *= i
            return res
        return factorial(m+n-2)/factorial(n-1)/factorial(m-1)
    
s = Solution()
print(s.uniquePaths(7,3))
Output
28.0

Approach 2

Pianke came up with this one — a great idea!

1 1 1
1 2 3
1 3 6
1 4 10
1 5 15
1 6 21
1 7 28
Python
class Solution:
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        if m < 1 or n < 1:
            return 0
        dp = [0] *n
        dp[0] = 1    
        for i in range(0,m):
            for j in range(1,n):
                dp[j] += dp[j-1]
        return dp[n-1]
    
s = Solution()
print(s.uniquePaths(7,3))
Output
28

12LeetCode 64: Minimum Path Sum

Source notebook: 064._minimum_path_sum.ipynb

064. Minimum Path Sum

Difficulty: Medium

Problem

Original links

Description

Given an m x n grid filled with non-negative numbers, find a path from the top-left corner to the bottom-right corner that minimizes the sum of the numbers along the path.

Note: you can only move down or right at each step.

Example:

Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: the path 1→3→1→1→1 has the minimal sum.

Solutions

Approach 1

Use dynamic programming. Observe that a cell at position [i][j] can be reached by exactly 2 routes: from above ([i-1][j] -> [i][j]) or from the left ([i][j-1] -> [i][j]). We only need to take whichever of the two has the smaller path sum. See also problem 072, Edit Distance.

Python
class Solution:
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        m = len(grid)
        n = len(grid[0])
        dp = grid.copy()
        for i in range(1, n):
            dp[0][i] = dp[0][i-1] + grid[0][i]
        for i in range(1, m):
            dp[i][0] = dp[i-1][0] + grid[i][0]
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = min(dp[i][j-1] + grid[i][j], dp[i-1][j] + grid[i][j])
        return dp[m-1][n-1]
    
s = Solution()
grid = [
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
print(s.minPathSum(grid))
Output
7

The version above is my first draft. Following advice to keep the number of for loops down, I rewrote it into the final version below, which is accepted (AC).

Python
class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        if not grid or len(grid) == 0:
            return 0
        row = len(grid)
        col = len(grid[0]) if row else 0
        dp = [[0 for j in range(col)] for i in range(row)]
        for i in range(row):
            for j in range(col):
                if i > 0 and j > 0:
                    dp[i][j] = min(dp[i-1][j]+grid[i][j], dp[i][j-1]+grid[i][j])
                elif i > 0 and j == 0:
                    dp[i][j] = sum([grid[k][0] for k in range(i+1)])
                elif i == 0 and j > 0:
                    dp[i][j] = sum([grid[0][k] for k in range(j+1)])
                else:
                    dp[i][j] = grid[0][0]
        return dp[-1][-1]
    
s = Solution()
grid = [
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
print(s.minPathSum(grid))
Output
7

13LeetCode 72: Edit Distance

Source notebook: 072._edit_distance.ipynb

072. Edit Distance

Difficulty: Hard

Problem

Original links

Description

Given two words word1 and word2, find the minimum number of operations required to convert word1 into word2.

You may perform the following three operations on a word:

Insert a character
Delete a character
Replace a character

Example 1:
Input:  word1 = "horse", word2 = "ros"
Output: 3
Explanation:
horse -> rorse (replace 'h' with 'r')
rorse -> rose  (delete 'r')
rose  -> ros   (delete 'e')

Example 2:
Input:  word1 = "intention", word2 = "execution"
Output: 5
Explanation:
intention -> inention (delete 't')
inention -> enention  (replace 'i' with 'e')
enention -> exention  (replace 'n' with 'x')
exention -> exection  (replace 'n' with 'c')
exection -> execution (insert 'u')

Solutions

This problem is the canonical dynamic-programming example; Wikipedia has dedicated pages for it:

Approach 1

Here is the idea in detail.

Always keep in mind what dp[i][j] means: the number of operations required to make the first i characters of word1 equal to the first j characters of word2. That is also why the dp matrix is initialized with one extra row and column.

We create a 2-D array dp[][] representing the minimum number of steps needed to edit the first i characters of word1 (indices 0 ~ i-1) into the first j characters of word2 (indices 0 ~ j-1). Then:

If $word1[i] = word2[j]$ then $dp[i][j] = dp[i-1][j-1]$

If $word1[i] != word2[j]$ then $dp[i][j] = min ( dp[i-1][j] , dp[i][j-1], dp[i-1][j-1] ) + 1$

Here is the explanation of that recurrence:

The first case is easy: if character i of word1 equals character j of word2, no operation is needed at this position, so we only care about the result for the substrings with those characters removed.

Let's focus on the three options in the second case:

Suppose the first i+1 characters (indices 0~i) of word1 are "abcde", and the first j+1 characters (indices 0~j) of word2 are "abcddgf". Now word1[i] != word2[j], i.e. 'e' != 'f'.

What should we do next?

Notice that the three interpretations below are exactly the three allowed operations, simulated as the final step. Each adds one extra operation.

Simply put: - 1. delete: dp[i-1][j] + 1 — keeps the optimal count for transforming word1[0~i-1] into word2[0~j]. Since word1's first 0~i-1 characters can already be transformed into word2, we just delete word1's final character — one extra delete operation. - 2. insert: dp[i][j-1] + 1 — keeps the optimal count for transforming word1[0~i] into word2[0~j-1]. Since word1[0~i] only reaches word2's second-to-last position, we append to word1 a character equal to word2's last character — one extra insert operation. - 3. replace: dp[i-1][j-1] + 1 — keeps the optimal count for transforming word1[0~i-1] into word2[0~j-1]. Since word1[0~i-1] reaches word2's second-to-last position and the last characters differ, a single replace of the final character suffices — one extra replace operation.

Whichever of the three we pick, we take the minimum of their values.

Reference: http://www.cnblogs.com/pandora/archive/2009/12/20/levenshtein_distance.html

Now the code:

Python
class Solution:
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        # initialize a (len(word1)+1) x (len(word2)+1) matrix
        matrix = [[i+j for j in range(len(word2) + 1)] for i in range(len(word1) + 1)]
        # to aid understanding, this is what the matrix looks like
        # print(matrix)
        for i in range(1, len(word1)+1):
            for j in range(1,len(word2)+1):
                if word1[i-1] == word2[j-1]:
                    d = 0
                else:
                    d = 1
                matrix[i][j] = min(matrix[i-1][j]+1, matrix[i][j-1]+1, matrix[i-1][j-1]+d)

        return matrix[len(word1)][len(word2)]
    
s = Solution()
word1 = 'horse'
word2 = 'ros'
print(s.minDistance(word1, word2))
Output
3

The way the matrix is generated in the code above can be confusing — I only understood the details after asking around.

Let's print it out.

Python
import numpy as np
juzhen = [[i+j for j in range(len(word2) + 1)] for i in range(len(word1) + 1)]
juzhen = np.mat(juzhen)
juzhen
Output
matrix([[0, 1, 2, 3],
        [1, 2, 3, 4],
        [2, 3, 4, 5],
        [3, 4, 5, 6],
        [4, 5, 6, 7],
        [5, 6, 7, 8]])

Printed numpy-style it is much clearer — rows and columns line up neatly.

What I want to point out:

14LeetCode 139: Word Break

Source notebook: 139._word_break.ipynb

Word Break

Difficulty: Medium

Problem

Original links

Description

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine whether s can be segmented into a space-separated sequence of one or more dictionary words.

Notes:

1. The same dictionary word may be reused multiple times in the segmentation.
2. You may assume the dictionary contains no duplicate words.

Example 1:
Input:  s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: return true because "leetcode" can be segmented as "leet code".

Example 2:
Input:  s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: return true because "applepenapple" can be segmented as "apple pen apple".
     Note that dictionary words may be reused.

Example 3:
Input:  s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false

Solutions

Approach 1

Python
class Solution:
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        # validate the arguments
        if s is None or len(s) < 1 or wordDict is None or len(wordDict) < 1:
            return False
        # match flags: match[i] means s[0..i-1] can be segmented
        length = len(s)
        match = [False for i in range(length + 1)]
        match[0] = True
        
        for i in range(1, length +1):
            for j in range(i):
                if match[j] and s[j:i] in wordDict:
                    match[i] = True
                    break
        return match[length]


sss = Solution()
s = "leetcode"
wordDict = ["leet", "code"]
print(sss.wordBreak(s, wordDict))
Output
True

Approach 2

Python
class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        ok = [True]
        for i in range(1, len(s)+1):
            ok += [any(ok[j] and s[j:i] in wordDict for j in range(i))]
        return ok[-1]
    
sss = Solution()
s = "leetcode"
wordDict = ["leet", "code"]
print(sss.wordBreak(s, wordDict))
Output
True

Different ways of appending to a list vary in speed. A comparison:

>>> from timeit import timeit
>>> timeit('x.append(1)', 'x = []', number=10000000)
1.9880003412529277
>>> timeit('x += 1,',     'x = []', number=10000000)
1.2676891852971721
>>> timeit('x += [1]',    'x = []', number=10000000)
3.361207239950204

So we might be tempted to rewrite the code as:

ok += any(ok[j] and s[j:i] in wordDict for j in range(i))  # raises an error

But this raises TypeError: 'bool' object is not iterable — a bool cannot be added to a list this way, though other types can (with lists themselves, mind the semantics).

So in this case we do it like this:

Python
class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        ok = [True]
        for i in range(1, len(s)+1):
            ok += any(ok[j] and s[j:i] in wordDict for j in range(i)),
        return ok[-1]
    
sss = Solution()
s = "leetcode"
wordDict = ["leet", "code"]
print(sss.wordBreak(s, wordDict))
Output
True

15LeetCode 179: Largest Number

Source notebook: 179._Largest_Number.ipynb

179. Largest Number

Difficulty: Medium

Problem

Original links

Description

Given a list of non-negative integers, arrange them so that they form the largest possible number.

Example 1:
Input:  [10,2]
Output: 210

Example 2:
Input:  [3,30,34,5,9]
Output: 9534330
Note: the result may be very large, so return a string instead of an integer.

Solutions

Approach 1

The sorting relies on the classic string comparison:

Note: Python 3 dropped cmp and only takes key; use functools.cmp_to_key to wrap a comparator.

A cmp function compares two objects (x, y): it returns 1 if x > y, 0 if x == y, and -1 if x < y.

The following is the Python 2 solution — it gets accepted (AC), but under Python 3 it raises the error shown below.

Python
class Solution(object):
    def largestNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: str
        """
        nums = [str(num) for num in nums]
        nums.sort(cmp=lambda x, y: cmp(y+x, x+y))
        return ''.join(num).lstrip('0') if ''.join(num).lstrip('0') else '0'
    
s = Solution()
nums = [10, 2]
print(s.largestNumber(nums))
Output
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-ac3558977848> in <module>()
     11 s = Solution()
     12 nums = [10, 2]
---> 13 print(s.largestNumber(nums))

<ipython-input-1-ac3558977848> in largestNumber(self, nums)
      6         """
      7         nums = [str(num) for num in nums]
----> 8         nums.sort(cmp=lambda x, y: cmp(y+x, x+y))
      9         return ''.join(num).lstrip('0') if ''.join(num).lstrip('0') else '0'
     10 

TypeError: 'cmp' is an invalid keyword argument for this function

Or, even simpler, it can be written like this (works in Python 2; raises an error in Python 3):

Python
class Solution(object):
    def largestNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: str
        """
        nums = [str(num) for num in nums]
        nums.sort(cmp=lambda x, y: cmp(y+x, x+y))
        return ''.join(num).lstrip('0') or '0'
    
s = Solution()
nums = [10, 2]
print(s.largestNumber(nums))
Output
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-af910a43d954> in <module>()
     11 s = Solution()
     12 nums = [10, 2]
---> 13 print(s.largestNumber(nums))

<ipython-input-2-af910a43d954> in largestNumber(self, nums)
      6         """
      7         nums = [str(num) for num in nums]
----> 8         nums.sort(cmp=lambda x, y: cmp(y+x, x+y))
      9         return ''.join(num).lstrip('0') or '0'
     10 

TypeError: 'cmp' is an invalid keyword argument for this function

The solution below is the Python 3 version.

Python
from functools import cmp_to_key

# comparator function
def compare(a, b):
    return int(b + a) - int(a + b)

class Solution(object):
    def largestNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: str
        """
        nums = sorted([str(x) for x in nums], key=cmp_to_key(compare))
        return str(int(''.join(nums)))
    
s = Solution()
nums = [10, 2]
print(s.largestNumber(nums))
Output
210

16LeetCode 242: Valid Anagram

Source notebook: 242._valid_anagram.ipynb

242. Valid Anagram

Difficulty: Easy

Problem

Original links

Description

Given two strings s and t, write a function to determine whether t is an anagram of s.

Example 1:
Input:  s = "anagram", t = "nagaram"
Output: true

Example 2:
Input:  s = "rat", t = "car"
Output: false

Note:
You may assume the strings contain only lowercase letters.

Follow-up:
What if the inputs contain unicode characters? How would you adapt your solution?

Solutions

Approach 1

Python
import collections

class Solution(object):
    def isAnagram(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: bool
        """
        return collections.Counter(s) == collections.Counter(t)
    
s = "anagram"
t = "nagaram"
ss = Solution()
print(ss.isAnagram(s, t))
Output
True

Approach 2

Python
class Solution(object):
    def isAnagram(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: bool
        """
        return sorted(s) == sorted(t)
s = "anagram"
t = "nagaram"
ss = Solution()
print(ss.isAnagram(s, t))
Output
True

Approach 3

Python
class Solution(object):
    def isAnagram(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: bool
        """
        if len(s) != len(t):
            return False
        
        charCnt = [0] * 26
        
        for i in range(len(s)):
            charCnt[ord(s[i]) - 97] += 1
            charCnt[ord(t[i]) - 97] -= 1 
        
        for cnt in charCnt:
            if cnt != 0:
                return False
        return True
    
s = "anagram"
t = "nagaram"
ss = Solution()
print(ss.isAnagram(s, t))
Output
True

17LeetCode 287: Find The Duplicate Number

Source notebook: 287._Find_the_Duplicate_Number.ipynb

287. Find the Duplicate Number

Difficulty: Medium

Problem

Original links

Description

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), at least one duplicate number must exist. Assuming there is exactly one duplicated number, find it.

Example 1:
Input:  [1,3,4,2,2]
Output: 2

Example 2:
Input:  [3,1,3,4,2]
Output: 3

Notes:
You must not modify the array (it is read-only).
You may only use O(1) extra space.
The runtime complexity must be less than O(n2).
There is only one duplicated number, but it may appear more than once.

Solutions

Approach 1

On first reading, the problem doesn't feel very hard. But once we reach the notes — the constraints on the problem — its difficulty suddenly jumps several times over.

The points to watch: - The array contains n+1 integers. - The numbers lie between 1 and n inclusive (but need not be contiguous, e.g. [1,4,4,3,4]). - The duplicated number may repeat many times — not just once, twice, or three times. - The array must not be modified (the killer constraint: my original plan was to sort and then binary-search, which this rule forbids). - Only O(1) extra space is allowed. (O(1) space means the extra memory does not grow with the array length: no matter how long the array, we may use only a fixed amount m that does not change with n.) - Time complexity must be below $O(n^2)$.

Those are essentially all the constraints.

This approach uses binary search + the Pigeonhole Principle.
Wikipedia entry on the pigeonhole principle: https://en.wikipedia.org/wiki/Pigeonhole_principle

The two constraints "no modifying the array" and "constant space" imply: no sorting, and no Map-like data structures.

A runtime below O(n^2) suggests using binary search to reduce one of the n factors to log n.
See LeetCode Discuss: https://leetcode.com/discuss/60830/python-solution-explanation-without-changing-input-array

Binary-search over the answer range, validating each guess with the pigeonhole principle.

By the pigeonhole principle, among n+1 integers in the range [1, n] some number must appear at least twice.
Suppose the candidate is n / 2:
scan the array; if more than n / 2 elements are <= n / 2, a solution must lie within [1, n/2]; otherwise the solution lies in (n/2, n].

Another way to see it:

If n is 5, the possible values are 1 2 3 4 5 — five numbers — while the array size is 6, so some number must appear at least twice.

With no duplicates, the count of numbers <= 1 equals 1;

the count of numbers <= 2 equals 2;

... and likewise for 3, 4, 5.

With a duplicate — say 1 is duplicated — the count of numbers <= 1 is certainly greater than 1.

Based on this, pick a mid among 1 2 3 4 5 and scan the array counting elements <= mid. Is the count <= mid, or > mid?

If the count is <= mid, the numbers 1..mid contain no duplicate, and the duplicate lies in the right half (mid..n) — narrow the search to the right half.

If the count is > mid, the numbers 1..mid contain the duplicate — narrow the search to the left half.
Python
class Solution(object):
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        low, high = 1, len(nums) - 1
        while low <= high:
            mid = (low + high) >> 1
            cnt = sum(x <= mid for x in nums)
            if cnt > mid:
                high = mid - 1
            else:
                low = mid + 1
        return low
    
s = Solution()
nums = [1,2,3,3]
print(s.findDuplicate(nums))
Output
3

Approach 1 runs in $O(nlogn)$; Approach 2 below runs in $O(n)$ time, though it bends the rules somewhat.

Approach 2

One pass to count, another pass to report.

Python
class Solution(object):
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dic = dict()
        for n in nums:
            dic[n] = dic.get(n, 0) + 1
            if dic[n] >= 2:
                return n

s = Solution()
nums = [1,2,3,3]
print(s.findDuplicate(nums))
Output
3

Approach 3

Approach 3 is the fully compliant $O(n)$ solution (Floyd's cycle detection). I haven't completely internalized it yet, so I'll leave it below for study.

Python
class Solution(object):
    def findDuplicate(self, nums):
        # The "tortoise and hare" step.  We start at the end of the array and try
        # to find an intersection point in the cycle.
        slow = 0
        fast = 0
    
        # Keep advancing 'slow' by one step and 'fast' by two steps until they
        # meet inside the loop.
        while True:
            slow = nums[slow]
            fast = nums[nums[fast]]
    
            if slow == fast:
                break
    
        # Start up another pointer from the end of the array and march it forward
        # until it hits the pointer inside the array.
        finder = 0
        while True:
            slow   = nums[slow]
            finder = nums[finder]
    
            # If the two hit, the intersection index is the duplicate element.
            if slow == finder:
                return slow
            
s = Solution()
nums = [1,2,3,3]
print(s.findDuplicate(nums))
Output
3