Array reversal 2026-10-31
Reverse the order of entries in an array segment by exchanging its outermost unprocessed pair and moving inward. This takes linear time and constant auxiliary space. Reversing two adjacent blocks separately and then reversing their union rotates the blocks while restoring each block's internal order.
Brent cycle-finding algorithm Created 2026-10-20 Updated 2026-11-05
For a deterministic iteration with eventual cycle length and preperiod , the Brent cycle-finding algorithm detects repetition using constant auxiliary space.
Hold a checkpoint fixed while another pointer advances through blocks of lengths . At a block boundary, move the checkpoint to the advancing pointer and double the next block length. Compare each new state to the checkpoint. Once the checkpoint lies on the cycle and its block is long enough, the first equality occurs exactly steps after the checkpoint.
To locate the entrance, restart two pointers at the initial state, advance one by steps, then move both one step at a time until they agree. Before index , the first pointer is outside the cycle; at both reach the same cyclic position. The procedure uses evaluations of and constant auxiliary space, assuming constant-cost state equality and copying. An infinite nonperiodic orbit has no finite detection guarantee.[ref]
Turn a binary search tree into a sorted doubly linked list by reusing each linked list node's left and right child fields as predecessor and successor fields. An inorder traversal determines the required order. A recursive implementation first saves both child references, flattens the disjoint child subtrees, and splices their endpoints around the root. Structural induction proves that it preserves every original entry exactly once and produces reciprocal links with null outside endpoints. An explicit stack implements the same traversal without recursive calls; each original right child must be saved before its field is overwritten. Both versions take linear time complexity and auxiliary space for original height . The old binary search tree representation is consumed, so outside aliases must not continue to interpret the changed links as child pointers.
For each candidate first index in a sorted array, use lower-bound search for only in the suffix starting at . A matching result is automatically at a different position. The outer loop invariant excludes all solutions whose smaller index precedes ; an unsuccessful suffix search extends this exclusion by one. Exhaustion proves absence of any pair. Searching all suffixes gives running time and constant auxiliary space. Compute the complement in a sufficiently wide type to avoid integer overflow before comparison.
Reverse a finite acyclic linked list using a reversed-prefix head and an untouched-suffix head. Save the suffix head's old successor, redirect its successor to the prefix, then advance both heads. If and are the current sequences of node identities and the original sequence, the loop invariant is , with the two reachable node sets disjoint and all payload fields unchanged. Moving from to preserves this equation. The suffix length decreases, and at exit the prefix is exactly the reversed original list. The method takes linear running time and constant auxiliary space, reusing the original nodes.
Evaluate the mixed radix representation from its most significant end, using Horner's method. There is no need to compute the positional weights separately.
def digsToInt(xs: Array[Int], bs: Array[Int]): Int = {
  require(xs.length == bs.length)
  val N = xs.length
  var i = N
  var acc = 0
  while (i > 0) {
    i -= 1
    acc = xs(i) + bs(i) * acc
  }
  acc
}
The digit conditions are additional preconditions. The loop invariant at the loop test is
An empty sum is zero and an empty product is one, so initialization at is correct. For the next digit, writing gives
which preserves the loop invariant. The loop variant is . At termination , giving
The code executes exactly multiplications, meeting the bound, and uses auxiliary space. When it returns zero. For the time representation in the source, it computes .
A suitable precondition, including the empty array case, is
A base of one is allowed: its only valid digit is zero. The empty product is one, so permits exactly . Each Euclidean division extracts the next least significant digit.
def intToDigs(n: Int, bs: Array[Int]): Array[Int] = {
  // Pre: every base is positive, and 0 <= n < product(bs).
  val N = bs.length
  val xs = new Array[Int](N)
  var i = 0
  var q = n
  while (i < N) {
    xs(i) = q % bs(i)
    q = q / bs(i)
    i += 1
  }
  xs
}
The expensive precondition is a caller obligation, rather than a separately computed product inside the conversion. Define . The loop invariant is
together with for each processed digit. The Euclidean division identity proves preservation of the reconstruction equality. Positivity of the bases gives the digit bounds and the next bound on the quotient. The loop variant is . At exit, forces , hence
There are exactly remainder operations and divisions, hence counted operations. The returned array occupies space and the remaining auxiliary space is constant. A represented value can never exceed , as follows either by the same nested expansion or by telescoping ; thus the stated range is also necessary for positive bases.
Place these methods inside the LLBuffer class. The two private insertion methods operate on physical endpoints; the public methods interpret logical endpoints using the reversal flag.
private def addFirst(x: Int): Unit = {
  val q = new Node(x)
  q.next = first
  if (first == null) last = q else first.prev = q
  first = q
  size += 1
}

