Swift, Recursion and the Recursive Sense

Understanding recursion and recursive thinking in Swift programming

✍️ Note

Some codes are sourced from Loony Corn’s Udemy Course (https://www.udemy.com/user/janani-ravi-2/). This post is for personal notes where I summarize the original contents to grasp the key concepts

Warming up, Recursive sense

Iterative solutions involve loops

Recursive solutions involve function that call themselves

A recursively written function takes in input, splits that input into smaller parts. Then calls itself on the smaller parts and re-combines the outputs

If the inputs to the function are too simple to split further, the recursive function will return a simple output (and not call itself)

Base case of the recursion (It prevents the recursion from going on forever)

This special case for simple inputs is called

https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463332#content

Reverse String

func reverseStringIteratively(input: String) -> String {
    if input.count == 1 || input.isEmpty {
        return input
    }
    var inputArray = Array(input)
    for i in 0..<input.count {
        let firstIndex = i
        let lastIndex = (input.count - 1) - i
        
        if firstIndex == lastIndex {
            break
        }
        inputArray.swapAt(firstIndex, lastIndex)
    }
    return String(inputArray)
}


reverseStringIteratively(input: "2AB") //BA2

Recursion approach

func reverseStringRecursively(input: String) -> String {
    //Base case
    if input.count == 1 || input.isEmpty {
        return input
    }
    var inputArray = Array(input)
    let first = inputArray.removeFirst()
    let reversedString = reverseStringRecursively(input: String(inputArray))
    return reversedString + String(first)
}

reverseStringRecursively(input: "2AB") //BA2

Exercise 1. Binary Search

Choose an element in at the mid point of a sorted list

Check whether it’s smaller than or greater than the element you are looking for

If the element at the mid point is larger than the element you are searching for

Reduce your search area to the portion where the element might be present

Base case

  • There is no part of the list to search and the element has not been found
  • The search element has been found at the mid point of the portion we’re searching

Recursive case

  • Binary search smaller portions of the list where the element might be found

Summary

  • By halving the search area at every step, binary search works much fatser than linear search
  • The complexity of binary search is O(logN)
  • The Iterative approach to binary search might perform better than the recursive approach in terms of space complexity. There is not recursive stack in the iterative approach, which means we save on some space.
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463334#content

Example 1. No boundary, Assumed that You will search entire input ranges

let input = Array(0...10000)

/// Return : Index
func binarySearch(input: [Int], search: Int) -> Int {
    if input.isEmpty {
        return -1
    }
    let midPoint = (input.count - 1) / 2
    if input[midPoint] == search {
        return input[midPoint]
    }
    if search > input[midPoint] {
        return binarySearch(input: Array(input[midPoint..<input.count]), search: search)
    }
    else {
        return binarySearch(input: Array(input[0..<midPoint]), search: search)
    }
}

let searchedItem = binarySearch(input: input, search: 2859)

Example 2. Boundary search

let input = Array(0...10000)

/// Return : Index
/// min, max: Search Area in Input
func binarySearch(input: [Int], search: Int, min: Int, max: Int) -> Int {
    //If the indices have crossed one another at the center, the term is not present in the array, return -1, base case of the recursion
    if min > max {
        return -1
    }
    let midPoint = min + (max - min) / 2
    if input[midPoint] == search {
        return input[midPoint]
    }
    //Call binary search on a smaller portion of the array depending on whether the element might be found in the first or second half of the list
    if search > input[midPoint] {
        return binarySearch(input: input, search: search, min: midPoint + 1, max: max)
    }
    else {
        return binarySearch(input: input, search: search, min: min, max: midPoint - 1)
    }
}

let searchedItem = binarySearch(input: input, search: 2859, min: 2800, max: 2900)
print(searchedItem)

Exercise 2. Find all subsets of a given set

Example, {1, 2, 3, 4}

  • There are 2^4 subsets of this set, If we have a set of N elements the number of subsets will be 2^N
  • At subsets, order is not important!

Recursive mind (Find a pattern!)

  • Break this down, 1 element {1} -> subsets are {}, {1}
  • 2 elements {1, 2} -> subsets are {}, {1}, {2}, {1, 2}
  • 3 elements {1, 2, 3} -> subsets are {}, {1}, {2}, {1, 2}, {3}, {1, 3}, {2, 3}, {1, 2, 3}
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463346#content
func findAllSubSets(input: [Int]) -> [[Int]] {
    //Base case, Break the Recursive
    if input.isEmpty {
        return [[]]
    }
    if input.count == 1 {
        return [[], [input.first!]]
    }
    
    var inputArray = input
    
    //Recursive case
    let currentItem = inputArray.removeFirst()
    //Reduce the inputArray at every step by removeFirst
    var subsets = findAllSubSets(input: inputArray)
    
    var currentSubSets = [[Int]]()
    
    for subset in subsets {
        var temp = subset
        //Subset's items order can be ignored
        temp.insert(currentItem, at: 0)
        currentSubSets.append(temp)
    }
    for currentSubSet in currentSubSets {
        subsets.append(currentSubSet)
    }
    return subsets
}

let subsets = findAllSubSets(input: [1, 2, 3, 4])
print(subsets)

//Prints
[[], [4], [3], [3, 4], [2], [2, 4], [2, 3], [2, 3, 4], [1], [1, 4], [1, 3], [1, 3, 4], [1, 2], [1, 2, 4], [1, 2, 3], [1, 2, 3, 4]]
Program ended with exit code: 0

Let’s take a look How to make subsets

Create the subsets of very small sets first

The for every subset, add a new element in to create newer subsets

Repeat till all elements from the original set have been used

Base Case

  • When the original set is empty, only the empty set is it’s subset

Recursive Case

  • Add the elements to the existing subsets to create new subsets

Since there are 2^N Subsets the complexity of this Code is O(2^N) -> This cannot be reduced further

https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463346#content

Exercise 3. Find whether 2 binary trees are exactly the same

A binary tree is one where every node can have a maximum of two children – A left child and right child

Two binary trees are the same if

  • Every corresponding node has the same value
  • The structure of the tree at every corresponding node is the same

Base case

  • A null current node on one tree should correspond to a null in the other tree
  • Node values should be the same at every node

Recursive case

  • Check that the sub-tree rooted at every node is the same

Time Complexity

  • We have to visit every node in both trees so 2N nodes, the complexity is O(N)
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463352#content
class Tree {
    let value: String
    var left: Tree?
    var right: Tree?
    
    init(value: String, left: Tree? = nil, right: Tree? = nil) {
        self.value = value
        self.left = left
        self.right = right
    }
}

let treeA = Tree(value: "a")
treeA.left = Tree(value: "b")
treeA.right = Tree(value: "c")
treeA.left?.left = Tree(value: "d")
treeA.left?.right = Tree(value: "e")
treeA.right?.right = Tree(value: "f")
treeA.right?.right?.right = Tree(value: "g")

let treeB = Tree(value: "a")
treeB.left = Tree(value: "b")
treeB.right = Tree(value: "c")
treeB.left?.left = Tree(value: "d")
treeB.left?.right = Tree(value: "e")
treeB.right?.right = Tree(value: "f")
treeB.right?.right?.right = Tree(value: "g")


func isSameTree(_ a: Tree?, _ b: Tree?) -> Bool {
    //Base case
    if a?.left == nil, a?.right == nil, b?.left == nil, b?.right == nil, a?.value == b?.value {
        return true
    }
    if a?.value == b?.value {
        return isSameTree(a?.left, b?.left) && isSameTree(a?.right, b?.right)
    }
    return false
}

let isSame = isSameTree(treeA, treeB)

print(isSame) //True

Exercise 4. Build a car given all tasks and each task’s dependencies

Tip (It’s the same, How OperationQueue works, see my post)

  • Set up the data structure that you plan to use to store a tasks and it’s dependencies
  • Dependencies of Task “A” are all tasks which should be complete before “A” can execute.
    • Paint a car, depends on build chassis of the car, The chassis needs to be ready before it can be painted
  • The method should take in a list of all tasks which are needed to build a car along with their dependencies and execute every task in order to build the car
    • The tasks are in any order, It’s up to you to determine some order in which it can be executed.

Tasks and dependencies

  • Tasks are A, B, C, D, E, F, G, H
    • B depends on A
    • D depends on E
    • C depends on A, B, D
    • F depends on C
  • An acceptable order of performing the tasks are
    • E-> D -> A -> B -> C -> F
    • or A -> E -> B -> D -> C -> F
    • and more

Remember

  • Remember that you can’t execute a task unless it’s dependencies have completed
  • Store the dependencies such that they are easily accessible from a task
  • Recursively execute the dependencies till you get to the task

Base case

  • The current task has been executed, it’s marked done

Recursive case

  • Execute dependencies before coming to the current task

Notes

  • The tasks and it’s dependencies form a directed acyclic graph
  • The graph may not be fully connected, I.E. certain tasks stand completely alone.
  • It’s a topological sort.
  • We have to visit every task to execute it, the complexity is O(N), where N is the number of tasks.
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463360#content

Example 1. Recursively run a tasks

class TaskQueue {
    let value: String
    var dependsOn = [TaskQueue]()
    var isFinished = false
    
    init(value: String) {
        self.value = value
    }
}

let taskA = TaskQueue(value: "A")
let taskB = TaskQueue(value: "B")
let taskC = TaskQueue(value: "C")
let taskD = TaskQueue(value: "D")
let taskE = TaskQueue(value: "E")
let taskF = TaskQueue(value: "F")
let taskG = TaskQueue(value: "G")
let taskH = TaskQueue(value: "H")

taskB.dependsOn = [taskA]
taskD.dependsOn = [taskE]
taskC.dependsOn = [taskA, taskB, taskD]
taskF.dependsOn = [taskC]

func buildCar(_ tasks: [TaskQueue]) {
    //Base case
    if tasks.isEmpty {
        return
    }
    var remainingTasks = tasks
    let dequeue = remainingTasks.removeFirst()
    if dequeue.isFinished {
        return buildCar(remainingTasks)
    }
    
    //[a, b, c]
    // a: finish return
    
    //Recursive case
    let dependsOnUnfinishedTasks = dequeue.dependsOn.filter { $0.isFinished == false }
    
    if !dependsOnUnfinishedTasks.isEmpty {
        buildCar(dependsOnUnfinishedTasks)
    }
    dequeue.isFinished = true
    print("\(dequeue.value)", terminator: "->")
    return buildCar(remainingTasks)
}

buildCar([taskA, taskB, taskC, taskD, taskE, taskF, taskG, taskH])

//Prints
A->B->E->D->C->F->G->H

Example 2. Simplify version

class TaskQueue {
    let value: String
    var dependsOn = [TaskQueue]()
    var isFinished = false
    
    init(value: String) {
        self.value = value
    }
    
    func run() {
        if isFinished {
            return
        }
        //Before run, recursively call run on all the dependencies of this task
        for task in dependsOn {
            task.run()
        }
        isFinished = true
        print("Completed task: \(value)")
    }
}

let taskA = TaskQueue(value: "A")
let taskB = TaskQueue(value: "B")
let taskC = TaskQueue(value: "C")
let taskD = TaskQueue(value: "D")
let taskE = TaskQueue(value: "E")
let taskF = TaskQueue(value: "F")
let taskG = TaskQueue(value: "G")
let taskH = TaskQueue(value: "H")

taskB.dependsOn = [taskA]
taskD.dependsOn = [taskE]
taskC.dependsOn = [taskA, taskB, taskD]
taskF.dependsOn = [taskC]

func runTask(_ tasks: [TaskQueue]) {
    for task in tasks {
        task.run()
    }
}

runTask([taskA, taskB, taskC, taskD, taskE, taskF, taskG, taskH])

//Prints
Completed task: A
Completed task: B
Completed task: E
Completed task: D
Completed task: C
Completed task: F
Completed task: G
Completed task: H

Exercise 5. Generate anagrams of a Word

Anagrams

  • A word have all the same letters of the word itself in a different order
  • Consider that the letters in the word passed in are unique and that there are no spaces. I.E. It’s a word not a sentence
  • The anagram need not be a valid word, we just want all the permutations of the letters.
  • If all letters are unique then there are N! anagrams.

Example

  • “ROAD” -> 4! Anagrams
    • “ROAD”, “ORAD”, “OARD”, “RDOA”, “RAOD”, …more!

Hint

  • Basic idea is the same as finding a all subsets from given a string

Break this down! (Finding a patterns!)

  • Consider a word of 1 letter “A” -> Anagrams are “A”
  • Consider a word of 2 letters “AB” -> Anagrams are “AB“, “BA”
  • Consider a word of 3 letters “ABC” -> Anagrams are “CAB”, “ACB”, “ABC“, “CBA”, “BCA”, “BAC

Break the word into smaller and smaller words, till we find the anagram of the smallest word.

To the smaller anagrams add in letter by letter at every possible positions

Recursively do this till we get all the letters in at every position

Base case

  • The anagram of an empty string is an empty string
  • The smallest word of one letter is the anagram itself

Recursive case

  • Find anagrams of smaller words by removing letters from the original word
  • Add the letters back one by one to every position

Complexity

  • The number of anagrams for a word with unique letter is N! which means the complexity of this algorithm is also O(N!)
  • This cannot be further optimized
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463362#content
func generateAnagrams(input: String) -> [String] {
    //Base case
    if input.count <= 1 {
        return [input]
    }
    var inputString = input
    let currentLetter = inputString.removeFirst()
    var anagram = generateAnagrams(input: inputString)
    
    var newAnagrams = [String]()
    for (index, word) in anagram.enumerated() {
        let wordCount = word.count
        for i in 0...wordCount {
            var tempWord = anagram[index]
            tempWord.insert(currentLetter, at: tempWord.index(tempWord.startIndex, offsetBy: i))
            newAnagrams.append(tempWord)
        }
    }
    return newAnagrams
}

let anagrams = generateAnagrams(input: "ABC")
print(anagrams)

//Prints
["ABC", "BAC", "BCA", "ACB", "CAB", "CBA"]

Exercise 6. Implement the paint fill feature from MS Paint

Paint starts from one pixel where you click and moves outwards

All adjoining pixels with the same original color get the color fill with the new color

The color fill moves outwards till it reaches a boundary which has a different color

https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463358#content

Start with one pixel which has some original color

Move outwards, If the neighboring pixels have the same original color, color them as well

Repeat till the borders are reached

What is the base case?

  • The current pixel does not have the same original color, so represents a border
  • The current pixel is beyond the screen boundaries

What is the recursive case?

  • Move outward from the start pixel coloring individual pixels
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463358#content
var screen: [[Int]] = [
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 2, 0, 0, 0, 0, 0],
[0, 0, 1, 2, 2, 2, 0, 0, 0, 0],
[0, 0, 1, 2, 2, 2, 2, 0, 3, 3],
[0, 0, 1, 1, 1, 0, 0, 0, 3, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 3, 0],
[0, 0, 0, 5, 0, 0, 0, 0, 3, 0],
[0, 0, 5, 5, 0, 0, 0, 0, 3, 0],
[0, 5, 5, 5, 0, 0, 0, 0, 3, 0]
]

