Backtracking Flashcards

1
Q
  1. Generate Parentheses
    Solved
    Medium

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/

A

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 ret
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q
  1. Subsets
    Medium
    Given an integer array nums of unique elements, return all possible
    subsets
    (the power set).

The 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.

A
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 ~~~
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q
  1. Combination Sum
    Medium
    Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.

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/

A
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 output
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q
  1. Permutations
    Medium
    Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.

Example 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/

A

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 output
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q
  1. Permutations II
    Solved
    Medium
    Topics
    Companies
    Given a collection of numbers, nums, that might contain duplicates, return all possible unique permutations in any order.

Example 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/

A

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 ret

With 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 output ~~~
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q
  1. Subsets II
    Solved
    Medium
    Topics
    Companies
    Given an integer array nums that may contain duplicates, return all possible
    subsets
    (the power set).

The 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/

A

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 output

Other 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 output
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q
  1. Word Search
    Solved
    Medium
    Topics
    Companies
    Given an m x n grid of characters board and a string word, return true if word exists in the grid.

The 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/

A
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 True
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q
  1. Palindrome Partitioning

Medium

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/

A
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 ~~~
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q
  1. Letter Combinations of a Phone Number
    Solved
    Medium
    Topics
    Companies

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/

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q
  1. N-Queens
    Solved
    Hard
    Topics
    Companies

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/

A
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 res
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q
  1. Stickers to Spell Word
    Solved
    Hard
    Topics
    Companies
    Hint

We 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/

A
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 -1
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q
  1. Robot Room Cleaner
    Solved
    Hard

Companies: 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/

A
# """
# 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')
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q
  1. Palindrome Partitioning II
    Hard
    Given a string s, partition s such that every substring of the partition is a
    palindrome
    .Return the minimum cuts needed for a palindrome partitioning of s.

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/

A
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)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q
  1. Remove Invalid Parentheses
    Hard
    Companies: Facebook 2024
    Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid.

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/

A

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 output

https://leetcode.com/problems/remove-invalid-parentheses/solutions/5523318/using-suffix-sum-cache-insanely-fast-and-easy-to-understand/

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q
  1. Verbal Arithmetic Puzzle
    Hard
    Companies: Atlassian
    Given an equation, represented by words on the left side and the result on the right side.

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

A

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)
                
How well did you know this?
1
Not at all
2
3
4
5
Perfectly