Brute Force Problems!
Lets look at some examples
What type of problems are we talking about?
Problems where you can imagine the solution space as a tree, here's an example for Tic Tac Toe
What are some other problems like this?
- N Queens
- Sudoku
- Traveling salesman
- Many of our harder competition problems
Backtracking can also be used for other problems in a pinch
- Shortest path
- Dikstra's is far more efficient but a brute force can be quickly implemented
- Knapsack
- Has a dynamic programming solution but can also be solved with backtracking*
Old competition example
Cheap Paint
https://www.hackerrank.com/contests/buacm/challenges/cheap-paint
Title Text
from copy import deepcopy
def solve(prices, used):
if len(prices) == 1:
for i in range(len(prices[0])):
if i not in used:
return prices[0][i]
vals = []
for i in range(len(prices[0])):
if i not in used:
usedcpy = deepcopy(used)
usedcpy.add(i)
vals.append(prices[0][i] + solve(prices[1:], usedcpy))
return min(vals)
num = int(input())
data = []
for i in range(num):
data.append(list(map(lambda x: int(x), input().split())))
print(solve(data, set()))
In 3 1 2 3 3 2 1 3 3 1
Out 4
Problem: Find the minimum sum obtainable by taking 1 value from each row but no two values can be from the same column
TL; DR: knowing how to brute force can often get you pretty far on most problems
Some tricks for implementing solutions
How is the result returned?
- Generally these problems will involve generating a big list (often containing other lists)
- Combining the lists from recursive calls can be annoying and potentially inefficient
- Often easiest to store all results in some global list or in a list that is passed by reference into each call
How are we actually going to generate every state?
for each new possible 'move'
copy current state
apply new 'move'
recursively call solution
for each new possible 'move'
apply new 'move'
recursively call solution
unapply the 'move'
Two options, make a bunch of copies of our state, or use the same state in every recursive call
Note: Copying in C++ vs Python
for each new possible 'move'
copy current state
apply new 'move'
recursively call solution
In this algorithm we copy our entire state, we'll assume state is some sort of list since that is generally the case
void backtrack(vector<int> state){
for(int i = 0; i < state.size(); i++){
vector<int> state_copy = state;
//state gets copied here ^^^^^
modify_state(state_copy[i]);
backtrack(state_copy);
}
}
from copy import deepcopy
def backtrack(state):
for i in range(len(state))
state_copy = deepcopy(state)
#state gets copied here ^^^^^
modify_state(state_copy[i])
backtrack(state_copy)
C++ Example
Python Example
Note: Copying in C++ vs Python
- Assignment/Arg Passing in C++ copies by default
- Assignment/Arg Passing in Python passes a reference meaning modifying the new value modifies the original
- The opposite behavior can be obtained with references (&) in C++ and calling deepcopy in Python
- Java works similarly to Python here
- Primitives are passed by value by default in Python
Problem #1
Beginner
https://leetcode.com/problems/binary-watch/
Advanced
https://leetcode.com/problems/permutations/
class Solution(object):
def permute(self, nums):
results = []
self.helper(nums, [], results)
return results
def helper(self, nums, temp, results):
if len(temp) == len(nums):
results.append(temp[:])
return
for i in range(0, len(nums)):
if nums[i] not in temp:
temp.append(nums[i])
self.helper(nums, temp, results)
temp.pop()
Permutations solution
Problem #2
N Queens
https://leetcode.com/problems/n-queens/
What is N Queens?
- Classic Backtracking problem
- Given a size N of a chessboard, can N queens be placed such that none are attacking each other?
- Some variations, leetcode wants all possible board states in their version
How can we apply backtracking here?
global solutions
solve(board)
if n queens placed
add board to solutions
return
find next square a queen can be placed on
copy board to boardcopy
place queen on boardcopy
solve(boardcopy)
if no valid square found
return
Sounds pretty simple right?
It is but the actual implementation gets messy
Optimizing
Can we make this any faster?
- Will every part of the solution space need to be explored?
- What if we avoid branches of the solution 'tree' that we know wont go anywhere
- What if we use a heuristic to explore the most likely to work options first?
- Branch and Bound
- A specialization of backtracking where we prune the solution tree
- For example, when we return if we encounter a board state where two queens are attacking each other in N Queens
- Best First Search
- Instead of searching solutions in any order, we order them based on which are most likely to succeed
But that's enough confusing algorithms for now, maybe we'll go over these optimizations in more detail another time
Brute Force Problems!
By theasocialmatzah
Brute Force Problems!
- 265