func paintFill(screen: inout [[Int]], x: Int, y: Int, originalColor: Int, newColor: Int) {
    //Base Case
    let width = screen.first?.count ?? 0
    let height = screen.count
    //Boundary check
    if x < 0 || x > (width - 1) {
        return
    }
    if y < 0 || y > (height - 1) {
        return
    }
    //Important! y is row, x is column
    let currentColor = screen[y][x]
    if currentColor != originalColor {
        return
    }
    
    //Paint it
    screen[y][x] = newColor
    
    //Fill neighbors pixels too
    //Up, Y is Row
    paintFill(screen: &screen, x: x, y: y-1, originalColor: originalColor, newColor: newColor)
    //Down, Y is Row
    paintFill(screen: &screen, x: x, y: y+1, originalColor: originalColor, newColor: newColor)
    //Left, X is Column
    paintFill(screen: &screen, x: x - 1, y: y, originalColor: originalColor, newColor: newColor)
    //Right, X is Column
    paintFill(screen: &screen, x: x + 1, y: y, originalColor: originalColor, newColor: newColor)
}

paintFill(screen: &screen, x: 4, y: 3, originalColor: 2, newColor: 9)

print("2 -> 9\n")
printBoard(screen)

//Results
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 9, 0, 0, 0, 0, 0],
[0, 0, 1, 9, 9, 9, 0, 0, 0, 0],
[0, 0, 1, 9, 9, 9, 9, 0, 3, 3],
[0, 0, 1, 1, 1, 0, 0, 0, 3, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 3, 0],
[0, 0, 0, 5, 0, 0, 0, 0, 3, 0],
[0, 0, 5, 5, 0, 0, 0, 0, 3, 0],
[0, 5, 5, 5, 0, 0, 0, 0, 3, 0]

