diff --git a/python/10-Regular-Expression-Matching.py b/python/10-Regular-Expression-Matching.py index db8ad0a4c..b2bd9c665 100644 --- a/python/10-Regular-Expression-Matching.py +++ b/python/10-Regular-Expression-Matching.py @@ -1,3 +1,29 @@ +#bottom up 2D dp +class Solution: + def isMatch(self, s: str, p: str) -> bool: + lenS, lenP = len(s), len(p) + dp = [[False]*(lenP+1) for i in range(lenS+1)] + dp[0][0] = True + + for j in range(1, lenP+1): + if p[j-1] == '*': + dp[0][j] = dp[0][j-2] + + for i in range(1, lenS+1): + for j in range(1, lenP+1): + #if chars match look top left + if p[j-1] in {s[i-1], '.'}: + dp[i][j] = dp[i-1][j-1] + #if we have a * char: + #1. not to use the previous char + #2. look one col up, if True, it was possible to make the previous chars in s with some number of * + #so check if it is possible to make the last char with * as well? (therefore the char before * should match the last s char) + elif p[j-1] == "*": + dp[i][j] = dp[i][j-2] or (dp[i-1][j] and p[j-2] in {s[i-1], '.'}) + + return dp[-1][-1] + + # BOTTOM-UP Dynamic Programming class Solution: def isMatch(self, s: str, p: str) -> bool: diff --git a/python/100-Same-Tree.py b/python/100-Same-Tree.py index 4d55e2588..2e63cb29e 100644 --- a/python/100-Same-Tree.py +++ b/python/100-Same-Tree.py @@ -5,7 +5,19 @@ # self.left = None # self.right = None +#solution 1 +class Solution: + def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: + if not p and not q: + return True + + if not p or not q or p.val != q.va: + return False + return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) + + +#solution 2 class Solution: def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: if not p and not q: diff --git a/python/104-Maximum-Depth-of-Binary-Tree.py b/python/104-Maximum-Depth-of-Binary-Tree.py index ac919b7a0..008ae7cdf 100644 --- a/python/104-Maximum-Depth-of-Binary-Tree.py +++ b/python/104-Maximum-Depth-of-Binary-Tree.py @@ -7,7 +7,7 @@ def maxDepth(self, root: TreeNode) -> int: return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right)) -# ITERATIVE DFS +# ITERATIVE PREORDER DFS class Solution: def maxDepth(self, root: TreeNode) -> int: stack = [[root, 1]] diff --git a/python/1143-Longest-Common-Subsequence.py b/python/1143-Longest-Common-Subsequence.py index 0860720a5..e950c23fe 100644 --- a/python/1143-Longest-Common-Subsequence.py +++ b/python/1143-Longest-Common-Subsequence.py @@ -1,3 +1,56 @@ +#bottom up 2D dynamic programming +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + ROWS=len(text1) + COLS=len(text2) + dp=[[0 for _ in range(COLS+1)] for _ in range(ROWS+1)] + for row in range(1, ROWS+1): + for col in range(1,COLS+1): + if text2[col-1]==text1[row-1]: + dp[row][col]=1+dp[row-1][col-1] + else: + dp[row][col]=max(dp[row-1][col],dp[row][col-1]) + return dp[-1][-1] + +#bottom up 1D dynamic programming- memory optimization with two rows +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + if len(text1) < len(text2): + text1,text2=text2,text1 + ROWS=len(text1) + COLS=len(text2) + above=[0 for _ in range(COLS+1)] + for row in range(1,ROWS+1): + current=[0]* (COLS+1) + for col in range(1,COLS+1): + if text2[col-1]==text1[row-1]: + current[col]=1+above[col-1] + else: + current[col]=max(above[col],current[col-1]) + + above=current + return current[-1] + +#bottom up 1D dynamic programming- memory optimization with two rows- minor time optimization +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + if len(text1) < len(text2): + text1,text2=text2,text1 + ROWS=len(text1) + COLS=len(text2) + above=[0 for _ in range(COLS+1)] + current=[0]* (COLS+1) + for row in range(1,ROWS+1): + for col in range(1,COLS+1): + if text2[col-1]==text1[row-1]: + current[col]=1+above[col-1] + else: + current[col]=max(above[col],current[col-1]) + + current,above=above,current + return above[-1] + + class Solution: def longestCommonSubsequence(self, text1: str, text2: str) -> int: dp = [[0 for j in range(len(text2) + 1)] for i in range(len(text1) + 1)] diff --git a/python/115-Distinct-Subsequences.py b/python/115-Distinct-Subsequences.py index 8f768fbfe..c9d517d9f 100644 --- a/python/115-Distinct-Subsequences.py +++ b/python/115-Distinct-Subsequences.py @@ -1,3 +1,23 @@ +class Solution: + def numDistinct(self, s: str, t: str) -> int: + m = len(s) + n = len(t) + dp = [[0] * (n+1) for _ in range(m+1)] + + for i in range(m+1): + dp[i][0] = 1 + + + for i in range(1, m+1): + for j in range(1, n+1): + dp[i][j] += dp[i-1][j] + if s[i-1] == t[j-1]: + dp[i][j] += dp[i-1][j-1] + + return dp[-1][-1] + + + class Solution: def numDistinct(self, s: str, t: str) -> int: cache = {} diff --git a/python/139-Word-Break.py b/python/139-Word-Break.py index 681107bd2..88658e5e1 100644 --- a/python/139-Word-Break.py +++ b/python/139-Word-Break.py @@ -1,3 +1,34 @@ +#bottom up dynamic programming - with dp padding +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + dp=[False]*(len(s)+1) + dp[0]=True + for i in range(1,len(s)+1): + for word in wordDict: + if i-len(word)>=0 and s[i-len(word):i]==word and dp[i-len(word)]: + dp[i]=True + break + return dp[-1] + +#bottom up dynamic programming - with no dp padding +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + dp = [False] * len(s) + for i in range(len(s)): + for word in wordDict: + # Handle out of bounds case + if i < len(word) - 1: + continue + + if i == len(word) - 1 or dp[i - len(word)]: + if s[i - len(word) + 1:i + 1] == word: + dp[i] = True + break + + return dp[-1] + + + class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> bool: diff --git a/python/143-Reorder-List.py b/python/143-Reorder-List.py index 899ae98a0..6d0703dcf 100644 --- a/python/143-Reorder-List.py +++ b/python/143-Reorder-List.py @@ -2,6 +2,8 @@ class Solution: def reorderList(self, head: ListNode) -> None: # find middle slow, fast = head, head.next + #following will also work + #slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next diff --git a/python/153-Find-Minimum-in-Rotated-Sorted-Array.py b/python/153-Find-Minimum-in-Rotated-Sorted-Array.py index e36c53238..848910079 100644 --- a/python/153-Find-Minimum-in-Rotated-Sorted-Array.py +++ b/python/153-Find-Minimum-in-Rotated-Sorted-Array.py @@ -1,16 +1,10 @@ class Solution: def findMin(self, nums: List[int]) -> int: - res = nums[0] - l, r = 0, len(nums) - 1 - - while l <= r: - if nums[l] < nums[r]: - res = min(res, nums[l]) - break - m = (l + r) // 2 - res = min(res, nums[m]) - if nums[m] >= nums[l]: - l = m + 1 + left, right = 0, len(nums) - 1 + while left < right: + middle = (left + right) // 2 + if nums[middle] < nums[right]: + right = middle else: - r = m - 1 - return res + left = middle + 1 + return nums[left] diff --git a/python/198-House-Robber.py b/python/198-House-Robber.py index 512f67aea..611deefad 100644 --- a/python/198-House-Robber.py +++ b/python/198-House-Robber.py @@ -1,3 +1,57 @@ +class Solution: + def rob(self, nums: List[int]) -> int: + first,second=0,0 + for i in range(len(nums)): + first,second=second, max(first+nums[i],second) + return second + + +class Solution: + def rob(self, nums: List[int]) -> int: + hashmap={-1:0,-2:0} + def helper(n): + if n not in hashmap: + hashmap[n]=max(nums[n]+helper(n-2), helper(n-1)) + return hashmap[n] + return helper(len(nums)-1) + +#recursive with memo +class Solution: + def rob(self, nums: List[int]) -> int: + hashmap={} + def helper(n): + if n not in hashmap: + if n==0: + hashmap[0]=nums[0] + elif n==1: + hashmap[1]=max(nums[0],nums[1]) + else: + hashmap[n]=max(helper(n-1), helper(n-2)+nums[n]) + + return hashmap[n] + + return helper(len(nums)-1) + + +class Solution: + def rob(self, nums: List[int]) -> int: + # edge cases: + if len(nums) == 0: return 0 + if len(nums) == 1: return nums[0] + if len(nums) == 2: return max(nums) + + # dynamic programming - decide each problem by its sub-problems: + dp = [0]*len(nums) + dp[0] = nums[0] + dp[1] = max(nums[0], nums[1]) + for i in range(2, len(nums)): + dp[i] = max(dp[i-1], nums[i]+dp[i-2]) + + return dp[-1] + + + + class Solution: def rob(self, nums: List[int]) -> int: rob1, rob2 = 0, 0 diff --git a/python/20-Valid-Parentheses.py b/python/20-Valid-Parentheses.py index 4d0ae3424..c77771dad 100644 --- a/python/20-Valid-Parentheses.py +++ b/python/20-Valid-Parentheses.py @@ -1,14 +1,12 @@ class Solution: def isValid(self, s: str) -> bool: - Map = {")": "(", "]": "[", "}": "{"} stack = [] - - for c in s: - if c not in Map: - stack.append(c) - continue - if not stack or stack[-1] != Map[c]: - return False - stack.pop() - - return not stack + mapping = {'}':'{', ']':'[', ')':'('} + for letter in s: + if letter in mapping: + if not stack or stack.pop() != mapping[letter]: + return False + else: + stack.append(letter) + if not stack: + return True \ No newline at end of file diff --git a/python/211-Design-Add-and-Search-Words-Data-Structure.py b/python/211-Design-Add-and-Search-Words-Data-Structure.py index 3c5f562aa..e07dd8dd1 100644 --- a/python/211-Design-Add-and-Search-Words-Data-Structure.py +++ b/python/211-Design-Add-and-Search-Words-Data-Structure.py @@ -1,36 +1,82 @@ -class TrieNode: - def __init__(self): - self.children = {} # a : TrieNode - self.word = False - - class WordDictionary: + def __init__(self): - self.root = TrieNode() + """ + Initialize your data structure here. + """ + self.trie = {} + def addWord(self, word: str) -> None: - cur = self.root - for c in word: - if c not in cur.children: - cur.children[c] = TrieNode() - cur = cur.children[c] - cur.word = True + """ + Adds a word into the data structure. + """ + node = self.trie + + for ch in word: + if not ch in node: + node[ch] = {} + node = node[ch] + node['$'] = True def search(self, word: str) -> bool: - def dfs(j, root): - cur = root - - for i in range(j, len(word)): - c = word[i] - if c == ".": - for child in cur.children.values(): - if dfs(i + 1, child): - return True + """ + Returns if the word is in the data structure. A word could contain the dot character '.' to represent any letter. + """ + def search_in_node(word, node) -> bool: + for i, ch in enumerate(word): + if not ch in node: + # if the current character is '.' + # check all possible nodes at this level + if ch == '.': + for x in node: + if x != '$' and search_in_node(word[i + 1:], node[x]): + return True + # if no nodes lead to answer + # or the current character != '.' return False + # if the character is found + # go down to the next level in trie else: - if c not in cur.children: - return False - cur = cur.children[c] - return cur.word + node = node[ch] + return '$' in node + + return search_in_node(word, self.trie) + + + +#class WordNode: + #def __init__(self): + #self.children = {} + #self.isEnd = False + +#class WordDictionary: + #def __init__(self): + #self.root = WordNode() + + #def addWord(self, word): + #node = self.root + #for w in word: + #if w in node.children: + #node = node.children[w] + #else: + #node.children[w] = WordNode() + #node = node.children[w] + #node.isEnd = True - return dfs(0, self.root) + #def search(self, word): + #stack = [(self.root,word)] + #while stack: + #node, w = stack.pop() + #if not w: + #if node.isEnd: + #return True + #elif w[0]=='.': + #for n in node.children.values(): + #stack.append((n,w[1:])) + #else: + #if w[0] in node.children: + #n = node.children[w[0]] + #stack.append((n,w[1:])) + #return False + \ No newline at end of file diff --git a/python/213-House-Robber-II.py b/python/213-House-Robber-II.py index ad7fdf484..8e82dd3eb 100644 --- a/python/213-House-Robber-II.py +++ b/python/213-House-Robber-II.py @@ -3,10 +3,32 @@ def rob(self, nums: List[int]) -> int: return max(nums[0], self.helper(nums[1:]), self.helper(nums[:-1])) def helper(self, nums): - rob1, rob2 = 0, 0 + first,second=0,0 + for i in range(len(nums)): + first,second=second, max(first+nums[i],second) + return second - for n in nums: - newRob = max(rob1 + n, rob2) - rob1 = rob2 - rob2 = newRob - return rob2 +#recursive top down with memo +class Solution: + def rob(self, nums: List[int]) -> int: + hashmap={} + + def helper(n, nums): + if len(nums)==0: + return 0 + if n not in hashmap: + if n==0: + hashmap[0]=nums[0] + elif n==1: + hashmap[1]=max(nums[0],nums[1]) + else: + hashmap[n]=max(helper(n-1,nums), helper(n-2,nums)+nums[n]) + + return hashmap[n] + + + a=helper(len(nums)-2,nums[0:-1]) + hashmap={} + b=helper(len(nums)-2,nums[1:]) + + return max(nums[0],a,b) \ No newline at end of file diff --git a/python/226-Invert-Binary-Tree.py b/python/226-Invert-Binary-Tree.py index 71740bd12..89dc70c46 100644 --- a/python/226-Invert-Binary-Tree.py +++ b/python/226-Invert-Binary-Tree.py @@ -5,15 +5,8 @@ # self.left = left # self.right = right class Solution: - def invertTree(self, root: TreeNode) -> TreeNode: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: if not root: - return None - - # swap the children - tmp = root.left - root.left = root.right - root.right = tmp - - self.invertTree(root.left) - self.invertTree(root.right) + return None + root.left,root.right =self.invertTree(root.right), self.invertTree(root.left) return root diff --git a/python/238-Product-of-array-except-self.py b/python/238-Product-of-array-except-self.py index 0326913a3..634cff3c2 100644 --- a/python/238-Product-of-array-except-self.py +++ b/python/238-Product-of-array-except-self.py @@ -1,13 +1,11 @@ class Solution: def productExceptSelf(self, nums: List[int]) -> List[int]: - res = [1] * (len(nums)) - - prefix = 1 - for i in range(len(nums)): - res[i] = prefix - prefix *= nums[i] - postfix = 1 - for i in range(len(nums) - 1, -1, -1): - res[i] *= postfix - postfix *= nums[i] - return res + + output = [1] * len(nums) + for i in range(len(nums)-1): + output[i+1] = nums[i] * output[i] + mult =1 + for j in reversed(range(len(nums)-1)): + mult *= nums[j+1] + output[j] *= mult + return output \ No newline at end of file diff --git a/python/271-Encode-and-Decode-Strings.py b/python/271-Encode-and-Decode-Strings.py index 722f440d7..03431ea53 100644 --- a/python/271-Encode-and-Decode-Strings.py +++ b/python/271-Encode-and-Decode-Strings.py @@ -1,28 +1,28 @@ -class Solution: - """ - @param: strs: a list of strings - @return: encodes a list of strings to a single string. - """ +class Codec: + def encode(self, strs: List[str]) -> str: + """Encodes a list of strings to a single string. + """ + encoded = '' + for string in strs: + encoded= encoded+ str(len(string)) + '#'+ string + #encoded = chr(258).join(strs) + return encoded + + - def encode(self, strs): - res = "" - for s in strs: - res += str(len(s)) + "#" + s - return res - - """ - @param: str: A string - @return: dcodes a single string to a list of strings - """ - - def decode(self, str): - res, i = [], 0 - - while i < len(str): - j = i - while str[j] != "#": - j += 1 - length = int(str[i:j]) - res.append(str[j + 1 : j + 1 + length]) - i = j + 1 + length - return res + def decode(self, s: str) -> List[str]: + """Decodes a single string to a list of strings. + """ + i = 0 + last_start = 0 + decoded=[] + while i < len(s): + if s[i] == '#': + length = int(s[last_start:i]) + decoded.append(s[i+1:i+length+1]) + last_start = i+length+1 + i +=length+1 + else: + i +=1 + #decoded = s.split(sep= chr(258)) + return decoded \ No newline at end of file diff --git a/python/300-Longest-Increasing-Subsequence.py b/python/300-Longest-Increasing-Subsequence.py index 854767039..674925cab 100644 --- a/python/300-Longest-Increasing-Subsequence.py +++ b/python/300-Longest-Increasing-Subsequence.py @@ -1,3 +1,14 @@ +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + dp = [1] * len(nums) + for i in range(1, len(nums)): + for j in range(i): + if nums[i] > nums[j]: + dp[i] = max(dp[i], dp[j] + 1) + + return max(dp) + + class Solution: def lengthOfLIS(self, nums: List[int]) -> int: LIS = [1] * len(nums) diff --git a/python/309-Best-Time-To-Buy-and-Sell-Stock-With-Cooldown.py b/python/309-Best-Time-To-Buy-and-Sell-Stock-With-Cooldown.py index c95b42883..660fd82b1 100644 --- a/python/309-Best-Time-To-Buy-and-Sell-Stock-With-Cooldown.py +++ b/python/309-Best-Time-To-Buy-and-Sell-Stock-With-Cooldown.py @@ -1,3 +1,43 @@ +#state machine & dp with 3 arrays +class Solution: + def maxProfit(self, prices: List[int]) -> int: + length=len(prices) + sold,reset,hold=[0]*(length+1),[0]*(length+1),[0]*(length+1) + reset[0]=0 + hold[0]=sold[0]=float('-inf') + for i in range(1,length+1): + sold[i]=hold[i-1]+prices[i-1] + reset[i]=max(sold[i-1],reset[i-1]) + hold[i]=max(hold[i-1],reset[i-1]-prices[i-1]) + print(sold,reset,hold) + return max(sold[-1],reset[-1]) + + + +#state machine & 1D dp +class Solution(object): + def maxProfit(self, prices): + """ + :type prices: List[int] + :rtype: int + """ + sold, held, reset = float('-inf'), float('-inf'), 0 + + for price in prices: + # Alternative: the calculation is done in parallel. + # Therefore no need to keep temporary variables + #sold, held, reset = held + price, max(held, reset-price), max(reset, sold) + + pre_sold = sold + sold = held + price + held = max(held, reset - price) + reset = max(reset, pre_sold) + + return max(sold, reset) + + + + class Solution: def maxProfit(self, prices: List[int]) -> int: # State: Buying or Selling? diff --git a/python/312-Burst-Balloons.py b/python/312-Burst-Balloons.py index 0c19c6537..423d42760 100644 --- a/python/312-Burst-Balloons.py +++ b/python/312-Burst-Balloons.py @@ -1,3 +1,35 @@ +#2D dp (or 3D) reverse row, column +class Solution: + def maxCoins(self, nums: List[int]) -> int: + # special case + if len(nums) > 1 and len(set(nums)) == 1: + return (nums[0] ** 3) * (len(nums) - 2) + nums[0] ** 2 + nums[0] + + # handle edge case + nums = [1] + nums + [1] + n = len(nums) + # dp[i][j] represents + # maximum if we burst all nums[left]...nums[right], inclusive + dp = [[0] * n for _ in range(n)] + + # do not include the first one and the last one + # since they are both fake balloons added by ourselves and we can not + # burst them + for left in range(n - 2, 0, -1): + for right in range(left, n - 1): + # find the last burst one in nums[left]...nums[right] + for i in range(left, right + 1): + # nums[i] is the last burst one + gain = nums[left - 1] * nums[i] * nums[right + 1] + # recursively call left side and right side + remaining = dp[left][i - 1] + dp[i + 1][right] + # update + dp[left][right] = max(remaining + gain, dp[left][right]) + # burst nums[1]...nums[n-2], excluding the first one and the last one + return dp[1][n - 2] + + + class Solution: def maxCoins(self, nums: List[int]) -> int: cache = {} diff --git a/python/33-Search-In-Rotated-Sorted-Array.py b/python/33-Search-In-Rotated-Sorted-Array.py index fe01019d4..65f509555 100644 --- a/python/33-Search-In-Rotated-Sorted-Array.py +++ b/python/33-Search-In-Rotated-Sorted-Array.py @@ -1,22 +1,21 @@ class Solution: def search(self, nums: List[int], target: int) -> int: - l, r = 0, len(nums) - 1 - - while l <= r: - mid = (l + r) // 2 - if target == nums[mid]: - return mid - - # left sorted portion - if nums[l] <= nums[mid]: - if target > nums[mid] or target < nums[l]: - l = mid + 1 + l,r=0,len(nums)-1 + while l<=r: + #[5] + m = l + (r-l)//2 + if nums[m] == target: + return m + elif nums[r] >= nums[m]: + if nums[m]< target and target <= nums[r]: + l=m+1 else: - r = mid - 1 - # right sorted portion - else: - if target < nums[mid] or target > nums[r]: - r = mid - 1 + r=m-1 + elif nums[l] <= nums[m]: + if nums[m] > target and target >=nums[l]: + r=m-1 else: - l = mid + 1 + l=m+1 + return -1 + diff --git a/python/39-Combination-Sum.py b/python/39-Combination-Sum.py index 7df4c4b4f..9d5b22580 100644 --- a/python/39-Combination-Sum.py +++ b/python/39-Combination-Sum.py @@ -1,18 +1,62 @@ class Solution: def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - res = [] - def dfs(i, cur, total): - if total == target: - res.append(cur.copy()) + results = [] + + def backtrack(remain, comb, start): + if remain == 0: + # make a deep copy of the current combination + results.append(list(comb)) return - if i >= len(candidates) or total > target: + elif remain < 0: + # exceed the scope, stop exploration. return - cur.append(candidates[i]) - dfs(i, cur, total + candidates[i]) - cur.pop() - dfs(i + 1, cur, total) + for i in range(start, len(candidates)): + # add the number into the combination + comb.append(candidates[i]) + # give the current number another chance, rather than moving on + backtrack(remain - candidates[i], comb, i) + # backtrack, remove the number from the combination + comb.pop() + + backtrack(target, [], 0) + + return results + + + +#class Solution(object): + #def combinationSum(self, candidates, target): + #res = [] + + #def dfs(nums, target, path): + #if target < 0: + #return + #if target == 0: + #res.append(path) + #return + #for i in range(len(nums)): + #dfs(nums[i:], target-nums[i], path+[nums[i]]) + #dfs(candidates, target, []) + #return res + + +#class Solution: + #def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + #res = [] + + #def dfs(i, cur, total): + #if total == target: + #res.append(cur.copy()) + #return + #if i >= len(candidates) or total > target: + #return + + #cur.append(candidates[i]) + #dfs(i, cur, total + candidates[i]) + #cur.pop() + #dfs(i + 1, cur, total) - dfs(0, [], 0) - return res + #dfs(0, [], 0) + #return res diff --git a/python/416-Partition-Equal-Subset-Sum.py b/python/416-Partition-Equal-Subset-Sum.py index 0fa35c087..7af71b81b 100644 --- a/python/416-Partition-Equal-Subset-Sum.py +++ b/python/416-Partition-Equal-Subset-Sum.py @@ -1,18 +1,40 @@ +#bottom up dp, also the next solution OK class Solution: def canPartition(self, nums: List[int]) -> bool: - if sum(nums) % 2: + # find sum of array elements + total_sum = sum(nums) + + # if total_sum is odd, it cannot be partitioned into equal sum subsets + if total_sum % 2 != 0: return False + subset_sum = total_sum // 2 + n = len(nums) - dp = set() - dp.add(0) - target = sum(nums) // 2 + # construct a dp table of size (n+1) x (subset_sum + 1) + dp = [[False] * (subset_sum + 1) for _ in range(n + 1)] + dp[0][0] = True + for i in range(1, n + 1): + curr = nums[i - 1] + for j in range(subset_sum + 1): + if j < curr: + dp[i][j] = dp[i - 1][j] + else: + dp[i][j] = dp[i - 1][j] or dp[i - 1][j - curr] + return dp[n][subset_sum] - for i in range(len(nums) - 1, -1, -1): - nextDP = set() - for t in dp: - if (t + nums[i]) == target: +from collections.abc import Set +class Solution: + def canPartition(self, nums: List[int]) -> bool: + total_sum=sum(nums) + if total_sum %2: + return False + half=total_sum//2 + sum_hashset=set() + sum_hashset.add(0) + for num in nums: + hashset_copy=sum_hashset.copy() + for elem in hashset_copy: + if num+elem ==half: return True - nextDP.add(t + nums[i]) - nextDP.add(t) - dp = nextDP - return False + sum_hashset.add(num+elem) + return False \ No newline at end of file diff --git a/python/424-Longest-Repeating-Character-Replacement.py b/python/424-Longest-Repeating-Character-Replacement.py index 7c6864cdc..fa1501ac9 100644 --- a/python/424-Longest-Repeating-Character-Replacement.py +++ b/python/424-Longest-Repeating-Character-Replacement.py @@ -4,12 +4,10 @@ def characterReplacement(self, s: str, k: int) -> int: res = 0 l = 0 - maxf = 0 for r in range(len(s)): count[s[r]] = 1 + count.get(s[r], 0) - maxf = max(maxf, count[s[r]]) - if (r - l + 1) - maxf > k: + while (r - l + 1) - max(count.values()) > k: count[s[l]] -= 1 l += 1 diff --git a/python/494-Target-Sum.py b/python/494-Target-Sum.py index e8965ecba..652355fd2 100644 --- a/python/494-Target-Sum.py +++ b/python/494-Target-Sum.py @@ -1,3 +1,44 @@ +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) + ROWS= len(nums) + if(S not in range(-1 * totalSum, totalSum + 1) ): return 0 + dp= [[0]*(totalSum*2+1) for _ in range(ROWS+1)] + dp[0][totalSum]=1 + + + for row in range(1,ROWS+1): + for col in range(totalSum*2 + 1): + if col - nums[row-1] >= 0: # left side + dp[row][col] += dp[row-1][col-nums[row-1]] + if col + nums[row-1] <= totalSum*2: # right side + dp[row][col] += dp[row-1][col+nums[row-1]] + + return dp[-1][totalSum + S] + + + + + class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: dp = {} # (index, total) -> # of ways diff --git a/python/518-coin-change-2.py b/python/518-coin-change-2.py index 163d2d06e..f8cbb8ce6 100644 --- a/python/518-coin-change-2.py +++ b/python/518-coin-change-2.py @@ -1,49 +1,72 @@ +#bottom up 2D dp +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + + dp= [[0]* (amount+1) for _ in range(len(coins)+1)] + for row in range(1,len(coins)+1): + dp[row][0]=1 + + for row in range(1,len(coins)+1): + for col in range(1,amount+1): + if col-coins[row-1] >=0: + dp[row][col]=dp[row-1][col]+dp[row][col-coins[row-1]] + else: + dp[row][col]=dp[row-1][col] + + return dp[-1][-1] + +#bottom up 1D dp- space optimization +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + dp = [0] * (amount + 1) + dp[0] = 1 + + for coin in coins: + for j in range(coin, amount + 1): + dp[j] += dp[j - coin] + + return dp[amount] + + +#bottom up 2D dp +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + dp = [[0 for _ in range(amount+1)] for _ in range(len(coins))] + + for row in range(len(coins)): + dp[row][0] = 1 + for row in range(len(coins)): + for col in range(1, amount+1): + if coins[row] > col: + dp[row][col] = dp[row-1][col] + else: + dp[row][col] = dp[row-1][col] + dp[row][col-coins[row]] + return dp[-1][-1] + + +#if we wanted to add a first row of 0s, then row-1 would corespond to coins index +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + + n = len(coins) + M = [[0 for i in range(amount+1)] for x in range(n+1)] + for i in range(n+1): + M[i][0] = 1 + for i in range(1, n+1): + for j in range(1, amount+1): + if coins[i-1] > j: + M[i][j] = M[i-1][j] + else: + M[i][j] = M[i-1][j] + M[i][j-coins[i-1]] + return M[n][amount] + + class Solution: def change(self, amount: int, coins: List[int]) -> int: - # MEMOIZATION - # Time: O(n*m) - # Memory: O(n*m) - cache = {} - - def dfs(i, a): - if a == amount: - return 1 - if a > amount: - return 0 - if i == len(coins): - return 0 - if (i, a) in cache: - return cache[(i, a)] - - cache[(i, a)] = dfs(i, a + coins[i]) + dfs(i + 1, a) - return cache[(i, a)] - - return dfs(0, 0) - - # DYNAMIC PROGRAMMING - # Time: O(n*m) - # Memory: O(n*m) - dp = [[0] * (len(coins) + 1) for i in range(amount + 1)] - dp[0] = [1] * (len(coins) + 1) - for a in range(1, amount + 1): - for i in range(len(coins) - 1, -1, -1): - dp[a][i] = dp[a][i + 1] - if a - coins[i] >= 0: - dp[a][i] += dp[a - coins[i]][i] - return dp[amount][0] - - # DYNAMIC PROGRAMMING - # Time: O(n*m) - # Memory: O(n) where n = amount dp = [0] * (amount + 1) dp[0] = 1 - for i in range(len(coins) - 1, -1, -1): - nextDP = [0] * (amount + 1) - nextDP[0] = 1 - - for a in range(1, amount + 1): - nextDP[a] = dp[a] - if a - coins[i] >= 0: - nextDP[a] += nextDP[a - coins[i]] - dp = nextDP - return dp[amount] + + for coin in coins: + for x in range(coin, amount + 1): + dp[x] += dp[x - coin] + return dp[amount] \ No newline at end of file diff --git a/python/62-Unique-Paths.py b/python/62-Unique-Paths.py index c5c418de6..c3fb1272e 100644 --- a/python/62-Unique-Paths.py +++ b/python/62-Unique-Paths.py @@ -1,3 +1,53 @@ +#bottom up 2D dynamic programming +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + dp=[[0] *(n+1) for _ in range(m+1)] + dp[1][0]=1 + for row in range(1,m+1): + for col in range(1,n+1): + dp[row][col]=dp[row-1][col]+dp[row][col-1] + print(dp) + return dp[-1][-1] + + +#bottom up 2D dynamic programming +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + d = [[1] * n for _ in range(m)] + + for row in range(1, m): + for col in range(1, n): + d[row][col] = d[row - 1][col] + d[row][col - 1] + + return d[m - 1][n - 1] + +#bottom up 1D dynamic programming- memory optimization with one row one variable +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + dp=[0]*(n) + dp[0]=1 + for _ in range(m): + prev=0 + for col in range(n): + dp[col]+=prev + prev=dp[col] + return dp[-1] + +#bottom up 1D dynamic programming- memory optimization with 2 rows +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + + aboveRow = [1] * n + + for _ in range(m - 1): + currentRow = [1] * n + for i in range(1, n): + currentRow[i] = currentRow[i-1] + aboveRow[i] + aboveRow = currentRow + + return aboveRow[-1] + + class Solution: def uniquePaths(self, m: int, n: int) -> int: row = [1] * n diff --git a/python/70-Climbing-Stairs.py b/python/70-Climbing-Stairs.py index e41decaba..b12a824fb 100644 --- a/python/70-Climbing-Stairs.py +++ b/python/70-Climbing-Stairs.py @@ -1,3 +1,20 @@ +class Solution: + def climbStairs(self, n: int) -> int: + first, second= 1,1 + for i in range(n-1): + first, second= second, first+second + return second + +class Solution: + def climbStairs(self, n: int) -> int: + hashmap={0:1,1:1} + def helper(n): + if n not in hashmap: + hashmap[n]=helper(n-1)+helper(n-2) + return hashmap[n] + return helper(n) + + class Solution: def climbStairs(self, n: int) -> int: if n <= 3: diff --git a/python/704-Binary-Search.py b/python/704-Binary-Search.py index df95ce190..5dc83448e 100644 --- a/python/704-Binary-Search.py +++ b/python/704-Binary-Search.py @@ -1,13 +1,13 @@ class Solution: def search(self, nums: List[int], target: int) -> int: - l, r = 0, len(nums) - 1 - - while l <= r: - m = l + ((r - l) // 2) # (l + r) // 2 can lead to overflow - if nums[m] > target: - r = m - 1 - elif nums[m] < target: - l = m + 1 + l,r=0, len(nums)-1 + while l<=r: + mid = (l+r) //2 + if nums[mid] ==target: + return mid + if nums[mid] < target: + l=mid+1 else: - return m + r=mid-1 return -1 + diff --git a/python/72-Edit-Distance.py b/python/72-Edit-Distance.py index 68c05d699..6693a6b1b 100644 --- a/python/72-Edit-Distance.py +++ b/python/72-Edit-Distance.py @@ -1,3 +1,52 @@ +#also next solution is good. this one initializes row 0 and col 0 in the same loop. next one has separate loops +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + COLS=len(word1) + ROWS=len(word2) + + dp=[[0]*(COLS+1) for _ in range(ROWS+1)] + + for row in range(ROWS+1): + for col in range(COLS+1): + if row==0 and col==0: + continue + if row==0: + dp[row][col]=1+ dp[row][col-1] + elif col==0: + dp[row][col]=1+ dp[row-1][col] + + else: + if word1[col-1]== word2[row-1]: + dp[row][col]=dp[row-1][col-1] + else: + dp[row][col]=1+ min(dp[row-1][col],dp[row][col-1],dp[row-1][col-1]) + return dp[-1][-1] + + +#bottom up 2D dp +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + m = len(word1) + n = len(word2) + # dp[i][j] := min # Of operations to convert word1[0..i) to word2[0..j) + dp = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(1, m + 1): + dp[i][0] = i + + for j in range(1, n + 1): + dp[0][j] = j + + for i in range(1, m + 1): + for j in range(1, n + 1): + if word1[i - 1] == word2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + else: + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + + return dp[m][n] + + class Solution: def minDistance(self, word1: str, word2: str) -> int: dp = [[float("inf")] * (len(word2) + 1) for i in range(len(word1) + 1)] diff --git a/python/74-Search-a-2D-Matrix.py b/python/74-Search-a-2D-Matrix.py index 8f986dc25..b16d0ad1b 100644 --- a/python/74-Search-a-2D-Matrix.py +++ b/python/74-Search-a-2D-Matrix.py @@ -1,27 +1,23 @@ class Solution: def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - ROWS, COLS = len(matrix), len(matrix[0]) + rows, cols = len(matrix), len(matrix[0]) - top, bot = 0, ROWS - 1 - while top <= bot: - row = (top + bot) // 2 - if target > matrix[row][-1]: - top = row + 1 - elif target < matrix[row][0]: - bot = row - 1 + up,down= 0, rows-1 + while up<=down: + mid = up + (down-up)//2 + if target < matrix[mid][0]: + down = mid-1 + elif target> matrix[mid][-1]: + up = mid+1 else: break - - if not (top <= bot): - return False - row = (top + bot) // 2 - l, r = 0, COLS - 1 - while l <= r: - m = (l + r) // 2 - if target > matrix[row][m]: - l = m + 1 - elif target < matrix[row][m]: - r = m - 1 - else: + l,r = 0, cols-1 + while l<=r: + m = l+(r-l)//2 + if target == matrix[mid][m]: return True - return False + elif target > matrix[mid][m]: + l= m+1 + else: + r=m-1 + return False \ No newline at end of file diff --git a/python/746-Min-Cost-Climbing-Stairs.py b/python/746-Min-Cost-Climbing-Stairs.py index 0f0bc0bde..e2e2898d5 100644 --- a/python/746-Min-Cost-Climbing-Stairs.py +++ b/python/746-Min-Cost-Climbing-Stairs.py @@ -1,3 +1,32 @@ +#buttomup dp +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + first,second=0,0 + for i in range(2,len(cost)+1): + first, second=second,min(first+cost[i-2],second+cost[i-1]) + return second +#recursive top down with memoization +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + hashmap={0:0,1:0} + def helper(n): + if n not in hashmap: + hashmap[n]= min(helper(n-2)+cost[n-2], helper(n-1)+cost[n-1]) + return hashmap[n] + return helper(len(cost)) + +#recursive with lru-cache +from functools import lru_cache +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + #we cannot pass a list to function with lru_cache, so a helper function is used + @lru_cache + def helper(n): + if n==0 or n==1: + return 0 + return min(helper(n-2)+cost[n-2], helper(n-1)+cost[n-1]) + return helper(len(cost)) + class Solution: def minCostClimbingStairs(self, cost: List[int]) -> int: for i in range(len(cost) - 3, -1, -1): diff --git a/python/875-Koko-Eating-Bananas.py b/python/875-Koko-Eating-Bananas.py index e4a730343..65b2e86b8 100644 --- a/python/875-Koko-Eating-Bananas.py +++ b/python/875-Koko-Eating-Bananas.py @@ -1,17 +1,19 @@ class Solution: def minEatingSpeed(self, piles: List[int], h: int) -> int: - l, r = 1, max(piles) - k = 0 + def calcHours(k): + sum=0 + for pile in piles: + sum += math.ceil(pile/k) + return sum - while l <= r: - m = (l + r) // 2 - totalTime = 0 - for p in piles: - totalTime += ((p - 1) // m) + 1 - if totalTime <= h: - k = m - r = m - 1 + l,r =1, max(piles) + answer=r + while l<=r: + m= l+(r-l)//2 + if calcHours(m) > h: + l=m+1 else: - l = m + 1 - return k + answer=m + r=m-1 + return answer diff --git a/python/91-Decode-ways.py b/python/91-Decode-ways.py index f5b4b5ada..ca0e7237b 100644 --- a/python/91-Decode-ways.py +++ b/python/91-Decode-ways.py @@ -1,34 +1,34 @@ +#best solution - iterative bottom up class Solution: def numDecodings(self, s: str) -> int: - # Memoization - dp = {len(s): 1} + first, second=0,1 - def dfs(i): - if i in dp: - return dp[i] - if s[i] == "0": - return 0 + for i in range(len(s)): + temp_sum=0 + if int(s[i]) >0 and int(s[i])<=9: + temp_sum+=second + if i>0 and int(s[i-1:i+1]) >=10 and int(s[i-1:i+1])<=26: + temp_sum+=first + first=second + second=temp_sum + return second - res = dfs(i + 1) - if i + 1 < len(s) and ( - s[i] == "1" or s[i] == "2" and s[i + 1] in "0123456" - ): - res += dfs(i + 2) - dp[i] = res - return res - return dfs(0) - - # Dynamic Programming - dp = {len(s): 1} - for i in range(len(s) - 1, -1, -1): - if s[i] == "0": - dp[i] = 0 - else: - dp[i] = dp[i + 1] - - if i + 1 < len(s) and ( - s[i] == "1" or s[i] == "2" and s[i + 1] in "0123456" - ): - dp[i] += dp[i + 2] - return dp[0] +class Solution: + def numDecodings(self, s: str) -> int: + if s[0] == "0": + return 0 + + first = 1 + second = 1 + for i in range(1, len(s)): + current = 0 + if s[i] != "0": + current = second + two_digit = int(s[i - 1: i + 1]) + if two_digit >= 10 and two_digit <= 26: + current += first + first = second + second = current + + return second \ No newline at end of file diff --git a/python/97-Interleaving-Strings.py b/python/97-Interleaving-Strings.py index efb43ac60..ebf139293 100644 --- a/python/97-Interleaving-Strings.py +++ b/python/97-Interleaving-Strings.py @@ -1,3 +1,56 @@ +#2D Dynamic programing- start from row 0 coloum 0 +class Solution: + def isInterleave(self, s1: str, s2: str, s3: str) -> bool: + ROWS=len(s2) + COLS=len(s1) + if len(s3) != ROWS+COLS:return False + dp=[[False for _ in range(COLS+1)] for _ in range(ROWS+1)] + dp[0][0]=True + for row in range(ROWS+1): + for col in range(COLS+1): + if col>0 and s3[row+col-1]==s1[col-1]: + if dp[row][col-1]: + dp[row][col]= True + + if row>0 and s3[row+col-1]==s2[row-1]: + if dp[row-1][col]: + dp[row][col]= True + + return dp[-1][-1] + +class Solution: + def isInterleave(self, s1: str, s2: str, s3: str) -> bool: + ROWS=len(s2) + COLS=len(s1) + if len(s3) != ROWS+COLS:return False + dp=[[False for _ in range(COLS+1)] for _ in range(ROWS+1)] + dp[0][0]=True + + for row in range(1,ROWS+1): + if s3[row-1]==s2[row-1]: + if dp[row-1][0]: + dp[row][0]= True + for col in range(1,COLS+1): + if s3[col-1]==s1[col-1]: + if dp[0][col-1]: + dp[0][col]= True + + + for row in range(1,ROWS+1): + for col in range(1,COLS+1): + if s3[row+col-1]==s1[col-1]: + if dp[row][col-1]: + dp[row][col]= True + + if s3[row+col-1]==s2[row-1]: + if dp[row-1][col]: + dp[row][col]= True + + return dp[-1][-1] + + + + class Solution: def isInterleave(self, s1: str, s2: str, s3: str) -> bool: if len(s1) + len(s2) != len(s3): diff --git a/python/983-Min-Cost-For-Tickets.py b/python/983-Min-Cost-For-Tickets.py new file mode 100644 index 000000000..0e7800696 --- /dev/null +++ b/python/983-Min-Cost-For-Tickets.py @@ -0,0 +1,11 @@ +class Solution: + def mincostTickets(self, days: List[int], costs: List[int]) -> int: + + dp = [0]*(days[-1]+1) + days = set(days) + for i in range(1,len(dp)): + if i in days: + dp[i] = min(dp[max(i-1,0)]+costs[0],dp[max(i-7,0)]+costs[1],dp[max(i-30,0)]+costs[2]) + else: + dp[i]=dp[i-1] + return dp[-1]