LeetCode day4

views 1140 words

70. Climbing Stairs

Topic: Dynamic Programming

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.

Example 1:

Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Example 2:

Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

n=1时, 有一种, n=2时, 有两种方法 n=3时, 有3种方法, 等于 n=1 和 n=2的方法数相加. 以此类推

Approach1

DP: As we can see this problem can be broken into subproblems, and it contains the optimal substructure property i.e. its optimal solution can be constructed efficiently from optimal solutions of its subproblems, we can use dynamic programming to solve this problem.

One can reach $i^{th}$step in one of the two ways:

  • Taking a single step from $(i-1)^{th}$step.
  • Taking a step of 2 from $(i-2)^{th}$step.

So, the total number of ways to reach $i^{th}$ is equal to sum of ways of reaching $(i-1)^{th}$ step and ways of reaching $(i-2)^{th}$step.

Let $dp[i]$ denotes the number of ways to reach on $i^{th}$step:

$dp[i]=dp[i-1]+dp[i-2]$

def climbStairs(self, n: int) -> int:
    temp = []
    temp.append(1)
    temp.append(2)
    for i in range(2,n):
        temp.append(temp[i-2] + temp[i-1])
    
    return temp[n-1]

Complexity Analysis

Time complexity : $O(n)$. Single loop upto n.

Space complexity : $O(n)$. dp array of size n is used.

Approach2

In the above approach we have used dp array where $dp[i]=dp[i-1]+dp[i-2]$. It can be easily analysed that $dp[i]$is nothing but $i^{th}$fibonacci number.

$Fib(n)=Fib(n-1)+Fib(n-2)$

Now we just have to find $n^{th}$number of the fibonacci series having 1 and 2 their first and second term respectively, i.e. $Fib(1)=1$ and $Fib(2)=2$

def climbStairs(self, n):
    a, b = 1, 1
    for i in range(n):
        a, b = b, a + b
    return a

Complexity Analysis

Time complexity : $O(n)$. Single loop upto nn is required to calculate $n^{th}$fibonacci number.

Space complexity : $O(1)$. Constant space is used.

Approach3

Fibonacci Formula:

Algorithm

We can find $n^{th}$fibonacci number using this formula:

$F_n = 1/\sqrt{5}\left[ \left(\frac{1+\sqrt{5}}{2}\right)^{n} - \left(\frac{1-\sqrt{5}}{2}\right)^{n} \right]$

For the given problem, the Fibonacci sequence is defined by $F_0 = 1$, $F_1= 1$, $F_1= 2$, $F_{n+2}= F_{n+1} + F_n$.

A standard method of trying to solve such recursion formulas is assume $F_n$of the form $F_n= a^n$.

Then, of course, $F_{n+1} = a^{n+1}$ and $F_{n+2}= a^{n+2}$ so the equation becomes $a^{n+2}= a^{n+1}+ a^n$.

If we divide the entire equation by an we arrive at $a^2= a + 1$ or the quadratic equation $a^2 - a- 1= 0$

Solving this by the quadratic formula, we get:

$a=1/\sqrt{5}\left(\left(\frac{1\pm \sqrt{5}}{2}\right)\right)$

The general solution, thus takes the form:

$F_n = A\left(\frac{1+\sqrt{5}}{2}\right)^{n} + B\left(\frac{1-\sqrt{5}}{2}\right)^{n}$

For $n=0$, we get $A + B = 1$

For $n=1$, we get $A\left(\frac{1+\sqrt{5}}{2}\right) + B\left(\frac{1-\sqrt{5}}{2}\right) = 1$

Solving the above equations, we get:

$A = \left(\frac{1+\sqrt{5}}{2\sqrt{5}}\right), B = \left(\frac{1-\sqrt{5}}{2\sqrt{5}}\right)$

Putting these values of AA and BB in the above general solution equation, we get:

$F_n = 1/\sqrt{5}\left( \left(\frac{1+\sqrt{5}}{2}\right)^{n+1} - \left(\frac{1-\sqrt{5}}{2}\right)^{n+1} \right)$