Exercise 7. Find a path a rat can travel through a maze

💡 Hint! Back Tracking!

Game Rule

  • Assume the maze is made up of cells with 4 walls. Each wall can have a door
  • All walls do not have a door, only some do
  • The rat has been placed in one of the cells, It has to make It’s way to a cell which is the “End” cell
  • The rat should not go through the same cell twice while finding a path

Base case

  • The rat has reached the end cell

Recursive case

  • Keep visiting cells by passing through doors when they exist
  • Do not visit cells if they are present in the current path, we don’t want the rat to move in circles
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463368#content

Solution 1. Using 2D Array

var maze: [[String]] = [
[" ", "G", "H", "I"],
["D", "E", "F", " "],
["A", "B", "C", " "]
]

//l: left, u: up, r: right, d: down
let door: [String: [String]] = [
    "D":["l", "u", "r"],
    "A": ["l", "d"],
    "B": ["d"],
    "C": ["d", "l", "u"],
    "E": ["l"],
    "F": ["d", "r"],
    "G": ["l", "u"],
    "H": ["u"],
    "I": ["u", "r", "d"]
]

//Path: Keep track of the current path, The rat has traversed
func findPath(maze: [[String]], currentX: Int, currentY: Int, endX: Int, endY: Int, path: inout [String]) -> Bool {
    //Base case
    if maze.isEmpty {
        return false
    }
    let width = maze.first?.count ?? 0
    let height = maze.count
    
    if currentX < 0, currentX >= width {
        return false
    }
    if currentY < 0, currentY >= height {
        return false
    }
    if endX < 0, endX >= width {
        return false
    }
    if endY < 0, endY >= height {
        return false
    }
    //X is column, Y is row, start X, Y means rabit's current position
    let cellValue = maze[currentY][currentX]
    if cellValue == " " {
        return false
    }
    //Set visited
    path.append(cellValue)
    
    //Reached the destination
    if currentX == endX, currentY == endY {
        return true
    }
    
    let doors = door[cellValue]!
    let allDoors = Set(["l", "r", "u", "d"])
    let availableOpenedDoors = allDoors.subtracting(Set(doors))
    
//Check All available neighbors
    for direction in availableOpenedDoors {
        switch direction {
        case "l":
            let leftCellValue = maze[currentY][currentX - 1]
//Check If already visited or not
            if path.contains(leftCellValue) == false {
//Store current path
                var newPath = path
//Recursive call!
                if findPath(maze: maze, currentX: currentX - 1, currentY: currentY, endX: endX, endY: endY, path: &newPath) {
//It is very important
                    path.removeAll()
                    path.append(contentsOf: newPath)
                    return true
                }
            }
        case "r":
            let rightCellValue = maze[currentY][currentX + 1]
            if path.contains(rightCellValue) == false {
                var newPath = path
                if findPath(maze: maze, currentX: currentX + 1, currentY: currentY, endX: endX, endY: endY, path: &newPath) {
                    path.removeAll()
                    path.append(contentsOf: newPath)
                    return true
                }
            }
        case "u":
            let upCellValue = maze[currentY - 1][currentX]
            if path.contains(upCellValue) == false {
                var newPath = path
                if findPath(maze: maze, currentX: currentX, currentY: currentY - 1, endX: endX, endY: endY, path: &newPath) {
                    path.removeAll()
                    path.append(contentsOf: newPath)
                    return true
                }
            }
        case "d":
            let downCellValue = maze[currentY + 1][currentX]
            if path.contains(downCellValue) == false {
                var newPath = path
                if findPath(maze: maze, currentX: currentX, currentY: currentY + 1, endX: endX, endY: endY, path: &newPath) {
                    path.removeAll()
                    path.append(contentsOf: newPath)
                    return true
                }
            }
        default:
            break
        }
    }
    print("This cell is false: \(maze[currentY][currentX])")
    return false
}

