5.2. Medium

5. Longest Palindromic Substring

5. Longest Palindromic Substring

class Solution:
    # DP, Time: O(n^2), Space: O(n^2)
    # 参考:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
    def longestPalindrome(self, s: str) -> str:
        size = len(s)
        if size < 2:
            return s

        dp = [[False for _ in range(size)] for _ in range(size)]

        max_len = 1
        start = 0

        for i in range(size):
            dp[i][i] = True

        for j in range(1, size):
            for i in range(0, j):
                if s[i] == s[j]:
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                else:
                    dp[i][j] = False

                if dp[i][j]:
                    cur_len = j - i + 1
                    if cur_len > max_len:
                        max_len = cur_len
                        start = i
        return s[start:start + max_len]

    """
    def longestPalindrome(self, s: str) -> str:
        res = ""
        for i in range(len(s)):
            # odd case
            tmp = self.helper(s, i, i)
            if len(tmp) > len(res):
                res = tmp
            # even case
            tmp = self.helper(s, i, i + 1)
            if len(tmp) > len(res):
                res = tmp
        return res

    def helper(self, s, l, r):
        while l >= 0 and r < len(s) and s[l] == s[r]:
            l -= 1
            r += 1
        return s[l + 1: r]
    """

62. Unique Paths

62. Unique Paths

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        if not m or not n:
            return 0
        dp = [[1 for _ in range(m)] for _ in range(n)]
        for row in range(1, n):
            for col in range(1, m):
                dp[row][col] = dp[row - 1][col] + dp[row][col - 1]
        return dp[-1][-1]

64. Minimum Path Sum

64. Minimum Path Sum

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        if not grid:
            return 0
        row, col = len(grid), len(grid[0])
        dp = [[0 for _ in range(col)] for _ in range(row)]
        dp[0][0] = grid[0][0]
        for i in range(1, row):
            dp[i][0] = dp[i-1][0] + grid[i][0]
        for i in range(1, col):
            dp[0][i] = dp[0][i-1] + grid[0][i]
        for i in range(1, row):
            for j in range(1, col):
                dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
        return dp[-1][-1]

91. Decode Ways

91. Decode Ways

# DP, Time: O(n), Space: O(n)
class Solution:
    def numDecodings(self, s: str) -> int:
        if not s:
            return 0
        size = len(s)
        dp = [1] + [0] * size
        for i in range(1, size + 1):
            if s[i - 1] != '0':
                dp[i] += dp[i - 1]
            if i >= 2 and 10 <= int(s[i - 2: i]) <= 26:
                dp[i] += dp[i - 2]
        return dp[-1]

95. Unique Binary Search Trees II

95. Unique Binary Search Trees II

class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        def generate(l, r):   # split between [l, r)
            if l == r:
                return [None]
            nodes = []
            for i in range(l, r):
                for lchild in generate(l, i):
                    for rchild in generate(i+1, r):
                        node = TreeNode(i+1)   # +1 to convert the index to the actual value
                        node.left = lchild
                        node.right = rchild
                        nodes.append(node)
            return nodes
        return generate(0, n) if n else []

96. Unique Binary Search Trees

96. Unique Binary Search Trees

class Solution:
    def numTrees(self, n: int) -> int:
        res = [0] * (n + 1)
        res[0] = 1
        for i in range(1, n + 1):
            for j in range(i):
                res[i] += res[j] * res[i-1-j]
        return res[n]

120. Triangle

120. Triangle

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        if not triangle:
            return 0
        res = triangle[-1]
        for i in range(len(triangle) - 2, -1, -1):
            for j in range(len(triangle[i])):
                res[j] = min(res[j], res[j+1]) + triangle[i][j]
        return res[0]

152. Maximum Product Subarray

152. Maximum Product Subarray

