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