Notes-01-Algorithmic-Thinking-Arithmetic-Problems
Notes-01-Algorithmic-Thinking-Arithmetic-Problems
Contents
1 Parity check of an integer 1
1.1 Parity Check: Formulation for large input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Parity Check: A Different Formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
4 Analysis of Algorithms 4
4.1 Running Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
4.2 Elementary operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.3 Runtime as a function of input size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.4 Best/Worst/Average Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.5 Growth of runtime as size of input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1
7 Egg Dropping Problem 12
Input: An integer A
Output: True if A is even, else False
Algorithm 1 Parity-Test-with-mod
if A mod 2 = 0 then
return true
The above solution is written in pseudocode (more on it later). This algorithm solves the problem, however
following are some issues with it.
It only works if A is given in an int
What if A doesn’t fit an int and A’s digits are given in an array?
What if A is given in binary/unary/. . . ?
Note that these issues are in addition to usual checks to see if it is a valid input, i.e. to check that A is not a
real number, a string, or an image, etc.
7 6 5 4 3 2 1 0
A=
5 4 6 9 2 7 5 8
2
Solution:
By definition of even integers, we need to check if A[0] mod 2 = 0. If so then output A is even.
Algorithm 2 Parity-Test-with-mod
Input: A - digits array
Output: true if A is even, otherwise false
if A[0] mod 2 = 0 then
return true
else
return false
Algorithm 3 Parity-Test-with-no-mod
Input: A - digits array
Output: true if A is even, otherwise false
if A[0] = 0 then return true
else if A[0] = 2 then return true
else if A[0] = 4 then return true
else if A[0] = 6 then return true
else if A[0] = 8 then return true
else
return false
An algorithm is defined as a procedure designed to solve a computational problem. When designing an algorithm,
the designer needs to keep in mind some important features which are to be used when formulating an algorithm:
Input: This is the data that is passed to the algorithm, and may be in different forms. For instance,
it could be a simple number, or a list of numbers. Other examples include matrices, strings, graphs,
images, videos, and many more. The format of input is an important factor in designing algorithms, since
processing it can involve extensive computations.
Size of Input: The size of input is a major factor in determining the effectiveness of an algorithm.
Algorithms which may be efficient for small sizes of inputs may not do the same for large sizes.
Output: This is the final result that the algorithm outputs. It is imperative that the result is in a form
that can be interpreted in terms of the problem which is to be solved by the algorithm. For instance, it
makes no sense to output ‘Yes’ or ‘No’ to a problem which asked to find the maximum number out of an
array of numbers.
3
Pseudocode: This is the language in which an algorithm is described. Note that when we say pseu-
docode, we mean to write down the steps of the algorithm using almost plain English in a manner that
is understandable to a general user. Our focus will be the solution to the problem, without going into
implementation details and technicalities of a programming language. Given our background, we will be
using structure conventions of C/C + +/Java. We will see many examples of this later on in the notes.
Formulating the problem with precise notation and definitions often yield a good ideas towards a solution. e.g.
both the above algorithms just use definitions of even numbers
This is implementing the definition algorithm design paradigm, one can also call it a bruteforce solution, though
this term is often used for search problem.
One way that I find very useful in designing algorithms to not look for a smart solution right away. I instead
ask, what is the dumbest/obvious/laziest way to solve the problem? What are the easiest cases? and what are
the hardest cases? where the hardness come from when going from easy cases to hard cases?
4
How the number of operation depends on the “size” of input
The answer usually is a function mapping the size of input to number of operations, in the worst case.
We discuss it in more detail in the following section
3.3.1 Runtime:
of the algorithms we discuused so far. The first algorithm performs one mod operation over an integer and one
comparison (with 0). The second algorithm performs one mod operation over a single-digit integer (recall size
of the input) and one comparison (with 0) The third algorithm performs a certain number of comparisons of
single digit integers. The actual number of comparison depends on the input. If A is even and its last digit is 0,
then it takes only one comparison, if A[0] = 2, then we first compare A[0] with 0 and then with 2, hence there
are two comparisons. Similarly if A is odd or A[0] = 8, then it performs five comparisons.
This gives rise to the concept of best-case, worst-case analysis. While the best-case analysis gives us an optimistic
view of the efficiency of an algorithm, common sense dictates that we should take a pessimistic view of efficiency,
since this allows us to ensure that we can do no worse than worst-case, hence our focus will be on worst-case
analysis.
Note that the runtimes of these algorithms do not depend on the input A, as it should not, as discussed above we
consider the worst case only. But they do not even depend on the size of the input, we call this constant runtime,
that is it stays constant if we increase the size of input, say we double the number of digits the algorithm still
in the worst case performs 5 comparisons.
4 Analysis of Algorithms
Analysis of algorithms is the theoretical study of performance and resource utilization of algorithms. We
typically consider the efficiency/performance of an algorithm in terms of time it takes to produce output. We
could also consider utilization of other resources such as memory, communication bandwidth etc. One could
also consider various other factors such as user-friendliness, maintainability, stability, modularity, and security
etc. In this course we will mainly be concerned with time efficiency of algorithm.
5
We need a consistent mechanism to measure running time of an algorithm. This runtime should be inde-
pendent of the platform of actual implementation of the algorithm (such as actual computer architecture,
operating system etc.) We need running time also to be independent to actual programing language used
for implementing the algorithm.
Over the last few decades, Moore’s Law has predicted the rise in computing power available in orders of
magnitude, so processing that might have been unfeasible 20 years ago is trivial with today’s computers. Hence,
a more stable measure is required, which is the number of elementary operations that are executed, based on
the size of input. We will define what constitutes an elementary operation below.
Average case:
tav (n) = AverageI:|I|=n {T (I)}
6
5 Basic Integers Arithmetic problems
Now that we have established the terminology and thinking styles, we look at some representative problems and
see whether we can satisfy the questions with regards to the algorithm employed.
This problem is very simple if A, B, and their sum are within word size of the given computer. But for larger n
as given in the problem specification, we perform the grade-school digit by digit addition, taking care of carry
etc.
6 5 4 3 2 1 0 1 1 1
A=
4 6 9 2 7 5 8 5 4 6 9 2 7 5 8
+
6 5 4 3 2 1 0 8 5 1 7 2 2 6 1
B=
5 1 7 2 2 6 1 1 3 9 8 6 5 0 1 9
In order to determine the tens digit and unit digit of
a 2-digit number, we can employ the mod operator and
the divide operator. To determine the units digit, we The algorithm, with mod operator.
simply mod by 10. As for the tens digit, we divide by
10, truncating the decimal. We need this to determine Algorithm 4 Adding two long integers
the carry as we do manually. Can there be a better way Input: A, B - n-digits arrays of integers A and B
to determine the carry in this case, think of what is the Output: S = A + B
(largest) value of a carry when we add two 1-digit integer. 1: c ← 0
We can determine if there should be a carry if a number 2: for i = 0 to n − 1 do
is greater than 9, in that case we only have to extract 3: S[i] ← (A[i] + B[i] + c) mod 10
the unit digit. We discussed in class that if the mod 4: c ← (A[i] + B[i] + c)/10
operator and type-casting is not available, then we can 5: S[n] ← c
still separate digits in an integer by using the definition
of positional number system.
Correctness of Algorithm for adding two integers given as arrays The correction of this algorithm
again follows from the definition of addition.
Runtime of Algorithm for adding two integers given as arrays The running time (number of single
digits) arithmetic operations performed by this algorithm is determined in details as follows, later on we will
not go into so much detail. We count how many times each step is executed.
7
We do not count the memory operations, the algorithm clearly performs 4n 1-digit additions, n divisions and
n mod operations. If we consider all arithmetic operations to be of the same complexity, then the runtime of
this algorithm is 6n. You should be able to modify the algorithm to be able to add to integers that do not have
the same number of digits.
Can we improve this algorithm? Since any algorithm for adding n digits integers must perform some
arithmetic on every digit, we cannot really improve upon this algorithm.
We apply the grade-school multiplication algorithms, multi- Algorithm 6 Multiplying two long integers
ply A with the first digit of B, then with the second digit Input: A, B - n-digits arrays of integers A and B
of B and so on, and adding all of these arrays. We use a Output: C = A ∗ B
n × n array Z (a 2-dimensional array or a matrix) to store 1: for i = 1 to n do
the intermediate arrays. Of course, now we know how to add 2: c←0
these arrays. 3: for j = 1 to n do
2 7 5 8 4: Z[i][j + i − 1] ← (A[j] ∗ B[i] + c) mod 10
×
9 6 3 2 5: c ← (A[j] ∗ B[i] + c)/10
5 5 1 6 6: Z[i][i + n] ← c
8 2 7 4
7: carry ← 0
1 6 5 4 8
8: for i = 1 to 2n do
2 4 8 2 2
9: sum ← carry
2 6 5 6 5 0 5 6
10: for j = 1 to n do
Here again we will use the technique to determine the carry
11: sum ← sum + Z[j][i]
and the unit digits etc. Can we be sure that when we multi-
ply two 1 digit integers, the result will only have at most 2 12: C[i] ← sum mod 10
digits. 13: carry ← sum/10
14: C[2n + 1] ← carry
8
5.2.1 A reformulation of the multiplication problem
In this section, we demonstrate how the multiplication problem can be
reformulated and how it helps us solve the problem much simply.
Algorithm 7 Multiplying two long
Think of the integers A and B in positional number system and apply
integers using Distributive law of
distributive and associative laws we get the following very simple algo-
multiplication over addition
rithm.
1: C ← 0
2: for i = 1 to n do
A[0]∗100 +A[1]∗101 +A[2]∗102 +. . . × B[0]∗100 +B[1]∗101 +B[2]∗102 +. . . 3: for j = 1 to n do
4: C ← C +10i+j ×A[i]∗B[j]
The algorithm with this view of the problem is as follows
The correctness of this algorithm also follows from definition of multiplication and it performs n2 single-digit
multiplications and n2 shifting (multiplication with powers of 10).
Can we improve these algorithms? When we study divide and conquer paradigm of algorithm design we
will improve upon this algorithm.
Checking if a program halts For some programs, checking if they halt is easy
9
Algorithm Integer n Algorithm No input
Algorithm No input n←1
while n ̸= 0 do
return true while n ̸= 0 do
n←n−2
n←n+1
Clearly halts
Halts if n ≥ 0 and n is even
Never halts
Algorithm negFLT(integer n) Suppose we had a solution halt for the halting problem.
Calling halt.cpp on negFLT we get the following.
f lag ← true
a←1 halt(negFLT(n)) = Yes ⇐⇒ Fermat’s last theorem is
while f lag = true do false
for b = 1 → a do So, halt would have resolved the Fermat’s last theorem,
for c = 2 → a + b do it took about 300 years for us to assert its correctness. Ok!
if an + bn = cn then
we know FLT is true, are there other applications of the
f lag ← false
would halt solution
a←a+1
Goldbach Conjecture
Cojecture 1. Goldbach Conjecture (1742)? Every even integer n > 2 is the sum of two primes.
The Collatz Conjecture, (also known as wondrous numbers, Algorithm Collatz Program ( integer n)
3n + 1 conjecture, Syracuse problem, Ulam conjecture) is an while n ̸= 1 do
open problem in mathematics and can be expressed as an iter- if n is even then
ative algorithm: n ← n/2
Cojecture 2. Collatz Conjecture (1937) For every integer n else
the Collatz Program eventually reaches 1 (thus halts) n ← 3n + 1
10
n=4 =⇒ 4, 2, 1
n=5 =⇒ 5, 16, 8, 4, 2, 1
n=6 =⇒ 6, 3, 10, 5, 16, 8, 4, 2, 1
n=7 =⇒ 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 15, 8, 4, 2, 1
n=8 =⇒ 8, 4, 2, 1
n=9 =⇒ 9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 15, 8, 4, 2, 1
27, 82, 41, 124, 62, 31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700,
350, 175, 526, 263, 790, 395, 1186, 593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319,
958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308,
1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1
Any other number checked so far, it does actually halts. But we do not have a proof of this. A solution to the
Halting problem will readily resolve this conjecture Despite its simplicity, the Collatz Conjecture has baffled
mathematicians and computer scientists for decades.
For about a month everyone at Yale worked on it, with no result. A similar phenomenon
happened when I mentioned it at the University of Chicago. A joke was made that this
problem was part of a conspiracy to slow down mathematical research in the U.S.
In computer science (in the realm of computable problems), we can roughly categorize researchers into two
main camps: Algorithm Designers and Complexity Theorists. Each of these camps focuses on different but
interconnected aspects of problem-solving with computers.
11
algorithm can even exist for a given problem. Similarly, complexity theorists often use insights from algorithmic
techniques to classify problems.
I am giving briefly some ideas of solving the factorization problem. Suppose x has n digits.
1. Try all factors from 3 to x
12
Step 4: Bob verifies that p × q = N . Based on the values of p and q,
Bob checks whether Alice chose p and q according to the rules (neither
ends in 3 for heads, at least one ends in 3 for tails).
13
8.1 Exponentiation by iterative multiplication
This algorithm clearly is correct and takes n multiplications. This time integer multiplications not 1-digit
multiplications. We can tweak it to save one multiplication by initializing x to a, but be careful what if n = 0.
14
n
/2 n/2
a · a
if n > 1 even
an = a · an−1/2 · an−1/2 if n is odd Algorithm 16 Exponentiation by re-
1 if n = 0 peated squaring
Input: a, n - Integers a and n ≥ 0
Note that when n is even n/2 is an integer and when n is odd, Output: an
(n−1)/2 is an integer, so in both cases we get the same problem 1: function rep-sq-exp(a,n)
(exponentiating one integer to the power of another integer) but 2: if n = 0 then return 1
of smaller size. And smaller powers are supposed to be easy, 3: else if n > 0 and n is even then
really! well at least n = 0 or 1, or sometime even 2. So we 4: z ← rep-sq-expP(a, n/2)
exploit this formula and use recursion. 5: return z ∗ z
Again correctness of this algorithm follows form the above for- 6: else
mula. But you need to prove it using induction, i.e. prove that 7: z ← rep-sq-exp(a, (n − 1)/2)
this algorithm returns an for all positive integers n, prove the 8: return a ∗ z ∗ z
base case using the stopping condition of this function etc.
We will discuss recurrence relation later, but for now if you think about it as follows: Assume n is a power of 2
so it stays even when halve it.
In general we have
j times
z }| {
R(n) = R (n/2j ) + 2 + 2 + ... + 2
It is very easy to argue that in general that is for n not necessarily power of 2 or even, the runtime is something
like 1 + 3 log n.
Exercise: Give a non-recursive implementation of repeated squaring based exponentiation. You can also use
the binary expansion of n
15
9 Matrix and Vectors Arithmetic
In this section, we develop some algorithmic primitives for arithmetic on matrices and vectors. They are building
blocks for various machine learning and data science solutions.
A B
n
X a1 b1
A·B = ai ∗ bi n
a2 b2 P
i=1
..
· ..
= a i bi
. .
i=1
an bn
Dot product is also commonly called an inner product or a scalar product. The a geometric interpretation for
dot product is the following. If u is a unit vector and v is any vector then v · u is the projection of v onto u.
The projection of any point p on v onto u is the point on u closest to p. If v and u are both unit vectors then
v · u is the cos of the angle between the two vectors. So in a way the dot product between two vectors measures
their similarity. It tells us how much of v is in the direction of u.
Runtime of dot-prod How much time does this take? Well that depends on a lot of factors. What machine
you’re running the program on. How many other programs are being run at the same time. What operating
system you’re using. And many other things. So just asking how much time a program takes to terminate
doesn’t really tell us much. So instead we’ll ask a different question. How many elementary operations does
this program performs. Elementary operations are things such as adding or multiplying two 32 bit numbers,
comparing two 32 bit numbers, swapping elements of an array etc. In particular we’re interested in how the
number of elementary operations performed grows with the size of the input.This captures the efficiency of the
algorithm much better.
So with that in mind, let’s analyze this algorithm. What’s the size of the input? A dot product is always
between two vectors. What can change however is the size of these vectors. So that’s our input size. So suppose
the vectors are both n dimensional. Then this code performs n additions and n multiplications. So a total of
2n elementary operations are performed. Can we do any better? Maybe for particular cases we can, when a
vector contains a lot of zeros. But in the general case probably this is as efficient as we can get.
16
9.2 Matrix-Vector Multiplication
Input: Matrix A and vector b
Output: c = A ∗ b
Condition: num columns of A = num rows of b
Am×n × bn×1 = Cm×1
Matrix-vector multiplication only for the case when the number of columns in matrix A equals the number of
rows in vector x. So, if A is an m × n matrix (i.e., with n columns), then the product Ax is defined for n × 1
column vectors x. If we let Ax = b, then b is an m × 1 column vector. Matrix vector multiplication should be
known to everyone, it is explained in the following diagram
(C)ij = Ai · Bj .
17
b11 . . . b1k
B b21 . . . b2k n × k Algorithm 19 Matrix Matrix Multiplication
.. ..
. . Input: A, B - m × n and n × k matrices respec-
bn1 . . . bnk tively given as arrays of appropriate lengths
Output: C = A × B
Dot Product Dot Product 1: function Mat-MatProd(A, B)
A 2: C[ ][ ] ← zeros(m × k)
a11 a12 . . . a1n c11 . . . 3: for i = 1 to m do
a21 a22 . . . a2n ... 4: for j = 1 to k do
a31 a32 . . . a3n ... C
5: C[i][j] ← dot-prod(A[i][:], B[:][j])
.. .. .. .. ..
. . . . .
am1 am2 . . . amn ... 6: return C
m×n m×k
Analysis : How many elementary operations are performed in this algorithm? i goes from 1 to m and for
each value of i, j goes from 1 to k. So the inner loop runs a total of mk times. Each time the inner loop runs we
compute a dot product of two n dimensional vectors. Computing the dot product takes of two n dimensional
vectors takes 2n operations, so the algorithm uses a total of 2mnk operations. Can we do better for this
problem? We defined matrix multiplication in terms of dot products and we said 2n is the minimum number of
operations needed for a dot product, so it would seem we can’t. But in fact there is a better algorithm for this.
For those interested, you can look up Strassen’s Algorithm for matrix multiplication.
b11 . . . b1k
B b21 . . . b2k n × k
.. .. Algorithm 20 Matrix Matrix Multiplication
. .
18