Store
pred[j] = i whenever a strict improvement sets D[j] = D[i] + 1. Initialize predecessors to NONE. After the dynamic programming pass, return NONE if the final position is unreachable; otherwise follow predecessors backwards:route = []
j = n - 1
while j != 0:
route.append(j)
j = pred[j]
route.append(0)
reverse(route)
return routeEvery stored predecessor is smaller than the current index, so reconstruction terminates. Its stored distance is exactly one smaller, so the output has precisely jumps and each jump satisfies its input bound. Therefore . Reconstruction takes additional time complexity and output space; the total dynamic programming bounds remain time and space complexity. Keeping the first predecessor on ties selects one optimal route deterministically.
Count pairwise exchanges in the implementation from part (b), including the initial root/final-leaf exchange. For , the final leaf has a nonroot parent whose key is strictly larger than the leaf's key. That parent remains after deletion. If the moved leaf could stay at the root without a downward exchange, it would dominate both root children and hence every remaining descendant, contradicting that larger remaining parent. Thus at least one heapify down exchange is necessary.
One downward exchange suffices. The following level-order array is a max-heap on keys one through fifteen:Every parent is larger than both children. Exchange the root key fifteen with the final key ten, remove fifteen, then exchange ten with the larger root child fourteen. Ten now has children nine and eight, so repair stops. The final array is
Hence the best-case maximum deletion in a binary heap isThis best case can be arranged at any such size: make the root child on the opposite side from the old final leaf the largest remaining key, the old leaf smaller than its own ancestors but larger than all descendants of that opposite child, and fill the remaining positions in decreasing order along each branch. The promoted child then allows the leaf to stop at depth one.
For sizes two and three, one initial exchange can suffice; for size one no exchange between distinct positions is needed. If only exchanges during heapify down are counted, the answer for is one. In the fifteen-key example the two exchanges involve three distinct keys, namely fifteen, ten and fourteen. An implementation that moves a hole by assignments need not perform literal swaps, so the count is explicitly tied to the exchange implementation. The worst-case time complexity remains logarithmic even though this best-case repair is constant.
Assume . Use bottom-up heap construction to make a max-heap of the first input items. For each subsequent input value , compare it with the root. If is at least the root, discard it; otherwise replace the root by and apply heapify down. Return the final root.
The loop invariant says the heap contains a multiset of the smallest values seen so far. It holds after initialization. If is at least the current largest retained value, the already retained items are no larger, so discarding preserves the invariant. If is smaller, remove one largest retained value and keep instead; precisely the smallest values remain. This argument handles equal integers by treating repeated occurrences as separate items. At the end,Construction costs ; each remaining value uses one comparison and at most one logarithmic repair. Thus the selection algorithm has time complexityFor , the heap is a single running minimum and the time is . The uniform expression covers that boundary; interpreting the printed literally at would incorrectly make it zero.
Run Kruskal's algorithm in nonincreasing order of edge weight, accepting an edge exactly when its endpoints belong to different current connected components. Use a disjoint-set data structure to test and merge those components. Stop after accepting edges.
Equivalently, apply ordinary Kruskal's algorithm to . For every spanning tree,so minimizing transformed total weight is exactly maximizing original total weight. Thus the output is a maximum spanning tree. Equal weights may be processed in any fixed order. Sorting dominates the time complexity, giving with the usual efficient disjoint-set data structure.
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 The loop invariant isIn addition, the active segment remains a permutation of its original contents, and every entry outside it remains unchanged. If
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
}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 , soThere are exactly iterations, giving time complexity and constant auxiliary space.Use missing-value search by deficient value intervals: halve the candidate value interval, and apply array partitioning only to the array segment whose entries lie in that interval.The loop invariant is , , andMore precisely, the segment contains all occurrences of original values in , and the complete array remains a permutation of its original state. The occurrences statement is essential: absence from the final segment must imply absence from the original array.
def missing(): Int = {
require(N < Int.MaxValue)
var lo = 0; var hi = N + 1
var l = 0; var r = N
while (hi - lo > 1) {
val p = lo + (hi - lo) / 2
val k = partition(l, r, p)
if (k - l < p - lo) {
hi = p; r = k
} else {
lo = p; l = k
}
}
lo
}After array partitioning, let , , , and . If , retain the left side. Otherwise the right side hasoccurrences. Either choice preserves the strict deficiency and the all-occurrences condition. The positive integer width decreases, proving termination. At width one, the active segment has fewer than one entry and is empty. ThereforeFor the time complexity, after splits the width is at most , and the scan length is smaller than that width. Summing over the levels gives a geometric series plus logarithmic overhead, hence overall, conventionally for . This proof bounds the retained occurrence count by the retained value width; it does not assume that each array partitioning splits the number of occurrences in half. The case returns zero immediately.
Start at the highest level. Move right only while the next key is strictly smaller than At each inner loop head,
x, then descend without returning to head.def findPreds(x: Int): Array[Node] = {
require(Int.MinValue < x && x < Int.MaxValue)
val preds = new Array[Node](NumLevels)
var n = head
var level = NumLevels - 1
while (level >= 0) {
while (n.nexts(level).datum < x) {
n = n.nexts(level)
}
preds(level) = n
level -= 1
}
preds
}n belongs to the current level and has key below x. Every node already passed has key below x. The representation invariant ensures that n also belongs to the next lower level. Stopping just before a key at least x therefore givesThe tail prevents a null dereference during the comparison. Strict comparison makes the predecessor lie before the first occurrence of x, including duplicates. On the source diagram, findPreds(14) yields data , in increasing level order.[ref]Imperative Programming examination paper by the University of Oxford (2014). p. 4, skiplist diagram and Question 3(c)
With the customary random-level policy this has expected logarithmic time complexity for a suitably sized level cap; an unlucky skip list can require a linear scan. No deterministic logarithmic guarantee is claimed.
Identify the first matching node at level zero, then bypass that node wherever it appears. Comparing keys alone at higher levels could accidentally remove links to a different occurrence with the same key.Because higher-level chains are subsequences of level zero, the predecessor found for any level containing
def delete(x: Int): Unit = {
if (x != Int.MinValue && x != Int.MaxValue) {
val preds = findPreds(x)
val victim = preds(0).nexts(0)
if ((victim ne tail) && victim.datum == x) {
for (level <- 0 until NumLevels) {
if (preds(level).nexts(level) eq victim) {
preds(level).nexts(level) = victim.nexts(level)
}
}
}
}
}victim points directly to it: no earlier equal-valued node can intervene before the first level-zero occurrence. Bypassing it preserves the order and nesting of all the linked lists. Levels lacking it are unchanged. ThusThe additional work is . With the usual level cap and random heights, updates have the expected logarithmic time complexity associated with a skip list.Select entries having the requested column. The representation invariant ensures that the selected list has length at most one.ConsequentlyThis version follows the hint and has time complexity linear in the number of stored entries in row
def get(i: Int, j: Int): Double = {
require(0 <= i && i < N && 0 <= j && j < N)
val entries = rows(i).filter(e => e._1 == j)
if (entries.isEmpty) 0.0 else entries.head._2
}i. An ordered early-exit scan is also possible.Keep the entries before column All indices in
j, discard any old entry at j, and retain those after it. Insert the replacement only when it is nonzero.def set(i: Int, j: Int, x: Double): Unit = {
require(0 <= i && i < N && 0 <= j && j < N)
val before = rows(i).takeWhile(e => e._1 < j)
val after = rows(i).dropWhile(e => e._1 <= j)
val middle: Row = if (x == 0.0) Nil else List((j, x))
rows(i) = before ::: middle ::: after
}before are below j and all in after are above it, so the representation invariant is preserved. Other rows and all other column values remain unchanged:Assigning zero removes storage for that entry. The time complexity is linear in the stored row length.Construct a fresh sparse matrix, transform each stored value, and omit products that become zero.Filtering preserves column order. It handles
def scale(s: Double): SparseMatrix = {
val out = new SparseMatrix(N)
for (i <- 0 until N) {
out.rows(i) = rows(i).map { case (j, x) => (j, s * x) }
.filter(e => e._2 != 0.0)
}
out
}s == 0.0 and any floating point underflow producing zero. Sharing immutable list structure elsewhere would be safe; this code constructs new transformed rows. Within the class, Scala's ordinary private permits access to out.rows. The result isThe time complexity is for stored entries. As usual for the supplied Double model, arithmetic means machine arithmetic; for a finite-real interpretation assume finite operands and products, excluding infinities and NaNs.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.During accumulation,
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
}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 isat 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.Store characters in a mutable dynamic array. Its length is maintained by the collection, giving constant-time The representation invariant requires a nonnull private dynamic array containing a finite sequence of characters, with its internal collection invariant maintained by the library. The abstraction function isInsertion allows the end position and shifts the suffix right; deletion returns exactly the removed character and shifts the suffix left.
size.class DocImpl extends Doc {
private val text = scala.collection.mutable.ArrayBuffer.empty[Char]
def insert(x: Char, pos: Int): Unit = {
require(0 <= pos && pos <= text.length)
text.insert(pos, x)
}
def delete(pos: Int): Char = {
require(0 <= pos && pos < text.length)
text.remove(pos)
}
def read(pos: Int): Char = {
require(0 <= pos && pos < text.length)
text(pos)
}
def size: Int = text.length
}read leaves the abstract state unchanged. size equals the abstract sequence length in time complexity. Indexed reads are also constant time; insertion and deletion may take linear time, which the question permits.Keep 26 cached counts, updating them after a successful delegated edit. Count exactly lowercase letters; the query alphabet does not make uppercase letters the same character.For an initially empty underlying document that stores inserted arguments unchanged, the representation invariant isInsertion increments precisely the inserted lowercase character's count. Deletion uses the returned character, avoiding an extra indexed read and decrementing the character actually removed. Updating after
trait CharTrackPlugIn extends Doc {
private val counts = Array.fill[Int](26)(0)
private def lower(x: Char): Boolean = 'a' <= x && x <= 'z'
abstract override def insert(x: Char, pos: Int): Unit = {
super.insert(x, pos)
if (lower(x)) counts(x - 'a') += 1
}
abstract override def delete(pos: Int): Char = {
val x = super.delete(pos)
if (lower(x)) counts(x - 'a') -= 1
x
}
def getCount(x: Char): Int = {
assert(lower(x))
counts(x - 'a')
}
}super means a rejected edit leaves the counts unchanged. Both the added update work and getCount use time complexity and fixed storage.The
abstract override methods implement a stackable trait modification: they require a concrete document implementation further along the delegation chain. The invariant above depends on subsequent layers preserving inserted characters. Part (d) deliberately composes a transforming layer that can break this assumption; that is the transform-sensitive trait frequency counter phenomenon.In executable Haskell, the algebraic data type declaration uses This is a preorder traversal of a full binary tree, including the leaves. Structural induction proves correctness: the leaf of a rooted tree case has precisely one output, and the fork case concatenates the correct preorder traversals of its two subtrees after its own marker.
data, rather than the printed type. A leaf of a rooted tree contributes False; a fork contributes True followed by its left and right subtrees in that order.data Tree = Leaf | Fork Tree Tree
preorder :: Tree -> [Bool]
preorder Leaf = [False]
preorder (Fork l r) = True : (preorder l ++ preorder r)An equivalent version avoids repeatedly copying the left list through list concatenation:Its strengthening is
preorder t = walk t []
where
walk Leaf rest = False : rest
walk (Fork l r) rest = True : walk l (walk r rest)walk t rest = preorder t ++ rest. Each vertex constructs one list cell, so this version has linear time complexity. The concise result isFor parts (b)–(e), use array indices as tree handles. This also makes the shared storage in part (d) type-correct: both pending handles and output markers occupy integer cells. Zero denotes a leaf of a rooted tree; a positive handle denotes a fork whose children are in Initialize
nodes[t-1]. Only finite acyclic structures are admitted. An allocated fork's children are themselves valid handles.CONST MaxForks = 100000;
TYPE Tree = INTEGER;
Node = RECORD left, right: Tree END;
Arena = ARRAY MaxForks OF Node;
VAR nodes: Arena;
used: INTEGER;
PROCEDURE NewFork(left, right: Tree): Tree;
BEGIN
ASSERT(used < MaxForks);
nodes[used].left := left; nodes[used].right := right;
INC(used);
RETURN used
END NewFork;
PROCEDURE Preorder(t: Tree; VAR out: ARRAY OF INTEGER;
VAR count: INTEGER);
PROCEDURE Visit(p: Tree);
BEGIN
IF p = 0 THEN
out[count] := 0; INC(count)
ELSE
out[count] := 1; INC(count);
Visit(nodes[p-1].left); Visit(nodes[p-1].right)
END
END Visit;
BEGIN
count := 0; Visit(t)
END Preorder;used := 0 before constructing any trees; NewFork allocates within this arena. The fixed capacity is an implementation parameter and may be replaced by a dynamically allocated arena. The returned prefix out[0..count) encodes Boolean values by and . This encoding is part of the chosen representation, not a conversion of a pointer into a Boolean value.The recursion invariant is that
Visit(p) appends exactly the preorder traversal of the subtree represented by p. If the full binary tree has forks, it has leaves; henceEach occurrence is visited once, using linear time complexity and a call stack bounded by the height of a binary tree. The arena is the input representation, not an extra copy made by the traversal.Use an explicit stack of handles. Push the right subtree first, so that the left subtree is removed first by the LIFO stack.The loop invariant says that the already written prefix, followed by the preorder traversals of the pending subtrees in top-to-bottom stack order, equals the entire required output. Popping a leaf of a rooted tree transfers one marker to the prefix. Popping a fork transfers its marker and replaces its remaining work by left then right subtree work. Both preserve the loop invariant. The number of as-yet-unwritten vertices decreases by one per iteration, proving termination.The time complexity is linear in the output length. The maximum stack size is at most one plus the height of a binary tree.
PROCEDURE PreorderIter(t: Tree; VAR out: ARRAY OF INTEGER;
VAR count: INTEGER;
VAR stack: ARRAY OF Tree);
VAR top: INTEGER; p: Tree;
BEGIN
count := 0; top := 1; stack[0] := t;
WHILE top > 0 DO
DEC(top); p := stack[top];
IF p = 0 THEN
out[count] := 0; INC(count)
ELSE
out[count] := 1; INC(count);
stack[top] := nodes[p-1].right; INC(top);
stack[top] := nodes[p-1].left; INC(top)
END
END
END PreorderIter;Maintain a cursor to the first unread marker. A fork consumes its own marker and then two complete encodings, in left-to-right order.Start with
PROCEDURE Decode(VAR code: ARRAY OF INTEGER;
VAR pos: INTEGER): Tree;
VAR marker: INTEGER; left, right: Tree;
BEGIN
marker := code[pos]; INC(pos);
IF marker = 0 THEN RETURN 0 END;
ASSERT(marker = 1);
left := Decode(code, pos);
right := Decode(code, pos);
RETURN NewFork(left, right)
END Decode;pos := 0; t := Decode(code, pos), with sufficient unused arena space. The valid-input precondition ensures that every read exists and every fork has two encoded subtrees. Evaluating the two recursive calls in separate statements fixes their order.Structural induction shows that decoding reconstructs the same binary tree shape and leaves the cursor just after its encoding. The tree handles need not coincide with the original handles: the abstract tree does. The result isThe time complexity is linear in the encoding length, with call stack space bounded by the height of a binary tree.
Reuse The traversal starts at the dummy header node, so after steps
p as q's header and allocate one new header for r. Cut after data elements, allowing .PROCEDURE Split(p: List; VAR q, r: List);
VAR n, k, i: INTEGER; last: Tail;
BEGIN
n := p.count; k := n DIV 2;
q := p; NEW(r);
last := q;
FOR i := 1 TO k DO last := last.tail END;
r.tail := last.tail; last.tail := NIL;
q.count := k; r.count := n-k
END Split;last precedes the suffix assigned to r. Cutting that link produces two finite disjoint linked lists. Their concatenated abstraction function is the original list, with order preserved, andBoth empty and singleton inputs work without special traversal cases. The time complexity is and only one header is allocated.Compute either the common depth of the leaves of a rooted tree, or failure when the depths differ. A tree fold can return a A leaf has depth zero. An internal vertex has equally deep leaves exactly when every child rose tree is perfect and their returned depths agree; adding one accounts for the edge to each child. This proves the stated meaning of the result by structural induction. In particular, unary branching is allowed, and a single leaf is perfect.
Maybe Integer: Just h means that the rose tree is a perfect rose tree with all leaves at depth relative to its root; Nothing means it is not perfect.perfect :: RTree a -> Bool
perfect t =
case foldRTree step t of
Nothing -> False
Just _ -> True
where
step :: a -> [Maybe Integer] -> Maybe Integer
step _ [] = Just 0
step _ (Nothing : _) = Nothing
step _ (Just h : hs)
| all (== Just h) hs = Just (h + 1)
| otherwise = Nothingperfect returns precisely the required Boolean. Its time complexity is linear in the number of vertices, assuming unit-cost depth arithmetic; labels are never compared or evaluated.