var path = [String]()
let isFoundPath = findPath(maze: maze, currentX: 0, currentY: 1, endX: 3, endY: 0, path: &path)

print("\(isFoundPath) -> \(path)")

print("Finish")

//Prints Case 1
This is false: C
This is false: F
true -> ["D", "A", "B", "E", "G", "H", "I"]
Finish

//Prints Case 2
This cell is false: G
true -> ["D", "A", "B", "E", "F", "H", "I"]
Finish
Program ended with exit code: 0

Solution 2. Using Cell class

class Cell {
    let id: String
    let isEnd: Bool
    var neighbors = [Cell]()
    
    init(id: String, isEnd: Bool) {
        self.id = id
        self.isEnd = isEnd
    }
}

let cellD = Cell(id: "D", isEnd: false)
let cellA = Cell(id: "A", isEnd: false)
let cellB = Cell(id: "B", isEnd: false)
let cellC = Cell(id: "C", isEnd: false)
let cellE = Cell(id: "E", isEnd: false)
let cellF = Cell(id: "F", isEnd: false)
let cellG = Cell(id: "G", isEnd: false)
let cellH = Cell(id: "H", isEnd: false)
let cellI = Cell(id: "I", isEnd: true)

cellD.neighbors = [cellA]
cellA.neighbors = [cellD, cellB]
cellB.neighbors = [cellA, cellC, cellE]
cellC.neighbors = [cellB]
cellE.neighbors = [cellG, cellF, cellB]
cellF.neighbors = [cellE, cellH]
cellH.neighbors = [cellG, cellF, cellI]
cellI.neighbors = [cellH]

