Recursion
recursion
Foundations
Memory Models
Why does a deeply recursive function crash with a stack overflow while an equivalent loop happily processes a million elements? Why does passing an array into a function sometimes mutate the caller's data and sometimes not? The answers all live one level below your code, in the runtime **memory model** that decides where every variable, object, and function call physically lives. This lesson opens up that model. You will see the difference between the **stack** (small, fast, organized into per-call frames) and the **heap** (larger, manually or garbage-collected, where objects and arrays actually live), watch how every function call pushes a stack frame and every `return` pops one, and trace how recursion stacks frames on top of frames until the base case unwinds them. You will also meet references and pointers conceptually, learn what triggers a stack overflow, and get a working mental model of garbage collection and memory leaks. This lesson builds on **Space Complexity Fundamentals**, where you learned to count auxiliary versus total space and saw why some algorithms claim `O(1)` space while others need `O(n)`. Memory Models gives you the underlying picture: those `O(...)` numbers describe stack frames, heap allocations, and reference graphs that you can now visualize concretely. Next, you will pivot to the mathematical side of the foundations track with **Discrete Mathematics Basics**, picking up the sets, relations, and logic vocabulary that algorithm analysis depends on.
Not Started
0%
Recurrence Relations
When you stare at merge sort or binary search, the running time is not naturally a simple sum like `n^2/2 + n/2`. It is something more interesting: the cost of a problem of size `n` is built out of the cost of smaller problems plus a little glue. That self-referential structure is captured by a **recurrence relation**, an equation of the form `T(n) = ...` written in terms of `T` on smaller inputs. This lesson teaches you to spot, write, and classify recurrences directly from recursive code. You will identify the two ingredients every recurrence needs (the recursive case and the base case), then translate familiar algorithms into their `T(n)` equations: factorial gives `T(n) = T(n-1) + 1`, binary search gives `T(n) = T(n/2) + 1`, merge sort gives `T(n) = 2 T(n/2) + n`, and naive Fibonacci gives `T(n) = T(n-1) + T(n-2) + 1`. You will also group recurrences into three families (linear, divide-and-conquer, and multi-branch) and recognize the Big-O class each typically produces. This lesson builds on **Big-O Notation (Upper Bound)**, which gave you the language for expressing complexity, and on **Mathematical Sequences**, which gave you experience with arithmetic, geometric, and Fibonacci-style sequences, exactly the patterns you will see when a recurrence is expanded. Writing a recurrence is half the battle; the other half is converting it into a closed-form Big-O. That is the subject of the very next lesson, **Solving Recurrence Relations**.
Not Started
0%
Data Structures
Binary Search Tree (BST)
Run an inorder traversal on a **Binary Search Tree (BST)** of any size and the keys come out sorted, in `O(n)` time and `O(h)` space, with no separate sort step. That single property (an ordering invariant baked into the structure itself) is what turns a generic binary tree into a logarithmic-time search container that powers Java's `TreeMap`, C++ `std::map`, and the conceptual model behind every database index. This lesson covers the BST property (left subtree keys less than the node, right subtree keys greater), search and insert that follow the invariant, the three deletion cases (leaf, one child, two children resolved by inorder successor or predecessor), and the validation problem that interviewers love because the naive `node.left.val < node.val` check is wrong. You will also analyze the gap between the balanced `O(log n)` and the degenerate `O(n)` chain that motivates the next lesson. In **Trees: Binary Tree Fundamentals**, you implemented preorder, inorder, postorder, and level-order traversals; the BST property is what makes inorder special, because it now produces sorted output for free. Next, **Balanced BST (AVL / Red-Black)** addresses the elephant in the room: an unlucky insertion order can degrade a BST to a linked list, and self-balancing rotations are the fix.
Not Started
0%
Persistent Data Structures
Cloning a 100k-element segment tree before every update so the previous version stays queryable costs `O(n)` time and space per update, which is unworkable. **Persistent Data Structures** achieve the same 'every past version is still queryable' guarantee in `O(log n)` extra time and space per update by sharing the unchanged subtrees between versions and only allocating new nodes along the path that actually changed. This lesson covers the path-copying technique on a persistent segment tree (each update returns a brand-new root that shares everything except a thin spine of new nodes with the previous version), the `O(log n)` point update and range query on any historical version, persistent arrays built on top of persistent segment trees, and persistent stacks and queues using the cons-list approach from functional programming. You will analyze the total space (`O(n + q log n)` for `q` updates) and apply the structure to k-th smallest in subarrays, time-travel queries, and offline-query reductions. In **Trees: Binary Tree Fundamentals**, a recursive subtree was a self-contained piece of data; structural sharing exploits exactly that property to reuse old subtrees in new versions. **Segment Tree** built the recursive aggregate-over-intervals shape that the persistent variant adapts directly, swapping in-place updates for path copying. Next, **Treap (Randomized BST)** returns to the BST family with a different twist: random priorities for expected balance and `O(log n)` split and merge primitives.
Not Started
0%
Segment Tree
Given an array and a stream of mixed queries (`sum(L, R)`, `min(L, R)`, plus single-index updates), prefix sums answer the queries in `O(1)` but pay `O(n)` for every update, while a raw array does the opposite. Neither extreme is acceptable when both updates and queries are frequent. A **Segment Tree** balances the two: every operation runs in `O(log n)` after an `O(n)` build. This lesson covers the recursive segment-tree shape (a binary tree where each node stores the aggregate over an array interval, and children split the parent's interval in half), the `4n` flat-array layout that maps perfectly onto an iterative or recursive implementation, point update and range query in `O(log n)`, and lazy propagation, where range updates deposit a tag at the highest covering node and push it down only when a later query forces it. You will apply the structure to range sum, range minimum, and range maximum queries, and see why the same template extends to any associative aggregate. In **Arrays & Strings**, a flat array gave you `O(1)` indexed access but no aggregate support; the segment tree layers a hierarchy of precomputed aggregates on top of that array. **Trees: Binary Tree Fundamentals** is where the recursive shape and the `O(h)`-stack traversal pattern came from, and you reuse both directly here. Next, **Fenwick Tree (BIT)** solves a narrower version of the same problem (prefix-only aggregates) with simpler code and half the memory.
Not Started
0%
Algorithms
Backtracking (Intro)
Listing every subset of a 20-element set produces over a million results, and there is no shortcut: you have to enumerate them. The trick is to do it without writing 20 nested loops. Backtracking solves the entire family of "generate all configurations" problems with a single recursive template that walks an implicit decision tree. **Backtracking (Intro)** introduces that template as the "choose, explore, unchoose" pattern. You will see how each recursive frame picks an option, recurses into the resulting state, and then undoes the choice on the way back up so the next branch starts from a clean slate. Classic problems include generating all subsets and permutations, the N-Queens placement puzzle, and a first look at Sudoku. The lesson also introduces pruning, where invalid partial solutions are cut off early so the algorithm explores far less than the full `O(2^n)` or `O(n!)` worst case. In **Recursion Fundamentals**, you learned to define a problem in terms of smaller subproblems. Backtracking adds one more idea: state mutated during recursion has to be restored before returning, so siblings in the call tree see the same starting state. From here, **BFS & DFS (Intro)** generalizes the same tree-of-states view to actual graphs.
Not Started
0%
Divide and Conquer (Advanced)
Multiplying two `n`-bit integers the way you learned in school takes `O(n^2)` digit operations. In 1960, Karatsuba showed that the same product can be computed with three recursive multiplications instead of four, dropping the cost to `O(n^1.585)`. A few years later Strassen pulled the same trick on matrix multiplication, replacing eight recursive multiplications with seven and breaking the `O(n^3)` barrier for the first time. Both algorithms are pure divide-and-conquer with cleverly engineered combine steps, and they remain the gateway to a deeper view of D&C as algebraic restructuring. **Divide and Conquer (Advanced)** walks through that view. You will derive Karatsuba multiplication and apply the Master Theorem to its `T(n) = 3T(n/2) + O(n)` recurrence. You will work through Strassen's seven-multiplication identity for `2 x 2` blocks and discuss when its higher constant factor pays off in practice. You will solve closest-pair-of-points in 2D in `O(n log n)` via the strip optimization in the combine step, and look at convex-hull computation through a divide-and-conquer lens. In **Divide and Conquer (Intro)**, you applied the Master Theorem to merge sort and quick sort. **Recursion Fundamentals** gave you the call-stack model these algorithms still inhabit. This lesson keeps the recurrence framework but engineers cleverer combine steps. Next, **Mathematical Algorithms** turns to number-theoretic computation.
Not Started
0%
Divide and Conquer (Intro)
Merge sort and quick sort both run in `O(n log n)`, and once you write down their recurrences (`T(n) = 2T(n/2) + O(n)` and its expected-case sibling) the same `n log n` falls out of both. That is not a coincidence: divide and conquer is a paradigm with a precise mathematical signature, and the Master Theorem reads it directly off the recurrence. **Divide and Conquer (Intro)** introduces the three-step pattern (divide, conquer, combine) and the recurrence-relation toolkit that comes with it. You will analyze merge sort and quick sort as canonical D&C algorithms, look at binary search through the same lens, walk through the divide-and-conquer maximum subarray algorithm, and meet the closest-pair-of-points problem in 1D. Along the way the lesson formalizes the Master Theorem template `T(n) = aT(n/b) + f(n)` and shows you how to read time complexity off it without solving the recurrence by hand. It also draws the line between D&C (independent subproblems) and DP (overlapping subproblems) so you can spot which paradigm fits. In **Recursion Fundamentals**, you saw recursion as one frame calling another. D&C is the case where a frame makes _multiple_ recursive calls and combines their results. Next, **Matrix Algorithms** turns to two-dimensional arrays.
Not Started
0%
Dynamic Programming (Intro)
Naive recursive Fibonacci computes `fib(40)` in seconds, `fib(50)` in minutes, and gives up on `fib(60)`, all because it recomputes the same subproblems exponentially many times. Cache the result of each `fib(k)` the first time you compute it and the same recursion runs in linear time. That single change, remembering answers, is the entire content of dynamic programming. **Dynamic Programming (Intro)** turns that observation into a complete problem-solving framework. You will identify overlapping subproblems and optimal substructure (the two properties a problem must have for DP to apply), and master both approaches: top-down memoization (recursion plus a cache) and bottom-up tabulation (iteratively filling a table). Classic 1D problems include Fibonacci, climbing stairs, coin change, house robber, and a first look at Kadane's algorithm. The lesson teaches you to define state precisely ("what does `dp[i]` represent?"), write the transition ("how does `dp[i]` follow from earlier states?"), set base cases, and apply rolling-variable space optimization that drops `O(n)` to `O(1)`. In **Recursion Fundamentals**, you treated each recursive call as a stack frame. Memoization just attaches a cache so identical inputs return immediately. Next, **Bit Manipulation (Intro)** turns to a different toolkit, where bitwise operators give elegant `O(1)` solutions.
Not Started
0%
Game Theory
Two players take turns removing stones from piles, the player who takes the last stone wins, and the entire winning strategy boils down to one calculation: XOR all pile sizes together, and you win if and only if the result is non-zero. That is Nim, and the surprise is not that it has a strategy but that the strategy is so compact. The Sprague-Grundy theorem extends the same idea to every impartial combinatorial game. **Game Theory** introduces the algorithmic side of two-player game analysis. You will work through Nim and its XOR strategy, then see how the Sprague-Grundy theorem assigns a Grundy number (nimber) to every position and how XOR-of-nimbers solves any sum of games. The lesson then moves to general game trees: minimax for optimal play, alpha-beta pruning for cutting branches that cannot affect the result, and move ordering for better pruning. You will analyze game states using DP, classifying positions as winning or losing by computing backward from terminal states, and meet retrograde analysis as a complete-game-state technique. In **Dynamic Programming (Intro)**, you learned to recurse on subproblems and cache results. **Recursion Fundamentals** gave you the call-stack model behind minimax and Grundy computation. Game theory recasts those tools as adversarial search. Next, **Randomized Algorithms** turns to a different way of taming worst-case inputs.
Not Started
0%
Recursion Fundamentals
The mathematical definition `factorial(n) = n * factorial(n - 1)` defines a function in terms of itself, with `factorial(0) = 1` as the stopping point. That two-line definition is also a complete program if your language lets a function call itself. Recursion is what turns self-referential definitions into runnable code, and many problems (tree traversal, graph search, divide and conquer, dynamic programming) describe themselves most naturally that way. **Recursion Fundamentals** breaks down how that machinery actually works. You will learn to identify a base case (when the recursion stops) and a recursive case (how the problem reduces to a smaller instance), trace the call stack as frames are pushed and popped, and reason about return values flowing back up the chain. Examples run through factorial, naive Fibonacci, sum of an array, and integer power, plus a first look at recursion-tree intuition, tail recursion, stack overflow, and the `O(depth)` space cost of deep recursion. In **How to Read Code (JS & Python)**, you saw functions call other functions. **Debugging by Tracing** taught you to follow those calls step by step. Recursion is the case where the function on the call stack is the same function, again and again, with smaller inputs each time. Next, **Backtracking (Intro)** extends recursion with an explicit undo step to systematically explore combinatorial search spaces.
Not Started
0%
Tree Algorithms
An invert-binary-tree problem famously got Max Howell rejected from Google in 2015, and the joke landed because the canonical solution is three lines of recursion. Trees show up everywhere in interviews precisely because they reward a particular kind of thinking: the answer for a node is almost always a function of the answers for its left and right subtrees, computed recursively, combined at the parent. **Tree Algorithms** trains that decomposition habit across a wide range of problems. You will implement BST insert, search, delete, validation, and the kth-smallest in-order trick. You will compute lowest common ancestor in both a generic binary tree and a BST. You will measure height, diameter, and width, and walk through path-sum problems, root-to-leaf enumeration, and the deceptively tricky maximum-path-sum-between-any-two-nodes. Structural operations include invert, mirror check, flatten to linked list, and serialize and deserialize. The lesson finishes with reconstructing a tree from inorder plus preorder or inorder plus postorder. In **Trees: Binary Tree Fundamentals**, you learned how nodes, children, and the four traversal orders work. **Recursion Fundamentals** gave you the call-stack mental model that every tree algorithm here relies on; tree recursion is essentially recursion with two recursive calls per frame. Next, **Graph Algorithms (Core)** removes the tree assumption (no cycles, no shared nodes) and tackles the more general traversal problems that result.
Not Started
0%
Practice Problems
Balanced Binary Tree
Given the root of a binary tree, determine if it is height-balanced (the depth of the two subtrees of every node never differs by more than one).
Count Complete Tree Nodes
Given the root of a complete binary tree, return the number of nodes in the tree.
Diameter of Binary Tree
Given the root of a binary tree, return the length of the diameter (the longest path between any two nodes, measured in edges).
Invert Binary Tree
Given the root of a binary tree, invert the tree (mirror it) and return its root.
Lowest Common Ancestor of a BST
Given a binary search tree and two nodes, find their lowest common ancestor by leveraging the BST property.
Maximum Depth of Binary Tree
Given the root of a binary tree, return its maximum depth (the number of nodes along the longest path from root to leaf).
Merge Two Sorted Lists
Merge two sorted linked lists into one sorted linked list by splicing the nodes together.
Path Sum
Given the root of a binary tree and a target sum, determine if the tree has a root-to-leaf path that sums to the target.
Reverse Linked List
Given the head of a singly linked list, reverse the list and return the new head.
Same Tree
Given the roots of two binary trees, determine if they are the same (structurally identical with the same node values).
Convert Sorted Array to Binary Search Tree
Given a sorted array, convert it into a height-balanced binary search tree using divide and conquer.
Subtree of Another Tree
Given the roots of two binary trees, determine if the second tree is a subtree of the first tree.
Symmetric Tree
Given the root of a binary tree, check whether it is a mirror of itself (symmetric around its center).
Basic Calculator
Implement a basic calculator to evaluate a string expression containing '+', '-', parentheses, and non-negative integers.
Binary Tree Maximum Path Sum
Given the root of a binary tree, return the maximum path sum where a path is any sequence of connected nodes (not necessarily passing through the root).
N-Queens II
Given an integer n, return the number of distinct solutions to the n-queens puzzle, where n queens are placed on an n x n chessboard so that no two queens attack each other.
N-Queens
Place n queens on an n x n chessboard so that no two queens attack each other. Return all distinct board configurations as lists of strings, where 'Q' represents a queen and '.' represents an empty space.
Reverse Nodes in k-Group
Given the head of a linked list, reverse the nodes of the list k at a time, and return the modified list. Nodes left over at the end that are fewer than k should remain in their original order.
Combination Sum II
Given a collection of candidate numbers (which may contain duplicates) and a target, find all unique combinations where the chosen numbers sum to the target. Each number may only be used once.
Combination Sum
Given an array of distinct integers and a target, return all unique combinations where the chosen numbers sum to the target. Each number may be used an unlimited number of times.
Combinations
Given two integers n and k, return all possible combinations of k numbers chosen from the range [1, n]. Return the answer in any order.
Construct BT from Inorder and Postorder
Given inorder and postorder traversal arrays, construct the binary tree and return its root.
Construct BT from Preorder and Inorder
Given preorder and inorder traversal arrays, construct the binary tree and return its root.
Construct Quad Tree
Given an n x n binary matrix, construct a Quad-Tree representation by recursively partitioning the grid into four equal quadrants.
Count Good Nodes in Binary Tree
Given a binary tree root, count the number of 'good' nodes where no node on the path from root to that node has a value greater than the node's value.
Flatten Binary Tree to Linked List
Given the root of a binary tree, flatten the tree into a linked list in-place using the right pointers in preorder traversal order.
Generate Parentheses
Given n pairs of parentheses, generate all combinations of well-formed (valid) parentheses.
Letter Combinations of a Phone Number
Given a string containing digits from 2-9, return all possible letter combinations that the number could represent on a phone keypad.
Lowest Common Ancestor of Binary Tree
Given a binary tree and two nodes, find their lowest common ancestor (the deepest node that is an ancestor of both).
Palindrome Partitioning
Given a string, partition it such that every substring of the partition is a palindrome. Return all possible palindrome partitions.
Permutations
Given an array of distinct integers, return all possible permutations in any order.
Sort List
Given the head of a linked list, sort it in ascending order using O(n log n) time and O(1) or O(log n) space.
Subsets II
Given an integer array that may contain duplicates, return all possible subsets (the power set) without duplicate subsets.
Subsets
Given an integer array of unique elements, return all possible subsets (the power set). The solution set must not contain duplicate subsets.
Sum Root to Leaf Numbers
Given a binary tree where each node contains a digit 0-9, find the total sum of all root-to-leaf numbers.
Swap Nodes in Pairs
Given a linked list, swap every two adjacent nodes and return its head. You must solve it without modifying the values in the list's nodes.
Validate Binary Search Tree
Given the root of a binary tree, determine if it is a valid binary search tree using bounds checking or in-order traversal.
Word Search
Given an m x n grid of characters and a string word, determine if the word exists in the grid by following a path of adjacent cells (horizontally or vertically) without reusing any cell.
Code Snippets
Flatten a Nested Array
Flattening a nested array is a one-line job most of the time, but pre-built `Array#flat` is shallow by default and the depth parameter has surprises. This snippet starts with the modern built-in, adds a recursive deep-flatten that works in any environment, and ends with the iterative stack version that avoids stack-overflow on pathological inputs. Three flavours, one mental model.
Deep Merge Two Objects
Layering a defaults object under a user override is a daily need for config files, theme tokens, and React props, but `Object.assign` and spread only do shallow merges. This snippet walks from a recursive deep merge that follows nested plain objects, to an array-strategy variant that lets the caller pick concat vs replace, to a variadic version that folds an arbitrary number of layers from defaults to overrides.
Deep Structural Equality Check
Comparing two values structurally is a deceptively hard problem: `===` only checks reference, `JSON.stringify` reorders keys and drops undefined, and naive recursion stack-overflows on cycles. This snippet builds up the comparator step by step: a baseline that handles primitives and plain objects, a typed-aware version that respects Date / RegExp / Map / Set, and a cycle-safe production-grade implementation. Use the version that matches your data shape.
Quick Sort One-Liner in Python
The functional Quicksort one-liner in Python is a classic teaching artifact: it is short enough to fit on one line and shows comprehensions plus recursion working together. This snippet covers the three-way functional one-liner, an in-place Lomuto-partition variant for performance, and a benchmark contrast against `sorted` to show why the one-liner is for teaching, not production.
Python DFS Templates (Recursive + Iterative)
DFS visits a graph by going as deep as possible before backtracking. Python lets you write it recursively (concise, but bounded by `sys.getrecursionlimit()`) or iteratively with an explicit stack (uglier, but safe for deep graphs). This entry ships both templates and a third variant that detects cycles in a directed graph using gray / black coloring.
Flatten a Nested Data Object
Analytics events, form payloads, and config files often arrive as deeply nested objects, but databases, query strings, and CSV exporters want a flat key-value map. This snippet builds a recursive flattener that produces dot-path keys, extends it to handle arrays with bracket notation, and adds the inverse `unflatten` so the round-trip is lossless. Drop it next to your event tracker or form serializer.
Reversing an Array (Iterative, Recursive, Copy)
Reversing an array is a one-liner if you do not mind mutating the input, and a slightly different one-liner if you do. This snippet covers the built-in `reverse()` mutating call alongside the immutable `toReversed()` and spread alternatives, the two-pointer in-place swap that interviewers ask about, and a recursive form for the recursion lesson. Production code should pick one of the first-accordion forms; the others are for understanding.
Fibonacci: Iterative, Recursive, Memoized, Generator
Fibonacci is the canonical exercise for comparing iteration, recursion, memoization, and lazy evaluation. The iterative for-loop is the production answer, naive recursion is the cautionary tale (exponential cost), memoization fixes the recursion to linear time, and a generator yields an infinite stream you can `take(n)` from. Above index 78, switch to BigInt to avoid floating-point precision loss.
Question Banks
Master Theorem Deep Dive
Five prompts on T(n) = a*T(n/b) + f(n): identifying the three cases, applying them to merge sort and Strassen, and spotting when the theorem does not apply.
Recursion and Backtracking
Four prompts on the recurse-mutate-undo pattern: subsets, permutations, and combinations. Includes one trace and one bug hunt.
Recursion Trace Challenge
Five Python tracing prompts on recursive call counts, return values, stack-frame depth, and a base-case bug hunt.
Tree-from-Flat-Array Challenges
Three drills on shape conversion: building a nested tree from a flat list (numeric ids), building from a nested list with string parents, and assembling a graph adjacency list.
JavaScript Factorial: Three Implementations Quiz
Three seeded ways to compute n! (iterative loop, classic recursion, reduce), plus two companions on memoization and on guarding against negative inputs.
Community
House Robber III
Maximize money robbed from a binary-tree neighborhood where no two directly-connected houses can both be robbed.
Restore IP Addresses
Given a string of digits, return every valid IPv4 address that can be formed by inserting three dots, using bounded-depth backtracking.
Pure vs Tail Recursion and Why JavaScript Doesn't Care
What pure recursion vs tail recursion actually means, why tail-call optimisation matters, why mainstream JS engines refuse to ship it, and the iterative rewrite I default to when stack depth scales with input.
Decode String
Expand a run-length-encoded string with arbitrary nesting using two parallel stacks (counts and string fragments).
Delete Node in a BST
Delete a value from a BST and return any valid resulting BST, handling the three child-count cases cleanly.
Backtracking: The Template That Solves 30 Problems
One choose, explore, unchoose template. Walk it on N-queens, then map the variations: permutations, subsets, word-search, sudoku. The pattern stays put; the predicate moves.
Recover Binary Search Tree
Two BST nodes have been swapped by mistake. Restore the BST in-place by finding and swapping them back.
Combination Sum III
Find all unique k-digit combinations from 1-9 that sum to a target n, using cardinality-bounded backtracking.
Range Sum of BST
Sum every BST value within an inclusive range, pruning subtrees that fall outside.
Recursion Demystified
How to read recursion as a contract instead of a stack trace, the four parts of every recursive function, and where iteration beats it in practice.
Binary Tree Paths
Return every root-to-leaf path in a binary tree as a list of arrow-joined strings.
Insert into a Binary Search Tree
Insert a new value into a BST while preserving the ordering invariant; any valid resulting BST is accepted.
Remove Linked List Elements
Delete every node in a singly-linked list whose value matches a target, with a sentinel/dummy node that makes the head case fall out of the general loop.
Expression Add Operators
Insert +, -, * between digits of a string so the resulting expression equals a target, using backtracking that handles operator precedence in O(1) per step.
Flatten a Multilevel Doubly Linked List
Flatten a doubly-linked list whose nodes may have a child pointer to another doubly-linked list, producing a single-level list via DFS or an explicit stack.
Sudoku Solver
Solve a partially-filled 9x9 Sudoku in place using backtracking with row, column, and 3x3-box constraint sets.
Beautiful Arrangement
Count the permutations of 1..n where every i either divides perm[i] or is divided by perm[i], using backtracking with divisibility pruning.
