Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Problem1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Problem1: Combination Sum (https://leetcode.com/problems/combination-sum/)
# Time Complexity: O(2^(m+n)) where m = length of candidates, n = target, at every index we make 2 choices (skip or take) and this branching repeats roughly m+n times, so paths double over and over
# Space Complexity: O(n) where n = target,this is the max depth of the recursion call stack, bounded by how many times we can subtract from target before hitting 0

# Approach:
# We explore all ways to reach the target by trying each candidate at every step.
# At each step, we either skip the current number or take it (staying at the same index since numbers can be reused) then undo the take after exploring it.
# When target hits exactly 0, we save a copy of the current path as a valid combination.

class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
self.result = [] # empty list to store all valid combinations we find
self.helper(candidates, target, 0, []) # start recursion: at index 0, needing full target, empty path so far
return self.result # return all combinations collected during recursion

def helper(self, candidates, target, i, path):
if target < 0 or i == len(candidates): # dead end: either we overshot the target or we have run out of candidates to try
return

if target == 0: # success: the numbers in path add up exactly to target
self.result.append(list(path)) # save a COPY of path, since path keeps changing after this
return

self.helper(candidates, target, i + 1, path) # branch 1 (skip): don't take candidates[i], move to next index

path.append(candidates[i]) # branch 2 (take): add candidates[i] to path

self.helper(candidates, target - candidates[i], i, path) # explore further, staying at index i since we can reuse this number
path.pop() # backtrack: remove candidates[i] so path is clean for the caller
71 changes: 71 additions & 0 deletions Problem2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Problem2: Expression Add Operators(https://leetcode.com/problems/expression-add-operators/)
# Time Complexity: O(4^n * n), We have n minus 1 gaps between digits. At each gap we have up to 4 real choices: plus, minus, times or letting digits join into a longer number. This gives roughly 4^n different expressions we build. Building each expression also costs up to n work,because we copy the path string every time we add a piece to it.
# Space Complexity: O(n),The recursion goes at most n levels deep, one level for each digit we place. Each level holds a path string of length up to about 2n. This does not count the final result list, since that is the required output, not extra working space.

# Approach:
# We walk through the digit string left to right and decide, at each step, how many digits to grab for the next number, then which operator to place before it.
# We carry calc, the running value of the expression and tail, the value of the last piece we added into calc. Tail lets us undo the last addition when we hit a multiply, since multiply must be applied before the operator that came before it.
class Solution:
def addOperators(self, num: str, target: int) -> List[str]:
self.result = [] # holds every finished expression whose value equals target

def helper(num, target, pivot, calc, tail, path):
# pivot is the index where the next number will start
# calc is the value of the expression built so far
# tail is the value of the last piece that was added into calc
# path is the expression string built so far

if pivot == len(num): # we have used every digit in num, so path is a complete expression
if calc == target: # calc already holds the value of path, so no need to evaluate the string
self.result.append(path)
return

for i in range(pivot, len(num)):
# i is the last index we include in the next number
# trying every i in turn lets the next number be one digit, two digits and so on

if num[pivot] == '0' and i != pivot:
# the number we are building starts with 0 and has more than one digit
# that is not allowed, for example 05 is not a valid number
break
# every longer slice from this pivot will also start with this same 0
# so there is no point checking any larger i, we leave the loop

curr = int(num[pivot:i+1])
# take the substring from pivot up to and including i, then turn it into a number
# the plus 1 is needed because slicing stops before the index we give it

if pivot == 0:
# this is the very first number of the expression
# it has no operator in front of it, so we just place it as is
helper(num, target, i+1, curr, curr, path + str(curr))
# calc becomes curr since this is the only piece so far
# tail becomes curr as well since it is also the last piece added

else:
# every number after the first one needs an operator in front of it
# we try all three operators, one after another, not just one of them

# plus branch
helper(num, target, i+1, calc + curr, curr, path + "+" + str(curr))
# add curr to calc, tail becomes curr since curr is now the last piece added

# minus branch
helper(num, target, i+1, calc - curr, -curr, path + "-" + str(curr))
# subtract curr from calc, tail becomes negative curr
# storing tail as negative here makes the multiply undo trick work later
# without needing any special case for minus

# times branch
helper(num, target, i+1, calc - tail + (tail * curr), tail * curr, path + "*" + str(curr))
# first remove tail from calc, undoing the last piece we added
# then add back tail times curr, which is the correct value once multiply
# is applied before the earlier operator
# tail becomes tail times curr, since that whole product is now the last piece

helper(num, target, 0, 0, 0, "")
# start with pivot at 0, calc and tail at 0, and an empty path
# calc and tail being 0 here does not matter since the pivot equals 0 branch
# does not use their old values anyway

return self.result