func findPath(currentCell: Cell, path: inout [Cell]) -> Bool {
    path.append(currentCell)
    
    //Base case
    if currentCell.isEnd {
        return true
    }
    for neighborCell in currentCell.neighbors {
        if path.contains(where: { $0.id == neighborCell.id }) == false {
            var currentPath = path
            if findPath(currentCell: neighborCell, path: &currentPath) {
                path.removeAll()
                path.append(contentsOf: currentPath)
                return true
            }
        }
    }
    return false
}

var path = [Cell]()
let isFoundPath = findPath(currentCell: cellD, path: &path)

print("\(isFoundPath) -> \(path.compactMap { $0.id })")

Exercise 8. Place 8 Queens on a Chessboard so they don’t kill each other

Rules

  • Queens can move along their rows, columns and along both diagonals
  • Place one queen at a time, one on each row or one on each column
  • Remember to check at every step to see if the queen placed is safe, and if the remaining queens can be placed with that configuration

Solution

  • Place a queen in each row or column in a safe position
  • See if the remaining queens can be placed safely with the current position of the queen
  • Recursively do this till the right positions for all queens have been found
  • Place all 8 queens safety

Base case

  • The queens have been all placed and we’re beyond the boundary of the chessboard

Recursive case

  • Keep placing queens in different positions of the same row or column
  • Check whether the remaining queens can be successfully placed
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8463370#content
import Foundation
//https://github.com/ShawnBaek/Table
import Table

