Summary

Solved rank-finding by using medians of medians, then introduced a divide-and-conquer algorithm for integer multiplication: Karatsuba.


The input is a problem of size 𝑁. We divide input into 𝐴 subproblems, each of size 𝑁𝐡. We conquer each subproblem recursively, and then combine subproblem solutions to solve the original problem.

Running time𝑇(𝑁)=𝐴⋅𝑇(𝑁𝐡)+{time to divide}+{time to combine}

Rank Finding

The input is an unsorted array 𝐴 of 𝑁 distinct numbers and a rank 1β‰€π‘Ÿβ‰€π‘. Output the π‘Ÿth smallest number, i.e. the unique π‘₯∈𝐴 with

π‘Ÿ=rank(𝐴,π‘₯)≔#{π‘¦βˆˆπ΄:𝑦≀π‘₯}

For instance, to find the median, π‘Ÿ=𝑁+12.

Example

𝐴=[2,βˆ’5,3,10,1,βˆ’1,8]. Median = 2, Max = 10, Min = -5. RankFind(𝐴,2)=βˆ’1

Can we do better than 𝑂(𝑛(log(𝑛)))? Yes. We can do 𝑂(𝑁) (solved in 1973).

One idea is to pick a pivot π‘₯∈𝐴 and partition 𝐴 into 𝐿(π‘₯)={π‘¦βˆˆπ΄:𝑦<π‘₯} and 𝑅(π‘₯)={π‘¦βˆˆπ΄:𝑦>π‘₯}. Comparing π‘Ÿ to |𝐿(π‘₯)| tells us which side the answer lives on, so we only need to recurse into one of the two halves. Since each recursive call shrinks the problem, a good pivot choice (near the median) gives 𝑇(𝑁)=𝑇(𝑁/2)+𝑂(𝑁)=𝑂(𝑁). This still could be bad if our pivot is far to the right or far to the left. In the worst case, we get

𝑁+(π‘βˆ’1)+…=Θ(𝑛2)

No improvement. If we could luckily get the median every time, we would have an Θ(𝑁) solution.

Definition

For 12<𝑐<1, we say π‘₯∈𝐴 is c-balanced if |𝐿(π‘₯)|,|𝐺(π‘₯)|≀𝑐⋅𝑁

Note

If we can find a c-balanced element in 𝑂(𝑁) time, we can solve rank finding in 𝑂(𝑁) time.

The solution is recursive. We divide 𝐴 into 𝑁5 buckets, each of size 5. We compute each bucket’s median, which we can do in constant time. Then we compute the median of all the bucket medians and use that as the pivot. The rest of the algorithm is standard divide-and-conquer.

The tricky part is the time complexity of finding the median of medians.

Claims

  1. Median of medians is 34-balanced.

  2. The runtime is 𝑂(𝑁).

Integer Multiplication

We input two 𝑁-bit numbers π‘Ž and 𝑏. We want to output their product π‘Žβ‹…π‘. The grade-school algorithm takes 𝑂(𝑛2) time. Kolmogorov conjectured that this was optimal. He was wrong; Karatsuba found a divide-and-conquer algorithm that is better.

Karatsuba Algorithm

Split each number in half: π‘Ž=2𝑁2β‹…π‘₯+𝑦 and 𝑏=2𝑁2⋅𝑀+𝑧. Expanding the product gives

π‘Žπ‘=2𝑁⋅π‘₯𝑀+2𝑁2β‹…(π‘₯𝑧+𝑦𝑀)+𝑦𝑧

which naively requires four multiplications of 𝑁2-bit numbers (π‘₯𝑀, π‘₯𝑧, 𝑦𝑀, 𝑦𝑧), giving 𝑇(𝑁)=4𝑇(𝑁2)+𝑂(𝑁)=𝑂(𝑁2) β€” no better than grade school.

Karatsuba’s trick is to notice that we already compute π‘₯𝑀 and 𝑦𝑧, so we can get the cross term for free: (π‘₯𝑧+𝑦𝑀)=(π‘₯+𝑦)(𝑀+𝑧)βˆ’π‘₯π‘€βˆ’π‘¦π‘§. This replaces two recursive multiplications with one, bringing us down to three multiplications of size 𝑁2. So now we only need three recursive multiplications: 𝑃=π‘₯𝑀, 𝑄=𝑦𝑧, and 𝑅=(π‘₯+𝑦)(𝑀+𝑧). We recover the cross term as 𝑆=π‘…βˆ’π‘ƒβˆ’π‘„=π‘₯𝑧+𝑦𝑀. Then appending 𝑁 zeros to 𝑃 is just multiplying by 2𝑁, and appending 𝑁2 zeros to 𝑆 is multiplying by 2𝑁2, so we return 𝑃⋅2𝑁+𝑆⋅2𝑁2+𝑄=π‘Žπ‘.

The recurrence becomes 𝑇(𝑁)=3𝑇(𝑁2)+𝑂(𝑁)=𝑂(𝑁log23)β‰ˆπ‘‚(𝑁1.585).

Is this the fastest? IN 1971, SchΓΆnge-Strassen found 𝑂(𝑁⋅log(𝑁)β‹…loglog(𝑁)). In 2007, it was brought down to 𝑂(𝑁⋅log(𝑁)β‹…2logβˆ—(𝑁)), where logβˆ—(𝑁) is the number of times you must apply log to 𝑁 before the result is ≀1. It grows absurdly slowly (e.g. logβˆ—(265536)=5), so is is nearly constant.

Not to worry about logβˆ—, as in 2019 an 𝑂(𝑁⋅log𝑁) solution was found.

Recitation

  1. Divide & Conquer
    1. Divide, conquer, combine
  2. Median Finding

We often see recurrences in the form

𝑇(𝑛)=π‘Žπ‘‡(𝑛𝑏)+𝑓(𝑛)

The Master Theorem is a general method for analyzing recurrence relations of the above form. It turns out that 𝑛log𝑏(π‘Ž) is important: it’s the recursion cost, while 𝑓(𝑛) is the one-time cost. We need to see which dominates.

  1. 𝑓(𝑛)=𝑂(𝑛log𝑏(π‘Ž)βˆ’πœ€))⇒𝑇(𝑛)=Θ(𝑛log𝑏(π‘Ž))
  2. 𝑓(𝑛)=𝑂(𝑛log𝑏(π‘Ž)βˆ’πœ€logπ‘˜π‘›))⇒𝑇(𝑛)=Θ(𝑛log𝑏(π‘Ž)logπ‘˜+1𝑛)
  3. Is 𝑓(𝑛) is polynomially greater, then it’s just Θ(𝑓(𝑛)) We can also use substitution.

Example

𝑇(𝑛)=3𝑇(𝑛5)+2𝑇(𝑛7)+𝑂(𝑛)

We will use induction. 35+27=3135<1. Let’s guess it’s 𝑂(𝑛), i.e. 𝑇(𝑛)≀𝑐1𝑛.

𝑇(π‘˜)≀35𝑐1π‘˜+27𝑐1π‘˜+𝑐2π‘˜=(3135𝑐1+𝑐2)π‘˜β‰€π‘1π‘˜