private def addLast(x: Int): Unit = {
  val q = new Node(x)
  q.prev = last
  if (last == null) first = q else last.next = q
  last = q
  size += 1
}

def append(x: Int): Unit = {
  if (backwards) addFirst(x) else addLast(x)
}

def prepend(x: Int): Unit = {
  if (backwards) addLast(x) else addFirst(x)
}

def get(i: Int): Int = {
  require(0 <= i && i < size)
  val j = if (backwards) size - 1 - i else i
  var q: Node = null
  if (j <= size - 1 - j) {
    q = first
    var k = 0
    while (k < j) { q = q.next; k += 1 }
  } else {
    q = last
    var k = size - 1
    while (k > j) { q = q.prev; k -= 1 }
  }
  val x = q.value
  if (q.prev == null) first = q.next else q.prev.next = q.next
  if (q.next == null) last = q.prev else q.next.prev = q.prev
  q.prev = null
  q.next = null
  size -= 1
  x
}

def rev: Unit = { backwards = !backwards }
In the forward traversal, the loop invariant is that q is physical node , with ; its loop variant is . In the backward traversal, q is physical node , with ; its loop variant is . Thus both locate physical index , which is exactly logical index under the abstraction function.
Deletion bypasses the chosen linked list node in both directions. Deleting the first or last linked list node updates the corresponding endpoint; deleting the only linked list node sets both endpoints to null. The surviving values keep their physical relative order, so the abstraction function gives precisely the required logical deletion. Each insertion preserves reciprocal links and adds its value at the physical end corresponding to the required logical end. Toggling the flag changes to its list reversal without moving any linked list nodes. Consequently
These are worst-case time complexity bounds in the constant-cost pointer model; total storage is and each operation uses constant auxiliary space.
Use three-way quicksort. Fix the pivot value before moving any entries, and group equal entries together so that none needs a recursive call. These helpers and the sorter belong inside an enclosing scope containing a.
def swap(i: Int, j: Int): Unit = {
  val t = a(i); a(i) = a(j); a(j) = t
}

def split(l: Int, r: Int): (Int, Int) = {
  // Pre: 0 <= l < r <= a.length.
  val p = a(l)
  var lt = l
  var i = l
  var gt = r
  while (i < gt) {
    if (a(i) < p) {
      swap(lt, i)
      lt += 1
      i += 1
    } else if (a(i) > p) {
      gt -= 1
      swap(i, gt)
    } else {
      i += 1
    }
  }
  (lt, gt)
}

def quicksort(l: Int, r: Int): Unit = {
  require(0 <= l && l <= r && r <= a.length)
  if (r - l > 1) {
    val (lt, gt) = split(l, r)
    quicksort(l, lt)
    quicksort(gt, r)
  }
}

