Topics
Companies Amazon
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
Example 1:
Input: n = 3
Output: [”((()))”,”(()())”,”(())()”,”()(())”,”()()()”]
Example 2:
Input: n = 1
Output: [”()”]
Constraints:
1 <= n <= 8
https://leetcode.com/problems/generate-parentheses/description/
Trick: Add open when you can, add closed when you can
We do not need to pop stuff as we do not modify the original value, we just send a modified version to the next call.
class Solution(object):
def generateParenthesis(self, n):
“””
:type n: int
:rtype: List[str]
“””
ret = []
def rec(val, numopen, numclosed):
if numopen == numclosed == n:
ret.append(““.join(val))
return
if numopen < n:
rec(val + ['('], numopen+1, numclosed)
if numclosed < numopen:
rec(val + [')'], numopen, numclosed+1)
rec([], 0, 0)
return retThe solution set must not contain duplicate subsets. Return the solution in any order.
Example 1:
Input: nums = [1,2,3]
Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
Example 2:
Input: nums = [0]
Output: [[],[0]]
Constraints:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
All the numbers of nums are unique.
class Solution(object):
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
output = [[]]
for num in nums:
output += [cur + [num] for cur in output]
return output
Backtracking
~~~
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
subsets = []
def dfs(i):
if i >= len(nums):
res.append(subsets.copy())
return
subsets.append(nums[i])
dfs(i+1)
subsets.pop()
dfs(i+1)
dfs(0)
return res ~~~The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the
frequency
of at least one of the chosen numbers is different.
The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.
Example 1:
Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
Explanation:
2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
7 is a candidate, and 7 = 7.
These are the only two combinations.
Example 2:
Input: candidates = [2,3,5], target = 8
Output: [[2,2,2,2],[2,3,3],[3,5]]
Example 3:
Input: candidates = [2], target = 1
Output: []
Constraints:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
All elements of candidates are distinct.
1 <= target <= 40
https://leetcode.com/problems/combination-sum/description/
class Solution(object):
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
output = []
def rec(nums, idx):
if nums and sum(nums) == target:
output.append(nums[:])
return
if (idx == len(candidates)) or (nums and sum(nums) > target):
return
rec([candidates[idx]] + nums, idx)
rec(nums, idx + 1)
rec([], 0)
return outputExample 1:
Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Example 2:
Input: nums = [0,1]
Output: [[0,1],[1,0]]
Example 3:
Input: nums = [1]
Output: [[1]]
Constraints:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
All the integers of nums are unique.
https://leetcode.com/problems/permutations/description/
Note the use of Set here, otherwise for each call we have to do n operations.
~~~
class Solution(object):
def permute(self, nums):
“””
:type nums: List[int]
:rtype: List[List[int]]
“””
output = []
seen = set()
def rec(perms):
if len(perms) == len(nums):
output.append(perms[:])
return
for num in nums:
if num not in seen:
seen.add(num)
rec([num] + perms)
seen.remove(num)
rec([])
return output ``` class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
output = [[]]
for n in nums:
temp = []
for p in output:
for i in range(len(p)+1):
temp.append(p[:i] + [n] + p[i:])
output = temp
return outputExample 1:
Input: nums = [1,1,2]
Output:
[[1,1,2],
[1,2,1],
[2,1,1]]
Example 2:
Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Constraints:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
https://leetcode.com/problems/permutations-ii/description/
https://leetcode.com/problems/permutations-ii/description/
Without Sorting - DFS
We maintain a pos dict that stores the numbers that have been seen in the current position, thus we can ignore same elements being repeated. You can also use a set that is created in each loop.
from collections import defaultdict, Counter
class Solution(object):
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
pos_occurance = defaultdict(set)
used = set()
ret = []
def gen_perms(perm, pos):
if pos == len(nums):
ret.append(perm[:])
return
for i in range(len(nums)):
if nums[i] not in pos_occurance[pos] and i not in used:
pos_occurance[pos].add(nums[i])
used.add(i)
gen_perms(perm+[nums[i]], pos+1)
used.remove(i)
pos_occurance[pos] = set()
gen_perms([], 0)
return retWith Sorting
from collections import Counter
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
output = []
counts = Counter(nums)
def rec(perms):
if len(perms) == len(nums):
output.append(perms[:])
return
# Use the unique nums to iterate. This way you accurately track if its been used or not.
for num in counts.keys():
if counts[num]:
counts[num] -= 1
rec([num] + perms)
counts[num] += 1
rec([])
return outputThe solution set must not contain duplicate subsets. Return the solution in any order.
Example 1:
Input: nums = [1,2,2]
Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]
Example 2:
Input: nums = [0]
Output: [[],[0]]
Constraints:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
https://leetcode.com/problems/subsets-ii/description/
The non sort does the same thing, if we notice, we do not make a call without the number if it’s already in the set.
We know that if it’s already in the set, the first occurance, will make the call without itself.
This will make sure to include all subsets.
~~~
et 1123
1 1 2 3
1 1 2
1 1 3
1 2– skip this chain as it will be repeated later.
1
1 2 3 - here is the chain we skipped earlier.
1 2
1 3
1
2 3
2
3
~~~
class Solution(object):
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
output = []
def rec(subsets, idx):
if idx == len(nums):
output.append(subsets[:])
return
rec([nums[idx]] + subsets, idx + 1)
if nums[idx] not in subsets:
rec(subsets, idx + 1)
rec([], 0)
return outputOther way is to sort the nums and if the previous is the same, skip.
add subsets so far each iteration to the output.
Each call go over the all the nums from idx to len(nums) where idx is incremented for each call.
Only the first non dup number will be used if its not the first time using it (i != idx)
eg.
~~~
1123
for ease of example first 1 is 1a and secondis 1b
1a
1a 1b
1a 1b 2
1a 1b 2 3
1a 1b 3
1a 1b
1a 2
1a 2 3
1a 3
1b - Skip as 1b is not idx (0) and its == 1a (i-1 loc)
2 3
2
3
~~~
class Solution(object):
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
output = []
nums.sort()
def rec(subsets, idx):
output.append(subsets[:])
if idx == len(nums):
return
for i in range(idx, len(nums)):
if i != idx and nums[i-1] == nums[i]:
continue
rec(subsets + [nums[i]], i+1)
rec([], 0)
return outputThe word can be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once.
Example 1:
Input: board = [[“A”,”B”,”C”,”E”],[“S”,”F”,”C”,”S”],[“A”,”D”,”E”,”E”]], word = “ABCCED”
Output: true
Example 2:
Input: board = [[“A”,”B”,”C”,”E”],[“S”,”F”,”C”,”S”],[“A”,”D”,”E”,”E”]], word = “SEE”
Output: true
Example 3:
Input: board = [[“A”,”B”,”C”,”E”],[“S”,”F”,”C”,”S”],[“A”,”D”,”E”,”E”]], word = “ABCB”
Output: false
Constraints:
m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board and word consists of only lowercase and uppercase English letters.
Follow up: Could you use search pruning to make your solution faster with a larger board?
https://leetcode.com/problems/word-search/
from collections import deque, Counter
class Solution(object):
def exist(self, board, word):
"""
:type board: List[List[str]]
:type word: str
:rtype: bool
"""
movements = [[0,1], [-1,0], [1,0], [0,-1]]
seen = set()
rows = len(board)
cols = len(board[0])
# If matrix smaller than word
if rows * cols < len(word):
return False
chars = {c for row in board for c in row}
# If board does not have all the characters in the word
if not chars.issuperset(set(word)):
return False
# if the board char counts are not same as word's char count
board_counts = Counter(c for row in board for c in row)
counts_word = Counter(word)
if not counts_word <= board_counts:
return False
word = word if counts_word[word[0]] < counts_word[word[-1]] else word[::-1]
def dfs(ri, ci, word):
if not word:
return True
seen.add((ri,ci))
for rm, cm in movements:
rn, cn = ri + rm, ci + cm
if rn in range(rows) and cn in range(cols) and board[rn][cn] == word[0] and (rn, cn) not in seen:
found = dfs(rn, cn, word[1:])
if found:
return True
seen.remove((ri, ci))
return False
for r in range(rows):
for c in range(cols):
if board[r][c] == word[0]:
found = dfs(r,c, word[1:])
if found:
return TrueMedium
Given a string s, partition s such that every
substring
of the partition is a
palindrome
. Return all possible palindrome partitioning of s.
Example 1:
Input: s = “aab”
Output: [[“a”,”a”,”b”],[“aa”,”b”]]
Example 2:
Input: s = “a”
Output: [[“a”]]
Constraints:
1 <= s.length <= 16 s contains only lowercase English letters.
https://leetcode.com/problems/palindrome-partitioning/description/
https://leetcode.com/problems/palindrome-partitioning/description/
class Solution:
def partition(self, s: str) -> List[List[str]]:
output = []
# Slower
def _isPalindrome(word):
l = 0
r = len(word) - 1
while l < r:
if word[l] != word[r]:
return False
l+=1
r-=1
return True
# This is faster, possibly optimized by python.
def isPalindrome(word):
return word == word[::-1]
def rec(i, palins):
if i == len(s):
output.append(palins[:])
return
for idx in range(i, len(s)):
if isPalindrome(s[i:idx+1]):
rec(idx+1, palins + [s[i:idx+1]])
rec(0, [])
return output
Using Dynamic Programming
Using Memoization + DP (fastest) saves results at every index + reduces time to check for palindrome
from functools import cache
class Solution:
def partition(self, s: str) -> List[List[str]]:
output = []
dp = [[False] * len(s) for _ in range(len(s))]
def isPalin(start, end):
return start == end or (s[start] == s[end] and (end-start<2 or dp[start+1][end-1]))
@cache
def backtrack(i):
if i == len(s):
return -1
out = []
for idx in range(i+1, len(s)+1):
if isPalin(i, idx-1):
consider = [s[i:idx]]
dp[i][idx-1] = True
res = backtrack(idx)
if not res:
return []
elif res == -1:
out.append([s[i:idx]])
else:
for p in res:
out.append(consider + p)
return out
return backtrack(0)
DP
~~~
class Solution:
def partition(self, s):
res = []
dp = [[False] * len(s) for _ in range(len(s))]
def dfs(pals, start):
if start >= len(s):
res.append(pals[:])
return
for end in range(start, len(s)):
if s[start] == s[end] and (end-start <= 2 or dp[start+1][end-1]):
dp[start][end] = True
dfs(pals + [s[start:end+1]], end+1)
dfs([], 0)
return res ~~~Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.
A mapping of digits to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.
Example 1:
Input: digits = “23”
Output: [“ad”,”ae”,”af”,”bd”,”be”,”bf”,”cd”,”ce”,”cf”]
Example 2:
Input: digits = “”
Output: []
Example 3:
Input: digits = “2”
Output: [“a”,”b”,”c”]
Constraints:
0 <= digits.length <= 4 digits[i] is a digit in the range ['2', '9'].
https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
letter_map = {
“2”: “abc”,
“3”: “def”,
“4”: “ghi”,
“5”: “jkl”,
“6”: “mno”,
“7”: “pqrs”,
“8”: “tuv”,
“9”: “wxyz”
}
output = []
def build(i, comb):
if i == len(digits):
output.append(comb[:])
return
for c in letter_map[digits[i]]:
build(i+1, comb+c)
build(0, “”)
return output
The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.
Given an integer n, return all distinct solutions to the n-queens puzzle. You may return the answer in any order.
Each solution contains a distinct board configuration of the n-queens’ placement, where ‘Q’ and ‘.’ both indicate a queen and an empty space, respectively.
Example 1:
Input: n = 4
Output: [[“.Q..”,”…Q”,”Q…”,”..Q.”],[”..Q.”,”Q…”,”…Q”,”.Q..”]]
Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above
Example 2:
Input: n = 1
Output: [[“Q”]]
Constraints:
1 <= n <= 9
https://leetcode.com/problems/n-queens/description/
Trick to know if pos or neg diagonals are occupied
## neg Diag goes from top left to bottom right, in this case along the diag r-c is equal
## Pos diag goes from bottom left to top right in this case along the diag r+c is equal
## For each queen, place it at every colum in the given row to see if its valid position,
## if it is, then call backtrack to pass it on to the next queen and row (each queen will have it's own row)
from collections import defaultdict
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
b = [["." for i in range(n)] for j in range(n)]
occupied_row = set()
occupied_col = set()
occupied_lr = set() # r-c
occupied_rl = set() # r+c
movements = [[0,1], [1,0], [-1,0], [0, -1]]
res = []
def isValid(r,c):
if (r in occupied_row or c in occupied_col or r-c in occupied_lr or r+c in occupied_rl):
return False
return True
def dfs(row, queens, board):
if row == n:
res.append(["".join(row) for row in board])
return
for col in range(n):
if (row in range(n) and col in range(n) and isValid(row, col)):
occupied_row.add(row)
occupied_col.add(col)
occupied_lr.add(row-col)
occupied_rl.add(row+col)
board[row][col] = "Q"
dfs(row+1, queens, board[:])
occupied_row.remove(row)
occupied_col.remove(col)
occupied_lr.remove(row-col)
occupied_rl.remove(row+col)
board[row][col] = "."
return
dfs(0, 0, b[:])
return resWe are given n different types of stickers. Each sticker has a lowercase English word on it.
You would like to spell out the given string target by cutting individual letters from your collection of stickers and rearranging them. You can use each sticker more than once if you want, and you have infinite quantities of each sticker.
Return the minimum number of stickers that you need to spell out target. If the task is impossible, return -1.
Note: In all test cases, all words were chosen randomly from the 1000 most common US English words, and target was chosen as a concatenation of two random words.
Example 1:
Input: stickers = [“with”,”example”,”science”], target = “thehat”
Output: 3
Explanation:
We can use 2 “with” stickers, and 1 “example” sticker.
After cutting and rearrange the letters of those stickers, we can form the target “thehat”.
Also, this is the minimum number of stickers necessary to form the target string.
Example 2:
Input: stickers = [“notice”,”possible”], target = “basicbasic”
Output: -1
Explanation:
We cannot form the target “basicbasic” from cutting letters from the given stickers.
Constraints:
n == stickers.length 1 <= n <= 50 1 <= stickers[i].length <= 10 1 <= target.length <= 15 stickers[i] and target consist of lowercase English letters.
Start with brute force and optimize, this can take all 30-40 mins
https://leetcode.com/problems/stickers-to-spell-word/description/
from collections import Counter
from functools import cache
class Solution:
def minStickers(self, stickers: List[str], target: str) -> int:
wordCounter = [Counter(s) for s in stickers]
# n = len(target)
# m = avg. len(sticker)
# M = num of stickers
# Time complexity (worst): M^n * m*n
# Time complexity (avg): 2^n * m*n
# Space: 2^n
def apply_sticker(sticker, state):
for c in sticker:
# clear out used letters up to how many letters are available in sticker.
state = state.replace(c, '', sticker[c])
return state
# since it is possible similar states can occur after applying different stickers, we cache the state response.
@cache
def dfs(state):
if not state:
return 0
# If we can't apply any sticker, we will return this infinity to signal, impossible.
result = float('inf')
# (s)
for sticker in wordCounter:
if state[0] in sticker:
# this is to prevent infinite loop where we apply a sticker that can't remove
# letter and call the dfs with same state. it's eq. to
# if sticker did not remove any letter afer applying, we continue to the next one.
# but with this check we do not need to apply the sticker saving mxn time because
# at some point there will be a state where it's 0 letter "Might" be in the current sticker
# hence we will eventually reach a state where we can apply this sticker.
s = apply_sticker(sticker, state)
# we can now call the method again for remaining states, since we used the sticker
# for each sticker we see if it's best option to apply it. 1 is for using 1 sticker.
# result will be updated. If none of the stickers can be used for this state, we return inf.
result = min(result, 1 + dfs(s))
return result
res = dfs(target)
return res if res < float('inf') else -1Companies: Facebook, Google 2024
You are controlling a robot that is located somewhere in a room. The room is modeled as an m x n binary grid where 0 represents a wall and 1 represents an empty slot.
The robot starts at an unknown location in the room that is guaranteed to be empty, and you do not have access to the grid, but you can move the robot using the given API Robot.
You are tasked to use the robot to clean the entire room (i.e., clean every empty cell in the room). The robot with the four given APIs can move forward, turn left, or turn right. Each turn is 90 degrees.
When the robot tries to move into a wall cell, its bumper sensor detects the obstacle, and it stays on the current cell.
Design an algorithm to clean the entire room using the following APIs:
interface Robot {
// returns true if next cell is open and robot moves into the cell.
// returns false if next cell is obstacle and robot stays on the current cell.
boolean move();
// Robot will stay on the same cell after calling turnLeft/turnRight.
// Each turn will be 90 degrees.
void turnLeft();
void turnRight();
// Clean the current cell.
void clean();
}
Note that the initial direction of the robot will be facing up. You can assume all four edges of the grid are all surrounded by a wall.
Custom testing:
The input is only given to initialize the room and the robot’s position internally. You must solve this problem “blindfolded”. In other words, you must control the robot using only the four mentioned APIs without knowing the room layout and the initial robot’s position.
Example 1:
Input: room = [[1,1,1,1,1,0,1,1],[1,1,1,1,1,0,1,1],[1,0,1,1,1,1,1,1],[0,0,0,1,0,0,0,0],[1,1,1,1,1,1,1,1]], row = 1, col = 3
Output: Robot cleaned all rooms.
Explanation: All grids in the room are marked by either 0 or 1.
0 means the cell is blocked, while 1 means the cell is accessible.
The robot initially starts at the position of row=1, col=3.
From the top left corner, its position is one row below and three columns right.
Example 2:
Input: room = [[1]], row = 0, col = 0
Output: Robot cleaned all rooms.
Constraints:
m == room.length
n == room[i].length
1 <= m <= 100
1 <= n <= 200
room[i][j] is either 0 or 1.
0 <= row < m
0 <= col < n
room[row][col] == 1
All the empty cells can be visited from the starting position.
How will you backtrack robot? (i.e move back afrer exploring each cell)
https://leetcode.com/problems/robot-room-cleaner/description/
# """
# This is the robot's control interface.
# You should not implement it, or speculate about its implementation
# """
#class Robot(object):
# def move(self):
# """
# Returns true if the cell in front is open and robot moves into the cell.
# Returns false if the cell in front is blocked and robot stays in the current cell.
# :rtype bool
# """
#
# def turnLeft(self):
# """
# Robot will stay in the same cell after calling turnLeft/turnRight.
# Each turn will be 90 degrees.
# :rtype void
# """
#
# def turnRight(self):
# """
# Robot will stay in the same cell after calling turnLeft/turnRight.
# Each turn will be 90 degrees.
# :rtype void
# """
#
# def clean(self):
# """
# Clean the current cell.
# :rtype void
# """
from operator import add
class Solution(object):
def cleanRoom(self, robot):
"""
:type robot: Robot
:rtype: None
"""
# Doing it via DFS
def reverse(r):
# face back move and face in same direction as started
r.turnLeft()
r.turnLeft()
r.move()
r.turnRight()
r.turnRight()
direction_movement = {'right': (0, 1), 'left' : (0, -1), 'up': (-1, 0), 'down': (1,0)}
direction_cycle = {'up': 'right', 'right': 'down', 'down': 'left', 'left': 'up'}
visited = set()
next_loc = lambda a,b: tuple(map(add, a, b))
def dfs(r, loc, direction):
# Clean when you reach the new cell
r.clean()
visited.add(loc)
# Continue where facing, if can't turn right till you can find valid movement
# after 4 turns face the same direction as started and then move back to prev position (backtrack)
for _ in range(4):
movement = direction_movement[direction]
new_loc = next_loc(movement, loc)
if new_loc not in visited and r.move():
dfs(r, new_loc, direction)
r.turnRight()
direction = direction_cycle[direction]
# explored all paths move back
reverse(r)
dfs(robot, (0,0), 'up')Example 1:
Input: s = “aab”
Output: 1
Explanation: The palindrome partitioning [“aa”,”b”] could be produced using 1 cut.
Example 2:
Input: s = “a”
Output: 0
Example 3:
Input: s = “ab”
Output: 1
Constraints:
1 <= s.length <= 2000
s consists of lowercase English letters only.
15 minutes
https://leetcode.com/problems/palindrome-partitioning-ii/
from functools import cache
class Solution:
def minCut(self, s: str) -> int:
def isPalin(s):
return s[::-1] == s
@cache
def backtrack(i):
if i == len(s):
return -1
elif isPalin(s[i:]):
return 0
cuts = float('inf')
for idx in range(i+1, len(s)+1):
if isPalin(s[i:idx]):
cuts = min(cuts, 1+backtrack(idx))
return cuts
return backtrack(0)You can use Dynamic programming to check isPalin to make it faster but not needed to pass.
O(n^2), O(n)
Return a list of unique strings that are valid with the minimum number of removals. You may return the answer in any order.
Example 1:
Input: s = “()())()”
Output: [”(())()”,”()()()”]
Example 2:
Input: s = “(a)())()”
Output: [“(a())()”,”(a)()()”]
Example 3:
Input: s = “)(“
Output: [””]
Constraints:
1 <= s.length <= 25
s consists of lowercase English letters and parentheses ‘(‘ and ‘)’.
There will be at most 20 parentheses in s.
https://leetcode.com/problems/remove-invalid-parentheses/
Create Suffix sum and then identify pressing conditions. Presssing conditions are those where you have to take one or the other step. ie. add or skip the parenthesis. Any other condition besides pressing condition, you can be ambivalent. i.e Choose to add or not i.e call recursion twice.
Once you reach the end, just append your solution and pop the previous ones if they are smaller than the one you have.
Worst case for every letter we have two choices.
O(2^n)
Space complexity:
We need to build the prefix array. O(n)
from itertools import accumulate
from collections import defaultdict
from functools import cache
class Solution:
def removeInvalidParentheses(self, s: str) -> List[str]:
mappings = defaultdict(lambda: [0,0])
mappings['('] = [1,0]
mappings[')'] = [0,1]
suffix_counts = list(accumulate(map(lambda c: mappings[c], s[::-1]), lambda a,b: [i+j for i,j in zip(a,b)]))[::-1]
min_removals = 26
output = []
@cache
def dfs(p, p_open, p_close, skipped, idx):
nonlocal min_removals
if idx == len(s):
if p_open == p_close and skipped <= min_removals:
min_removals = skipped
while output and len(output[-1]) < len(p):
output.pop()
output.append(p)
return
future_open, future_close = suffix_counts[idx]
excess_open = (p_open-p_close)
if s[idx] == '(':
if excess_open + future_open <= future_close:
# If excess open and future open are less than close available in future, we have to add this one.
dfs(p+'(', p_open+1, p_close, skipped, idx+1)
elif excess_open >= future_close:
# if excess opens are already in excess of what we can find to balance in future, we need to skip this.
dfs(p, p_open, p_close, skipped+1, idx+1)
else:
# other than the two cases above, we can chose to add or not this as there is no pressing condition.
dfs(p+'(', p_open+1, p_close, skipped, idx+1)
dfs(p, p_open, p_close, skipped+1, idx+1)
elif s[idx] == ')':
# We have to skip this if we have p_close >= p_open or we can skip if there is enough close in future to cover excess open
if p_close >= p_open or excess_open < future_close:
dfs(p, p_open, p_close, skipped+1, idx+1)
if p_close < p_open:
# If more open than closed or future close excess is > p_open we can add it
dfs(p+')', p_open, p_close+1, skipped, idx+1)
else:
dfs(p+s[idx], p_open, p_close, skipped, idx+1)
dfs('', 0,0,0,0)
return outputhttps://leetcode.com/problems/remove-invalid-parentheses/solutions/5523318/using-suffix-sum-cache-insanely-fast-and-easy-to-understand/
You need to check if the equation is solvable under the following rules:
Each character is decoded as one digit (0 - 9).
No two characters can map to the same digit.
Each words[i] and result are decoded as one number without leading zeros.
Sum of numbers on the left side (words) will equal to the number on the right side (result).
Return true if the equation is solvable, otherwise return false.
Example 1:
Input: words = [“SEND”,”MORE”], result = “MONEY”
Output: true
Explanation: Map ‘S’-> 9, ‘E’->5, ‘N’->6, ‘D’->7, ‘M’->1, ‘O’->0, ‘R’->8, ‘Y’->’2’
Such that: “SEND” + “MORE” = “MONEY” , 9567 + 1085 = 10652
Example 2:
Input: words = [“SIX”,”SEVEN”,”SEVEN”], result = “TWENTY”
Output: true
Explanation: Map ‘S’-> 6, ‘I’->5, ‘X’->0, ‘E’->8, ‘V’->7, ‘N’->2, ‘T’->1, ‘W’->’3’, ‘Y’->4
Such that: “SIX” + “SEVEN” + “SEVEN” = “TWENTY” , 650 + 68782 + 68782 = 138214
Example 3:
Input: words = [“LEET”,”CODE”], result = “POINT”
Output: false
Explanation: There is no possible mapping to satisfy the equation, so we return false.
Note that two different characters cannot map to the same digit.
Constraints:
2 <= words.length <= 5
1 <= words[i].length, result.length <= 7
words[i], result contain only uppercase English letters.
The number of different characters used in the expression is at most 10.
Hint: Reverse strings
Just go brute force backtracking
Solution From: https://leetcode.com/problems/verbal-arithmetic-puzzle/solutions/2912760/similar-to-ye15-solution-with-more-comments-for-easier-understanding
Modified to add optimizations (Reverse sort and combining some if else) these improve performance significantly. 450ms -> 320ms (more than 20% improvement)
from functools import reduce
class Solution:
def isSolvable(self, words: List[str], result: str) -> bool:
max_len = reduce(lambda ml, w: max(ml, len(w)), words, 0)
if max_len > len(result):
return False
words = list(map(lambda x: x[::-1], words))
# Since words are reverse sorted based on length, as soon as we hit a word where the column
# is longer than the length, we know no other word downstream will have longer length.
# abcde
# bcde
# cde
# de
# Once we hit c_idx 3, on w_idx 2, we know we should just move to next column from first word..
words = sorted(words, key=lambda x: len(x), reverse=True)
words = [result[::-1]] + words
max_len = max(max_len, len(result))
mapping = {}
state = [False] * 10
def backtracking(w_idx, c_idx, total):
if c_idx == max_len:
# we went through all columns at this point total should be zero
return total == 0
if w_idx == len(words) or c_idx >= len(words[w_idx]):
# Reached end of all words for given c_idx, the total is either 0 or multiple of 10
# which signifies that after subtracting the result's c_idx letter the result was valid.
# eg. 7 + 6 - 3 = 10 which means if we added 7 + 6 and the result was 3 it would be valid as the
# 1 would become carry for next set of integers.
return total % 10 == 0 and backtracking(0, c_idx+1, total // 10)
if words[w_idx][c_idx] in mapping:
digit = mapping[words[w_idx][c_idx]]
# Digit is zero and we are at the first letter, can't be possible unless only one len
if digit == 0 and (c_idx == (len(words[w_idx]) - 1) and len(words[w_idx]) > 1):
return False
if w_idx == 0:
return backtracking(w_idx + 1, c_idx, total - digit)
else:
return backtracking(w_idx + 1, c_idx, total + digit)
else:
# We do reversed here because we start with the first word being the result word, we should assign it
# higher value than the others since its the result of sum of the rest. This improves performance
for digit, used in reversed(list(enumerate(state))):
# digit is unused, if it is eq. to zero it can not be applied as the first
if not used and (digit != 0 or 0 == c_idx or len(words[w_idx]) == 1 or c_idx < (len(words[w_idx]) - 1)):
state[digit] = True
mapping[words[w_idx][c_idx]] = digit
# we are at the last word (i.e the result word, now we subtract it's value)
if w_idx == 0 and backtracking(w_idx + 1, c_idx, total - digit):
return True
elif w_idx > 0 and backtracking(w_idx + 1, c_idx, total + digit):
return True
state[digit] = False
mapping.pop(words[w_idx][c_idx])
return backtracking(0,0,0)