//0: Empty, 1: Queen
var chessBoard = Array(
    repeating: Array(repeating: 0, count: 8),
    count: 8
)

func isSafeOnLeftDiagonal(chess: [[Int]], row: Int, col: Int) -> Bool {
    var r = 0
    var c = 0
    
    //Find the initial position tracing the left diagonal from the first cell in the line of the placed queen
    if row > col {
        r = row - col
    }
    else {
        c = col - row
    }
    let columns = chess.first?.count ?? 0
    let rows = chess.count
    var numberOfQueen = 0
    while r < rows && c < columns {
        if chess[r][c] == 1 {
            numberOfQueen += 1
        }
        r += 1
        c += 1
    }
    return numberOfQueen <= 1
}

func isSafeOnRightDiagonal(chess: [[Int]], row: Int, col: Int) -> Bool {
    let columns = chess.first?.count ?? 0
    let rows = chess.count
    
    //Start position will be top right based on current position
    var r = 0
    var c = columns - 1
    var numberOfQueens = 0
    //Option 1. I suggest calculating it and checking the start position
    if (row + col < columns) {
        c = min(row + col, columns - 1)
    }
    else {
        r = (row + col) % (columns - 1)
    }
    
    while r < rows && c > 0 {
        if chess[r][c] == 1 {
            numberOfQueens += 1
        }
        r += 1
        c -= 1
    }
    return numberOfQueens <= 1
}