// Sort the whole array with quicksort(0, a.length).
The array partitioning loop invariant is
with unclassified. Swaps preserve the segment's array multiset and leave all entries outside it unchanged. The loop variant decreases in every branch. When swapping an entry from the right into i, do not increment i, since the incoming entry still needs classification. At termination,
Here the middle segment is nonempty because the original pivot occurs somewhere in the preserved array multiset. Both recursive segments therefore have length strictly smaller than . Mathematical induction on segment length proves that the two recursive calls sort their segments, and their ordering relative to the middle segment proves the complete sorting postcondition.
There is no additional array. The call stack and returned boundary pairs still use auxiliary space. An all-equal input takes one linear scan and no nontrivial recursive calls, so its time complexity is for . Balanced partitions give time complexity; a fixed first pivot can still give on adversarial distinct inputs. The worst-case call stack has entries.
Traverse the trie along the input string, stopping as soon as the required child is absent. All words and letter collections in this question are assumed to use only lowercase letters a through z; this is the alphabet represented by the given child array. Put the following methods inside the supplied Trie class.
def contains(w: String): Boolean = {
  var t: Trie = this
  var i = 0
  while (i < w.length && t != null) {
    t = t.children(toIndex(w.charAt(i)))
    i += 1
  }
  t != null && t.complete
}
The loop invariant is that t is the trie node reached by the prefix w.take(i), or null if that prefix has no path, with . If t is null, no extension can be a stored word. Otherwise, following the next character preserves the loop invariant. The loop variant is . At exit,
A prefix that exists but is not complete must return false. The empty string is present exactly when the root's complete flag is true. The time complexity is , with constant auxiliary space.
These operations consume the receiver, as expected for this destructive iterator interface. exists stops immediately after the first successful test.
trait RichIter[T] extends Iter[T] {
  def foreach(f: T => Unit): Unit = {
    while (hasNext) f(next())
  }

  def exists(p: T => Boolean): Boolean = {
    while (hasNext) {
      if (p(next())) return true
    }
    false
  }

  def foldLeft[U](x: U)(f: (U, T) => U): U = {
    var acc = x
    while (hasNext) acc = f(acc, next())
    acc
  }
}
For foreach, after iterations the supplied function has been applied in order to exactly the first entries. For exists, every consumed entry has failed the predicate unless the method is returning true. For the left fold, the loop invariant after consuming entries is
Each iteration extends the expression by the next entry on the right. For a finite remaining sequence, its unconsumed length is a common loop variant. Thus
An empty iterator invokes no callback, gives false for exists, and returns from the left fold. On an infinite iterator, exists can terminate when it finds a match; the other two operations ordinarily do not terminate. With constant-cost callbacks, finite input of length takes time complexity and constant auxiliary space.
Use an upper type bound requiring each element type to implement the supplied comparison interface for its own type. This is F-bounded polymorphism.
trait Ordered[T] {
  def <(that: T): Boolean
}

class MinSet[T <: Ordered[T]](contents: List[T])
    extends Set[T](contents) {
  def min: T = {
    require(elems.nonEmpty)
    var best = elems.head
    var rest = elems.tail
    while (rest.nonEmpty) {
      if (rest.head < best) best = rest.head
      rest = rest.tail
    }
    best
  }
}
Use this locally defined Ordered, rather than silently substituting the standard library's different comparison interface. The type bound makes rest.head < best well typed. To interpret the result as a minimum, require the comparison to be a consistent strict total order; merely providing a method named < cannot establish its mathematical laws.
The loop invariant is that best is a minimum among the entries already inspected, and rest is exactly the uninspected suffix. Comparing its next head with best preserves that property. The loop variant is the length of rest. On termination,
The method leaves the set unchanged, takes time complexity under constant-cost comparisons, and uses constant auxiliary space. It requires a nonempty set; inherited insertion and deletion continue to preserve the duplicate-free representation invariant.
Maintain a processed prefix split into values below the pivot and values at least the pivot. The remaining suffix has not yet been classified. Here a and N are the given array and its length.
def partition(l: Int, r: Int, p: Int): Int = {
  require(0 <= l && l <= r && r <= N)
  var k = l
  var j = l
  while (j < r) {
    if (a(j) < p) {
      val t = a(k); a(k) = a(j); a(j) = t
      k += 1
    }
    j += 1
  }
  k
}
The loop invariant is
In addition, the active segment remains a permutation of its original contents, and every entry outside it remains unchanged. If a(j) < p, swapping it with a(k) extends the first region; the displaced entry, when the two positions differ, already belongs in the second region. Otherwise only the second region grows. The loop variant is . At termination , so
There are exactly iterations, giving time complexity and constant auxiliary space.
For each source row, use a temporary dense array to accumulate contributions from nonzero pairs. For every stored , traverse stored entries in row of the other sparse matrix.
def mult(that: SparseMatrix): SparseMatrix = {
  require(N == that.N)
  val out = new SparseMatrix(N)
  for (i <- 0 until N) {
    val acc = Array.fill[Double](N)(0.0)
    for ((k, x) <- rows(i)) {
      for ((j, y) <- that.rows(k)) {
        acc(j) += x * y
      }
    }
    var j = N - 1
    var result: Row = Nil
    while (j >= 0) {
      if (acc(j) != 0.0) result = (j, acc(j)) :: result
      j -= 1
    }
    out.rows(i) = result
  }
  out
}
During accumulation, acc(j) is the sum of the already processed terms contributing to position . Missing entries multiply to zero, so ignoring them loses no contribution under the finite arithmetic convention of part (e). At completion,
Descending j and prepending produce strictly increasing stored indices. Omitting zero also handles cancellation. The temporary array uses auxiliary space beyond the output. The time complexity is
at worst , but potentially much smaller when the matrices are sparse. The term covers clearing and scanning the row accumulators. Finite-precision rounding can affect the numerical result, as in any Double matrix multiplication.
Track the length r of the trailing constant segment of an array, and the best length m anywhere in the processed prefix:
i := 1; r := 1; m := 1;
WHILE i < n DO
  IF a[i] = a[i-1] THEN r := r+1 ELSE r := 1 END;
  IF r > m THEN m := r END;
  i := i+1
