Use permutation generation by insertion: recursively arrange the tail, then insert the removed head in every possible position.
inserts x [] = [[x]]
inserts x (y:ys) = (x:y:ys) : map (y:) (inserts x ys)
perms [] = [[]]
perms (x:xs) = concat (map (inserts x) (perms xs))The map function in
inserts prefixes y to each placement after the first position. For a list of length , the number of results obeys and , so mathematical induction gives . Every result still contains the same old elements and the one inserted element.Let be the length of
perms xs for an input of length . There is exactly one permutation of the empty list, namely the empty list itself: hence perms [] is [[]], with length one. For a nonempty input, each recursively obtained permutation has length and gives placements. ThusLength alone would not exclude repeated answers. Since the input elements are distinct, removing the distinguished element from any result uniquely recovers both its placement and the shorter permutation. Hence different construction choices give different outputs, and every desired permutation is obtained by inserting into its restriction to xs. For [a,b,c], this code yields [a,b,c], [b,a,c], [b,c,a], [a,c,b], [c,a,b], [c,b,a], each exactly once.In tail recursion, each recursive call is a tail call: its result is returned directly, with no suspended addition or other work. A compiler can then reuse an activation record, giving constant stack use. This is a possible optimization, not a guarantee made by the source syntax. The direct Fibonacci number procedure in part (a) must retain information while waiting to add two recursive results.
Extend the Fibonacci numbers with and use the suggested quantity . For ,while . This gives the Oberon procedures
PROCEDURE G(n, a, b: INTEGER): INTEGER;
VAR result: INTEGER;
BEGIN
IF n = 1 THEN
result := a
ELSE
result := G(n-1, a+b, a)
END;
RETURN result
END G;
PROCEDURE FibTail(n: INTEGER): INTEGER;
BEGIN
RETURN G(n, 1, 0)
END FibTail;The assignment of the recursive result does not perform arithmetic after the call; it merely passes that result to
RETURN, so the call is in tail recursion. A dialect permitting an immediate RETURN G(...) in the branch expresses the same control flow. The decreasing argument proves algorithm termination, and the displayed identity proves correctness by mathematical induction. ThusThere are recursive steps, giving arithmetic operations. Stack use is with tail call elimination and without it. With unbounded integers, the growing Fibonacci numbers also make arithmetic costs grow; the linear bound counts arithmetic operations.For either an upper triangular matrix or a lower triangular matrix,In the permutation formula for the determinant, every contribution except the diagonal product includes an entry on the zero side of the diagonal. Equivalently, successive cofactor expansions along the first column of an upper triangular matrix prove the formula by mathematical induction.
First consider powers of four, with a fixed finite base value. For a mathematical induction hypothesis , the recurrence relation givesprovided and also covers the base value.
The allowed integer rounding needs a little more care. Suppose the recurrence relation is applied above fixed base cases, as usual; applying with a ceiling would not define a positive sequence. For either rounding write or . For ,Choose large enough to bound all finitely many positive-index base values below . By strong induction,This argument starts at , so the floor convention's index zero, if used in an earlier base calculation, is absorbed into those fixed initial values. Thus, for either convention with a well-defined finite base,
Mathematical induction has two obligations. Establish the base case if the natural numbers start at zero, or if they start at one. Then prove the induction step: for an arbitrary natural in the relevant range, assume and deduce .
The base case is , so . To relate successive blocks of five, use the Fibonacci addition formula:This identity can also be obtained simply by applying the Fibonacci recurrence five times. If the mathematical induction hypothesis is , thenis a sum of two multiples of five. HenceThe generalisation is Fibonacci multiple-index divisibility:Indeed, , so precisely the same mathematical induction works with replaced by .
Represent Pascal's triangle by an infinite outer lazy list, each of whose elements is one finite row. Pad the preceding row with zero on its left and on its right, then add corresponding entries:
pascal :: [[Integer]]
pascal = iterate next [1]
where
-- Padding supplies the two boundary entries as well as the interior sums.
next :: [Integer] -> [Integer]
next row = zipWith (+) (0 : row) (row ++ [0])Here
iterate next [1] begins [[1],[1,1],[1,2,1],[1,3,3,1],...]. Every call to next receives a finite row, so its final zero is reachable. Lazy evaluation produces any requested finite prefix without trying to finish the infinite outer list.Indexing rows and columns from zero, Pascal's rule saysTake out-of-range binomial coefficients to be zero. The seed is the zeroth row, and the padded addition implements this identity at every position, including the two boundaries. Mathematical induction therefore proves every generated row correct. Using unbounded integers prevents overflow. Generating rows zero through takes arithmetic operations, with additional bit costs as the binomial coefficients grow.
Normalize each word once by applying
toLower to its characters. Maintain an association list whose keys are distinct normalized words and whose values are their frequencies:import Data.Char (toLower)
type TextWord = String
type Text = [TextWord]
type Frequency = Integer
type Entry = (TextWord, Frequency)
count :: Text -> [Entry]
count = foldl addWord [] . map (map toLower)
where
-- Increment the unique matching key, or append a new key with count one.
addWord :: [Entry] -> TextWord -> [Entry]
addWord [] w = [(w, 1)]
addWord ((v, n):rest) w
| w == v = (v, n + 1) : rest
| otherwise = (v, n) : addWord rest wAfter any input prefix, each normalized word seen so far occurs exactly once as a key, with its number of occurrences as value. This loop invariant holds initially for the empty association list. A step either increments the unique existing entry or introduces exactly one new entry, so mathematical induction provesfor every word that occurs. In particular,
Baa and baa contribute to the same key baa; the three frequency-two keys in the source example are baa, yes and sir.The association list preserves first-occurrence order, although the specification allows any order here. For input words and distinct normalized words, it performs key comparisons in the worst case. Character normalization costs the total input character count, and a string comparison can inspect multiple characters. Characterwise
toLower supplies the intended case-insensitive comparison convention for the example; full Unicode case folding can require a different normalization policy.Compare frequencies in decreasing order and use lexicographic order on words to break ties. Implement merge sort explicitly, so no imported sorting routine is needed:
-- Entry, TextWord and Frequency are as in part (b).
before :: Entry -> Entry -> Bool
before (w, p) (v, q) = p > q || (p == q && w <= v)
sort :: [Entry] -> [Entry]
sort [] = []
sort [x] = [x]
sort xs = merge (sort left) (sort right)
where
(left, right) = splitAt (length xs `div` 2) xs
merge :: [Entry] -> [Entry] -> [Entry]
merge [] ys = ys
merge xs [] = xs
merge xa@(x:xs) ya@(y:ys)
| before x y = x : merge xs ya
| otherwise = y : merge xa ysThe merge algorithm step always emits the smaller key under the ordering . The unchosen heads cannot precede that emitted entry because each input is already sorted. It preserves all entries, and each call removes one head, so it terminates with exactly the sorted union of the inputs. Mathematical induction over the recursive splits establishes correctness of merge sort.
For entries, splitting and merging cost per level, and there are levels:The base cases take constant time. This counts word comparisons as primitive operations, as in the question. If the maximum compared word length is , the character-operation bound becomes , in addition to frequency comparison costs.
For the supplied input,
sort . count yields[("baa",2),("sir",2),("yes",2),("any",1),("bags",1),("black",1),
("full",1),("have",1),("sheep",1),("three",1),("wool",1),("you",1)]In particular, the secondary ordering places
sir before yes, even though yes appears earlier in the original text. The requirement concerns sort; the simpler association list implementation of count has its own cost.P := I OR E
repeat:
Q := P OR boolean_product(P, E)
if Q = P: return P
P := QAt the start of each iteration, for the current length bound . The update produces , proving the loop invariant by mathematical induction. If , every edge extension of a known graph path is already known. Repeating the same update then makes no further change, so every longer graph path is represented as well.
Every reachable pair of distinct vertices has a simple path of at most edges: a repeated vertex allows the intervening part to be deleted. Thus the process stabilizes after finitely many updates and returnsThis is the reflexive transitive closure. An alternative stopping rule is to stop once the length bound is at least , avoiding a final equality-detection iteration. For or , already suffices.
Divide-and-conquer algorithms split a problem into smaller instances of the same kind, solve them recursively, and combine the solutions. A base case handles instances too small to split. For merge sort, divide the input into two halves, recursively sort both halves, and merge them by repeatedly taking the smaller current head. Empty and singleton inputs are already sorted.
The merge algorithm step preserves sortedness because its chosen head is no larger than either remaining head; it also preserves every input occurrence. These facts and mathematical induction on input length prove correctness. Splitting and merging take time, soAt each level the subproblem sizes sum to , and there are levels. Part 3(c) supplies an explicit Haskell implementation of this construction with a more specialized comparison rule. Standard array implementations use auxiliary storage.
In the 0-1 knapsack problem, item has weight and value . Choose a subset of the items, using each at most once, whose total weight is at most and whose total value is as large as possible. For the capacity-indexed dynamic program, take and the weights to be nonnegative integers. The empty choice is allowed.
Let be the greatest value attainable using only items with capacity . The base case is . With item unavailable when it is too heavy, the recurrence isEvery feasible optimum either excludes item , giving the first candidate, or includes it exactly once, leaving capacity for earlier items. The principle of optimality proves that the remaining earlier items must attain the indicated residual optimum. Conversely, either candidate constructs a feasible solution, so taking the larger is both necessary and sufficient. Mathematical induction on proves the table's meaning and the final answer .
Store item weights and values in arrays and the values in a two-dimensional array with entries. Fill rows in increasing ; each entry uses at most two values from the previous row. There are constant-work updates, givingFor positive growing capacities this is the familiar bound. The terms also cover capacity zero and zero-weight items: one must not initialize every to zero if zero-weight, positive-value items are allowed.
To recover an actual subset, store the chosen alternative at each entry or compare the two candidates while tracing backward from . A selected item changes to ; an excluded item changes it to . This takes extra time.
If only the optimal value is needed, keep two rows, using space. With strictly positive weights, a single capacity row updated in decreasing capacity also prevents item reuse. The two-row version handles zero weights without special in-place reasoning. The running time is pseudopolynomial, since the binary input length of is only logarithmic in its numerical value.
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 The array partitioning loop invariant iswith 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
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).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.
Prove both identities by mathematical induction. The base case is the two-set form of De Morgan's laws. For the union identity, suppose the result holds for . Applying the two-set identity and then the inductive hypothesis givesFor the intersection identity, the corresponding step isThese close the two inductions.
The real cube root in the denominator vanishes at , so the printed rule needs the domain of a function . On its actual domain of a function, the second iterated function simplifies toThis suggestsFor the induction step, assume the formula at and restrict to arguments for which its value is not . Its cube is , so another application givesReal cube roots are multiplicative, including for negative arguments. The newly excluded point satisfies , hence ; all earlier exclusions remain. This proves both the formula and the domain of a function by mathematical induction. The inherited exclusions are essential for an iteration of a partial function: cancellation in the final expression cannot repair an undefined intermediate application.
Use mathematical induction with the defining Fibonacci recurrence. Both identities hold at : their two sides are zero. If the first holds at , thenIf the second holds at , thenEach inductive hypothesis therefore advances to .
For fixed , use mathematical induction on . At , both sides equal one. If the hockey-stick identity holds at , then Pascal's identity givesThis is the required formula with replaced by .
Keep the two adjacent Fibonacci numbers in one shared pair. From the Fibonacci recurrence, if the current pair is , the next is . This gives the paired Fibonacci recurrence:
twoFibs :: Integer -> (Integer, Integer)
twoFibs 0 = (0, 1)
twoFibs n
| n > 0 = let (a, b) = twoFibs (n - 1)
in (b, a + b)
| otherwise = error "negative Fibonacci index"
fib :: Integer -> Integer
fib = fst . twoFibsThe pair pattern shares the result of the single recursive call. Mathematical induction establishesThere are pair updates and additions, rather than the repeated subcomputations of the original branching recursion. This is linear arithmetic-operation count; exact Fibonacci numbers have growing bit length. The pattern
fib (n+2) in the source expresses the recurrence; the guarded definition above uses ordinary Haskell syntax without requiring an n+k pattern extension.Maintain all pairs discovered so far. Extend every discovered path by one original binary relation edge, keeping the old pairs first:
transclose :: Eq a => [(a, a)] -> [(a, a)]
transclose r = grow (removedup r)
where
grow s =
let t = removedup (s ++ relcomp r s)
in if length t == length s then s else grow tAfter extensions, the represented binary relation is . This loop invariant follows by distributivity of composition of relations over a union. The old list
s is a duplicate-free prefix of t; therefore equality of lengths is equivalent to equality of the represented sets and signals a fixed point.Let be the finite set of endpoints appearing in the input. No extension introduces a new endpoint, so there are at most distinct pairs. Every nonfinal iteration adds at least one pair, proving algorithm termination. At the fixed point , we have and . Mathematical induction gives for every positive , while the loop invariant says that each discovered pair belongs to some . HenceThis is the transitive closure of a relation, rather than a reflexive closure: diagonal pairs appear only when supported by nonempty cycles. The empty input is handled immediately.
Let be the minimum number of jumps from index zero to index , with denoting an unreachable position. The minimum forward-jump dynamic programming recurrence iswhere the minimum of an empty set is and . Compute it in increasing index order:
D[0] = 0
for j = 1, ..., n-1:
D[j] = INFINITY
for i = 0, ..., j-1:
if D[i] != INFINITY and A[i] >= j-i:
D[j] = min(D[j], D[i] + 1)
return D[n-1]For algorithm correctness, classify any route to by its penultimate index . Every jump advances, so , and legality requires . By the principle of optimality, a shortest prefix to can be used; this gives the lower bound represented by the recurrence. Conversely, a minimizing reachable predecessor supplies an actual route of length , proving attainability. Mathematical induction on therefore proves every stored value correct.
There are predecessor tests, and two arrays are unnecessary when only the number is required. ThusFor the source example, the computed array isWhen , zero jumps are needed even if the lone entry is zero. An unreachable final position returns . Testing
A[i] >= j-i also avoids forming a potentially overflowing sum i + A[i] in bounded implementations. A faster frontier-based algorithm is possible, but the displayed method directly supplies the requested dynamic programming solution.