func isSafePosition(chess: [[Int]], row: Int, col: Int) -> Bool {
    let columns = chess.first?.count ?? 0
    let rows = chess.count
    //Edge case
    if col < 0 || col >= columns {
        return false
    }
    if row < 0 || row >= rows {
        return false
    }
    //Check horizontal
    for c in 0..<columns {
        //Found a Queen and It's not a current position of Queen
        if chess[row][c] == 1, c != col {
            return false
        }
    }
    //Check vertical
    for r in 0..<rows {
        if chess[r][col] == 1, r != row {
            return false
        }
    }
    
    //Check TopLeft -> DownRight Diagonal
    if !isSafeOnLeftDiagonal(chess: chess, row: row, col: col) {
        return false
    }
    
    if !isSafeOnRightDiagonal(chess: chess, row: row, col: col) {
        return false
    }
    return true
}

func placeQueen(
    chess: inout [[Int]],
    col: Int
) -> Bool {
    //Base case, Placed all Queens
    if col >= 8 {
        return true
    }
    for row in 0..<8 {
        //Place a Queen
        chess[row][col] = 1
        if isSafePosition(chess: chess, row: row, col: col) {
            if placeQueen(chess: &chess, col: col + 1) {
                return true
            }
        }
        //Remove Queen at this position
        chess[row][col] = 0
    }
    return false
}

let isPlacedAllQueens = placeQueen(chess: &chessBoard, col: 0)
print(table: chessBoard)

Results

Time Complexity is O(N!), We’re trying every possible position for the queens

Comments

Leave a Reply

Discover more from Shawn

Subscribe now to keep reading and get access to the full archive.

Continue reading