class Solution:
    # O(1) space
    def maxProduct(self, nums):
        if not nums:
            return
        locMin = locMax = gloMax = nums[0]
        for i in range(1, len(nums)):
            tmp = locMin
            locMin = min(locMin * nums[i], nums[i], locMax * nums[i])
            locMax = max(tmp * nums[i], nums[i], locMax * nums[i])
            gloMax = max(gloMax, locMax)
        return gloMax

213. House Robber II

213. House Robber II

# Time: O(n), Space: O(1)
class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0
        if len(nums) == 1:
            return nums[0]
        return max(self.helper(nums[:-1]), self.helper(nums[1:]))

    def helper(self, nums):
        if not nums:
            return 0
        if len(nums) == 1:
            return nums[0]
        a, b = nums[0], max(nums[:2])
        for i in range(2, len(nums)):
            a, b = b, max(b, a + nums[i])
        return b

279. Perfect Squares

279. Perfect Squares

# Time: O(n * n ^ 0.5), Space: O(n)
class Solution:
    def numSquares(self, n: int) -> int:
        dp = [i for i in range(n + 1)]
        for i in range(2, n + 1):
            for j in range(1, int(i ** 0.5) + 1):
                dp[i] = min(dp[i], dp[i - j * j] + 1)
        return dp[-1]

300. Longest Increasing Subsequence

300. Longest Increasing Subsequence

# Time: O(n^2), Space: O(n)
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        dp = [1] * len(nums)
        for i in range(len(nums)):
            for j in range(i):
                # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。
                 if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

322. Coin Change

322. Coin Change

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float('inf')] * (amount + 1)
        dp[0] = 0

        for coin in coins:
            for x in range(coin, amount + 1):
                dp[x] = min(dp[x], dp[x - coin] + 1)
        return dp[amount] if dp[amount] != float('inf') else -1

494. Target Sum

494. Target Sum

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        ## RC ##
        ## APPROACH : DP ##
        ## INTUITION : THINK LIKE SUBSET SUM PROBLEM (tushor roy DP solution) Leetcode 416. Partition equal subset sum ##
        # but here  1. our target can range from -totalSum to +totalSum
        #           2. and we dont include True directly from above sequence, coz it is not subsequence we are looking for. so here consider if and only if previous value exists
        # [1,1,1,1,1]
        # 3
        # [
        #   [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
        #   [0, 0, 0, 1, 0, 2, 0, 1, 0, 0, 0],
        #   [0, 0, 1, 0, 3, 0, 3, 0, 1, 0, 0],
        #   [0, 1, 0, 4, 0, 6, 0, 4, 0, 1, 0],
        #   [1, 0, 5, 0, 10, 0, 10, 0, 5, 0, 1]
        # ]

        ## TIME COMPLEXITY : O(N^2) ##
        ## SPACE COMPLEXITY : O(N^2) ##

        totalSum = sum(nums)
        if(S not in range(-1 * totalSum, totalSum + 1) ): return 0
        dp = [ [ 0 for j in range( totalSum*2 + 1 ) ] for i in range(len(nums))]

        ## BASE CASE ## FIRST ROW ##
        dp[0][totalSum + nums[0]] += 1
        dp[0][totalSum - nums[0]] += 1

        for i in range(1, len(nums)):
            for j in range( totalSum*2 + 1 ):

                if( j - nums[i] >= 0 and dp[i-1][j-nums[i]] > 0 ):          # left side
                    dp[i][j] += dp[i-1][j-nums[i]]

                if( j + nums[i] <= totalSum*2 and dp[i-1][j+nums[i]] > 0 ): # right side
                    dp[i][j] += dp[i-1][j+nums[i]]

        return dp[-1][totalSum + S]

        ## APPROACH : BACKTRACKING + MEMOIZATION ##
        def dfs(curr, nums):
            key = (curr, tuple(nums))
            if key in cache: return cache[key]
            if not nums: return 1 if curr == S else 0
            res = dfs(curr - nums[0], nums[1:]) + dfs(curr + nums[0], nums[1:])
            cache[key] = res
            return res
        cache = {}
        return dfs(0, nums)