def climbStairs(self, n: int) -> int:
    sqrt5=math.sqrt(5);
    fibn=pow((1+sqrt5)/2,n+1)-pow((1-sqrt5)/2,n+1);
    return int(fibn/sqrt5);

88. Merge Sorted Array

Topic: Array, Two Pointers

Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array.

Note:

  • The number of elements initialized in nums1 and nums2 are m and n respectively.
  • You may assume that nums1 has enough space (size that is greater or equal to m + n) to hold additional elements from nums2.

Example:

Input:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

Output: [1,2,2,3,5,6]

Approach1

Use sorted function:

def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
    """
    Do not return anything, modify nums1 in-place instead.
    """
    nums1[:] = sorted(nums1[:m] + nums2[:n])

Approach2

def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
    """
    Do not return anything, modify nums1 in-place instead.
    """
    while m>0 and n>0:
        if nums1[m-1] > nums2[n-1]:
            nums1[n+m-1] = nums1[m-1]
            m -= 1
        else:
            nums1[n+m-1] = nums2[n-1]
            n -= 1
    if m-1 < 0:  # nums2 left
        nums1[:n] = nums2[:n]
    ## when n decreases to 0 but m is not yet 0, the loop will stop. The first m elements in num2 should be copied to nums1

101. Symmetric Tree

Topic: Tree, Depth-first Search, Breadth-first Search

Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).

For example, this binary tree [1,2,2,3,4,4,3] is symmetric:

    1
   / \
  2   2
 / \ / \
3  4 4  3

But the following [1,2,2,null,3,null,3] is not:

    1
   / \
  2   2
   \   \
   3    3

Note: Bonus points if you could solve it both recursively and iteratively.

Approach1

Recursive:

def isSymmetric(self, root: TreeNode) -> bool:
    def isMirror(L,R):
        if L and R:
            # print(L.val,R.val,L,R)
            return L.val == R.val and isMirror(L.left,R.right) and isMirror(L.right,R.left)
        return L == R
    return not root or isMirror(root.left, root.right)

Approach2

Iterative:

def isSymmetric(self, root: TreeNode) -> bool:
    queue = [root]
    # print(queue,len(queue)) # len = 1
    while queue:
        values = [i.val if i else None for i in queue]
        print(values)
        if values != values[::-1]: return False
        queue = [child for i in queue if i for child in (i.left, i.right)]
    return True

# stdout:
# [1]
# [TreeNode{val: 2, left: TreeNode{val: 3, left: None, right: None}, right: TreeNode{val: 4, left: None, right: None}}, TreeNode{val: 2, left: TreeNode{val: 4, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}}]
# [2, 2]
# [TreeNode{val: 3, left: None, right: None}, TreeNode{val: 4, left: None, right: None}, TreeNode{val: 4, left: None, right: None}, TreeNode{val: 3, left: None, right: None}]
# [3, 4, 4, 3]
# [None, None, None, None, None, None, None, None]
# [None, None, None, None, None, None, None, None]
# []

104. Maximum Depth of Binary Tree

Topic: Tree, Depth-first Search

Given a binary tree, find its maximum depth.

The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.

Note: A leaf is a node with no children.

Example:

Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

return its depth = 3.

Approach1

recursive:

def maxDepth(self, root: TreeNode) -> int:
    if not root:
        return 0
    return max(self.maxDepth(root.left), self.maxDepth(root.right))+1

Approach2

Iterative:

def maxDepth(self, root: TreeNode) -> int:
    if root ==None:
        return 0
    
    stack = [root]
    depth = 0
    while stack:
        nextLevel = []
        depth+=1
        for item in stack:
            if item.left:
                nextLevel.append(item.left)
            if item.right:
                nextLevel.append(item.right)
        stack = nextLevel
    return depth

Approach

def maxDepth(root: TreeNode) -> int:
    queue = [root]
    cnt = 0
    while queue:
        cnt += 1
        queue = [child for i in queue if i for child in (i.left,i.right)]
    return cnt -1