END;
The loop invariant is: ; r is the length of the longest constant suffix of ; and m is the length of the longest constant segment of an array within . Initially all three lengths are one, using .
When the next entry equals its predecessor, the trailing constant segment extends by one; otherwise its new length is one. Every new plateau either was already in the old prefix or ends at the new entry, so taking the larger of the old m and the new r preserves the loop invariant. The loop variant is , a nonnegative integer decreased by one. At termination , so
The auxiliary space is , and correctly returns one without entering the loop.
Use the sorted sparse dot product merge, processing a coordinate only when it appears in both supports of a vector:
def dotProduct(vec1: Node, vec2: Node): Double = {
  require(vec1 != null && vec2 != null)
  require(vec1.index == vec2.index)
  var p = vec1.next
  var q = vec2.next
  var result = 0.0
  while ((p ne vec1) && (q ne vec2)) {
    if (p.index < q.index) p = p.next
    else if (q.index < p.index) q = q.next
    else {
      result += p.value * q.value
      p = p.next
      q = q.next
    }
  }
  result
}
Both structures must satisfy the representation invariant; equal dimension is checked explicitly. The loop invariant says that result contains exactly the products for all matching indices already passed, and that every skipped unequal index has zero in the other vector. The latter follows from sortedness: if p.index < q.index, that index cannot appear at q or later, and all earlier entries of the other vector have already been settled. Equality contributes its product exactly once. The total number of remaining ordinary entries is a loop variant; every iteration advances at least one cursor. Once either cursor reaches its header, all remaining entries pair with zeros. Thus the exact-arithmetic result is . With stored entries, time complexity is and auxiliary space is constant. Floating-point products and additions may round or overflow even when input coordinates are finite; no exact real-arithmetic accuracy claim is made for Double.
Replace suspended recursive calls by an explicit stack for an inorder traversal. Save each original right child before overwriting that link:
def flattenIterative(): (Tree, Tree) = {
  val pending = new scala.collection.mutable.Stack[Tree]
  var current = root
  var start: Tree = null
  var end: Tree = null
  while (current != null || pending.nonEmpty) {
    while (current != null) {
      pending.push(current)
      current = current.left
    }
    val t = pending.pop()
    val nextSubtree = t.right
    t.left = end
    if (end == null) start = t else end.right = t
    t.right = null
    end = t
    current = nextSubtree
  }
  root = null
  (start, end)
}
The method belongs to the concrete class owning root. Clearing root records that ownership has transferred to the returned doubly linked list; callers retain both returned endpoints. The inner loop invariant says that the current subtree lies before all its pending ancestors in inorder traversal; pushing and descending left preserves this fact. Its loop variant is the number of entries along the remaining left-child path, a finite decreasing quantity.
The outer loop invariant says that start through end is precisely the already emitted sorted prefix, with reciprocal links and null outside endpoints. current and the pending ancestors describe exactly the remaining traversal in order; original links that are still needed have been retained. A pending ancestor has not yet had its right child overwritten. The next popped entry is therefore the next sorted entry. Saving nextSubtree preserves its future work before assigning t.right = null; linking it to end extends the prefix by exactly one entry. The number of original entries not yet emitted is an outer loop variant, since each full outer iteration emits one. At exit nothing remains, proving the same preservation and ordering postconditions as the recursive version.
Each entry is pushed and popped once, giving time complexity and auxiliary space. No new Tree entries are allocated. The historical mutable stack interface provides the push and pop operations used here; its later deprecation does not change their meaning.[ref]
Perform a destructive binary search tree flattening by converting both child subtrees and splicing their endpoints around the original root:
def flatten(t: Tree): (Tree, Tree) = {
  if (t == null) (null, null)
  else {
    val oldLeft = t.left
    val oldRight = t.right
    val (ls, le) = flatten(oldLeft)
    val (rs, re) = flatten(oldRight)
    t.left = le
    if (le != null) le.right = t
    t.right = rs
    if (rs != null) rs.left = t
    val start = if (ls == null) t else ls
    val end = if (re == null) t else re
    (start, end)
  }
}
Save the child references before changing their interpretation. The input is a finite binary search tree satisfying part (b), and no external client may simultaneously rely on the old tree links. By structural induction, the two recursive calls return disjoint, ordered doubly linked lists containing exactly their original entries. The binary search tree bounds place every left word before t.word and every right word after it. Splicing the left end, t, and the right start therefore preserves strict order and reciprocal links. A missing left list makes t.left null and t the start; a missing right list similarly makes t the end. The resulting outside links are null as required by part (i).
The recursive loop variant analogue is the strictly smaller number of entries in each child subtree. Every entry is processed once and incurs a constant number of link writes, so time complexity is and recursion uses auxiliary space. No new Tree entry is created; returning endpoint tuples and using a call stack are permitted auxiliary storage. The old binary search tree representation is consumed by this operation.
Assume ascending sorted arrays; descending storage can be accessed in reverse index order without copying. Among entries, the requested rank is in ascending order, not . Repeated values occupy repeated ranks.
Choose entries from the first array and from the second, using their prefixes. The feasible range is . Write , , and similarly at , with missing left endpoints replaced by and missing right endpoints by . These are conceptual sentinels, not extra stored entries.
Use binary search over the feasible values of :
  • If , there are too many entries from on the left; decrease the upper bound to .
  • If , there are too few entries from on the left; increase the lower bound to .
  • Otherwise return .
For correctness, in the last case every left entry is no greater than every right entry, and exactly entries lie on the left. Their maximum is therefore the -th smallest entry. The failed inequalities are monotone in the searched index: increasing raises the boundary and decreases the boundary, so it cannot repair ; the other case is symmetric. A partition of the first entries of a merged ordering supplies an admissible solution, including ties. Each step halves an interval with at most choices, giving running time and auxiliary space. This is a two-sorted-array order statistic without constructing the merged array.
Use unit-cost insertion, deletion and substitution of one symbol. Their minimum total cost is the edit distance. Let be the edit distance between the first symbols of the first sequence and the first of the second. Initialize and . For compute
The cases delete the final source symbol, insert the final target symbol, or align the two final symbols, paying one for substitution and zero for a match. Every optimal alignment ends in one of these cases, and each case extends a valid shorter alignment. Induction on proves the dynamic programming recurrence correct.
Fill rows or columns in increasing order so every dependency is already available. There are entries with constant work per interior entry: running time for nonempty sequences, or including the boundary cases. The full table has space complexity ; two rows give auxiliary space when only the distance is wanted. Predecessor choices in the full table recover an optimal edit sequence.