An 𝑚 × 𝑛 chessboard is to be cut into its 𝑚·𝑛 unit squares. At each step, you can make either one horizontal cut or one vertical cut. The first cut will split the board into two sub-boards; after that each cut splits one remaining sub-board into two. The cost of a cut equals the number of unit squares remaining in the smaller of the two resulting sub-boards. For example, a horizontal cut on a 2 × 3 board results in two 1 × 3 sub-boards and costs 3, whereas a vertical cut results in sub-boards of dimensions 2 × 1 and 2 × 2 and costs 2. Costs are additive: the cost of a sequence of cuts equals the sum of their individual costs. Describe an algorithm to compute the minimum total cost of reducing the 𝑚 × 𝑛 board into its unit squares. Prove its correctness and show your analysis of its time complexity.
My solution goes as follows:
1. I follow the greedy approach of checking for the highest between m (row) and n (column) and making a cut.
2. If m is higher I make a vertical cut and other a horizontal cut.
3. This gives me the lowest cut cost in every step.
4. I follow divide and conquer and recursively follow the approach until I have m x n = 1 x 1
This seems to be working but what I am struggling with is to derive the time complexity and proving the correctness of my algorithm.
My expression of time complexity is T(mn) = 2 T(mn/2) + theta(n).
Can someone advice me how I can do this?
The minimum cost is (n-1)*m+(m-1)*n. You can obtain by making m horizontal cuts paying for every one n-1 and n vertical cuts paying for every one m-1. That is O(1) algorithm.
To cut off a one unit square you need to pay at least 2 for it in (n-1)*(m-1) cases, at least 1 in (n-1)+(m-1), and one unit square you can get for free. This bounds overall price from below:
2*(n-1)*(m-1)+1*(n-1)+(m-1)+0*1 = 2*n*m-n-m = (n-1)*m+(m-1)*n
Here is a dynamic programming approach which works in O (m * n * (m + n)).
For each possible size w * h of a sub-rectangle, compute the cost f (w, h) of cutting it into individual squares.
Choose the first cut: w * h is cut either into a * h and b * h, or into w * c and w * d.
The cost of cutting into a * h and b * h is min (a, b) * h + f (a, h) + f (b, h).
The cost of cutting into w * c and w * d is w * min (c, d) + f (w, c) + f (w, d).
Putting all that together, we have
f (w, h) = min of
min {for every a such that 0 < a < w}
(let b = w - a)
min (a, b) * h + f (a, h) + f (b, h)
and
min {for every c such that 0 < c < h}
(let d = h - c)
w * min (c, d) + f (w, c) + f (w, d)
The base is f (1, 1) = 0.
We can store all f (w, h) as a two-dimensional array and calculate them bottom-up in two loops, like this:
for w = 1, 2, ..., m:
for h = 1, 2, ..., n:
calculate f[w][h] by the formula above in O (w + h)
Or write a recursive function with memoization.
Any of the two approaches will work in O (m * n * (m + n)): we have to calculate m * n values, and each one is calculated as a minimum of O (m + n) values.
If the greedy approach actually works (I don't know), it would do so in O (log m + log n).
By intuitive argument, for example, if m = 17 and n = 23, then the following rectangles must be considered:
17 * 23
17 * 11 and 17 * 12
8 * 11 and 8 * 12 and 9 * 11 and 9 * 12
8 * 5 and 8 * 6 and 9 * 5 and 9 * 6
4 * 5 and 4 * 6 and 5 * 5 and 5 * 6
4 * 2 and 4 * 3 and 5 * 2 and 5 * 3
2 * 2 and 2 * 3 and 3 * 2 and 3 * 3
1 * 1 and 1 * 2 and 2 * 1 and 2 * 2
1 * 1 again
As we can see, the rectangles will be of the form (m / 2^x) * (n / 2^y), where x and y are between 0 and log_2 (m + n), and the rounding can go either way.
Related
I have read several answers here on SO and on the web about choosing a good hash table length and that it should be a prime to reduce collisions and to uniformly distribute the keys across the hash table.
Though there are a lot of answers, I couldn't find a satisfying proof, and I didn't understand the explanations I found.
So if we have a key k and a hash table of length n and we do k % n = i to find the index i of a bucket in a hash table, we say that n should be a prime in order to minimize the number of collisions and to better distribute the keys across the hash table.
But why? Here is my attempt to prove it. It is going to be quite long and a bit pedantic, but please bear with me and try to read till the end.
I will start by making the following assumptions:
For every key k of the set of keys K, we can have a k which is either even or odd. A key is an integer, either even (k = 2x) or odd (k = 2x + 1).
For every n we may choose, n also can either be even (n = 2y) or odd (n = 2y + 1).
If we add an even number to another even number, we get an even number (2x + 2y = 2(x + y)). Likewise, if we add an odd number to another odd number, we still get an even number
((2x + 1) + (2y + 1) = 2x + 1 + 2y + 1 = 2x + 2y + 2 = 2(x + y + 1)).
If we add an odd number to an even number (same as adding an even number to an odd one), we always get an odd number ((2x + 1) + 2y = 2x + 1 + 2y = 2(x + y) + 1).
First of all, let's try to think about using an n which is not a prime, so maybe we will find out that these numbers are not good enough to be used as the length of a hash table (assuming that the keys share some patterns, e.g. like being all even or all odd).
Let's assume that n is even, i.e. n = 2y. In this case we have 2 scenarios: our keys k of K can be even (1.1.) or odd (1.2.).
1.1. n = 2y is even, keys are even k = 2x
For k = 2x and n = 2y, we have: k % n = 2x % 2y = i.
In this case we can say that if both the key k and hash table length n are even,
then i is also going to always be even.
Why? Because if we take the quotient by the integer division k // n = 2x // 2y = q, we get a quotient q such that:
k = 2x = (n * q) + i = (2y * q) + i = 2yq + i
Since 2yq (2y * q) is an even number, in order to satisfy 2x = 2yq + i the remainder i is always going to be even because 2x is even (even + even = even). If i were odd, we would get an odd number (even + odd = odd), but again 2x is even.
This leads to the following issue if we choose n to be even: if all our ks are even, then they will always end up in a bucket at an even index, increasing the number of collisions and clustering because only half n / 2 of the length of our hash table (only the even indices) will be occupied.
Therefore it's not a good idea to use an even number for n if all our ks or the majority of our ks are going to be even.
1.2. n = 2y is even, keys are odd k = 2x + 1
For k = 2x + 1 and n = 2y, we have: k % n = (2x + 1) % 2y = i.
Likewise, in this case if all of our ks (or the majority of them) are going to be odd, we end up in this situation:
k = 2x + 1 = (n * q) + i = (2y * q) + i = 2yq + i
Since 2yq is even, in order to get an odd k = 2x + 1, i is always going to be odd (even + odd = odd).
Again, choosing an even n as the hash table length is a bad idea even if all or the majority of our ks are odd, because we will end up with only the odd indices (buckets) being occupied.
So let's try with an n which is not an even number, i.e. an odd n = 2y + 1.
Let's assume that n is odd, i.e. n = 2y + 1. We still have even (2.1.) and odd (2.2.) keys (k of K).
2.1. n = 2y + 1 is odd, keys are even k = 2x
Here we have:
k = 2x = (n * q) + i = ((2y + 1) * q) + i = (2yq + q) + i = 2yq + q + i
We know that 2yq is even, so in order to get k = 2x which is even as well we need q + i to also be even.
When can q + i be even? Only in these 2 cases:
q -> even, i -> even, even + even = even
q -> odd, i -> odd, odd + odd = even
If either q or i is even while the other one is odd, we will get an odd q + i, and consequently an odd 2yq + (q + i), but we have k = 2x which is even, so either both q and i are even or they're both odd.
In this case we can see that for an odd n = 2y + 1, i can either be even or odd, which is good because it means that now we will use both even and odd bucket indices of our hash table and not only the even or only the odd ones.
By the way, it turns out that all primes p : p > 2 are odd numbers, so at least for now we can say that choosing a prime could be a good idea because a prime greater than 2 is always odd.
2.2. n = 2y + 1 is odd, keys are odd k = 2x + 1
Similarly here:
k = 2x + 1 = (n * q) + i = ((2y + 1) * q) + i = 2yq + q + i = 2yq + (q + i)
In order to get an odd k = 2x + 1 we need (q + i) to be odd (2yq is even), and this happens only in these 2 cases:
q -> even, i -> odd, even + odd = odd
q -> odd, i -> even, odd + even = odd
Again, we prove that an odd number is a better choice for n as this way we have the chance that both even and odd bucket's indices i are going to be occupied.
Now, I got stuck here. Is there a connection between this proof and prime numbers and how can I continue this proof to conclude that a prime number p would be an even better choice than a generic odd number with a similar reasoning?
EDIT:
So I tried to reason about it a bit further. This is what I came up with:
3. Using a generic odd n sharing a common factor f with k
We can say that for any factor f which is shared across k (k = f * x = fx) and n (n = f * y = fy), we end up with an i = k % n also sharing that common factor f. Why?
Again, if we try to compute k:
k = fx = (n * q) + i = (fy * q) + i = fyq + i
Then:
k = fx = fyq + i
Can only be satisfied if and only if i also shares f as one of its factors, e.g. i = f * g = fg:
k = fx = fyq + fg = f(yq + g)
Leading to yq + g = x.
This means that if both k and n share a common factor, then the result of the modulo i will also have that common factor and therefore i will always be a multiple of that common factor, e.g. for k of K = {12, 15, 33, 96, 165, 336} and n = 9 (an odd number, not a prime):
k | k % n
---------------------------
12 | 12 % 9 = 3
15 | 15 % 9 = 6
33 | 33 % 9 = 6
96 | 96 % 9 = 6
165 | 165 % 9 = 3
336 | 336 % 9 = 3
Both k and n always share a common factor (3 in this case).
This leads to i = k % n also being a multiple of 3 and therefore, again in such scenarios the hash table's bucket indices being used will only be those that are multiples of the common factor 3.
So while an odd number for n is definitely better than an even one (as explained at 2.1. and 2.2), we still may have unwanted patterns in numbers when k and n both share a common factor f.
So, if we make n a prime (n = p), we will certainly avoid that n shares that common factor f with k (provided that f != p), because a prime p can only have two factors: 1 and itself. So...
4. Using a prime for n
If n is a prime (n = p), we end up with:
k = fx = (q * p) + i = qp + i
Then:
k = fx = qp + i
Implies that the quotient q resulting from the integer division k // n can either share the common factor f or not, i.e.:
q = fz
Or:
q = z
In the first case (q = fz) we have:
k = fx = (q * p) + i = (fz * p) + i = fzp + i
So i ends up sharing the common factor f as well, e.g. i = fg:
k = fx = (q * p) + i = (fz * p) + i = fzp + i = fzp + fg = f(zp + g)
Such that zp + g = x.
And in the second case (q = z), we have:
k = fx = (q * p) + i = (z * p) + i = zp + i = zp + i
i.e. in this second case, i won't have f as one of its factors, as zp doesn't have f among its factors too.
So when using a prime for n, the benefit is that the result for i = k % n can either share a common factor f with k or not share it at all, e.g. for k of K = {56, 64, 72, 80, 88, 96} and n = p = 17:
k | k % n
---------------------------
56 | 56 % 17 = 5
64 | 64 % 17 = 13
72 | 72 % 17 = 4 ---> Common factor f = 4 of k and i
80 | 80 % 17 = 12 ---> Common factor f = 4 of k and i
88 | 88 % 17 = 3
96 | 96 % 17 = 11
In this case, all ks share a common factor f = 4, but only i = 72 % 17 = 4 and i = 80 % 17 = 12 both have k and i sharing that common factor f:
72 % 17 = 4 -> (18 * 4) % 17 = (4 * 1)
80 % 17 = 12 -> (20 * 4) % 17 = (4 * 3)
Also, if we take the previous example, for k of K = {12, 15, 33, 96, 165, 336} and we use the prime 17 for n instead of 9, we get:
k | k % n
---------------------------
12 | 12 % 17 = 12
15 | 15 % 17 = 15
33 | 33 % 17 = 16
96 | 96 % 17 = 11
165 | 165 % 17 = 12
336 | 336 % 17 = 13
Even here, we see that the common factor f = 3 in this case is shared between both k and n only in these 3 cases:
12 % 17 = 12 -> (4 * 3) % 17 = (4 * 3)
15 % 17 = 15 -> (5 * 3) % 17 = (5 * 3)
165 % 17 = 12 -> (55 * 3) % 17 = (4 * 3)
This way, using a prime, the probability for a collision has decreased, and we can distribute the data across the hash table better.
Now, what happens if even k is a prime, or at least a multiple of a prime? I think that in this case the distribution along the hash table would be even better, because there won't be any common factors between k and n if they are both primes or if k is a multiple of a prime, provided that k is not a multiple of the prime n.
This is my conclusion why a prime is better suited for the length of a hash table.
Would appreciate to receive your feedback and thoughts regarding my way of understanding this topic.
Thank you.
When it comes to chaining hash tables, you pretty much have the answer, although it can be written in fewer words:
Data often has patterns. Memory addresses, for example, often have zero in the lower bits.
Many hash functions, especially the very popular polynomial hash functions, are built using only addition, subtraction, and multiplication. All of these operations have the property that the lowest n bits of the result depend only on the lowest n bits of the operands, so these hash functions have this property too.
If your table size is zero in the lowest n bits, and the data is all the same in the lowest n bits, and your hash function has the property mentioned above... then your hash table will only use one out of every 2n of its slots.
Sometimes we fix this problem by choosing an odd hash table size. Prime sizes are better, because each small factor of the table size causes similar problems with different arithmetic progressions in hash values.
Sometimes, though, we fix this problem by adding an additional hash step to the hash table itself -- an additional hash step that mixes all the bits of the hash together and prevents this kinds of problems. Java HashMap uses tables with size 2N, but does this extra mixing step to help cover all the slots.
That's for chaining hash tables.
For hash tables that use open addressing to resolve collisions, the choice of a prime table size is usually required to ensure that the probing scheme will eventually check all (or at least half of) the slots. This is required to guarantee that the table will work at least until it is (half) full.
The problem is as follows:
We have 3 integer values as input: n, i, j
Suppose we have n × n matrix, that is filled with numbers from 1 to n² in clockwise spiral way. Find the number at the index (i, j).
I know how to construct such matrix, I can solve it by filling the matrix and looking at the index (i, j), and I consider such solution a bit too "brute force". I believe there should be some mathematical relationship between the number n, the indices i, j and the number sitting at that cell. I've tried some approaches but couldn't find a way to do it. Any suggestions to help me in my attempt?
Edit: an example 5x5 matrix:
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
You can do it with some basic arithmetic (assuming 0-based indexing, i stands for the row and j for the column):
Find the ring in which the number is in. It is r = min(i, j, n - i - 1, n - j - 1). This is counting the rings from outer to inner. If we count from inside to outside (which will come in handy later), then we get q = (n - 1) / 2 - r for odd n or q = (n - 2) / 2 - r for even n. Or generalized: q = (n - 2 + n % 2) / 2 - r which is the same as q = (n - 1) / 2 - r for integer division (as mentioned by #Stef).
It is not that hard to see that the number of elements covered by a ring (including the numbers inside of it) from most inner going outwards is 1^2, 3^2, 5^2, ... if n is odd and 2^2, 4^2, 6^2, ... if n is even. So the side length of the square covered by ring q is (in generalized form) m = q * 2 + 2 - n % 2. This means the element in the upper left corner of the ring is b = n^2 - m^2 + 1.
Get the number:
If r == i: b + j - r (element is on top side).
If r == n - j - 1: b + m - 1 + i - r (element is on the right side)
If r == n - i - 1: b + 2 * m - 2 + n - j - 1 - r (element is on the bottom)
Otherwise (r == j): b + 3 * m - 3 + n - i - 1 - r (element is on the left side)
This is O(1). The formulas can be simplified, but then the explanation would be harder to understand.
Example: n = 5, i = 3, j = 2
r = min(2, 3, 1, 2) = 1, q = (3 + 1) / 2 - 1 = 1
m = 2 + 2 - 1 = 3, b = 25 - 9 + 1 = 17
Third condition applies: 17 + 6 - 2 + 5 - 2 - 1 - 1 = 22
The question is, given a number D and a sequence of numbers with amount N, find the amount of the combinations of three numbers that have a highest difference value within it that does not exceed the value D. For example:
D = 3, N = 4
Sequence of numbers: 1 2 3 4
Possible combinations: 1 2 3 (3-1 = 2 <= D), 1 2 4 (4 - 1 = 3 <= D), 1 3 4, 2 3 4.
Output: 4
What I've done: link
Well my concept is: iterate through the whole sequence of numbers and find the smallest number that exceeds the D value when subtracted to the current compared number. Then, find the combinations between those two numbers with the currently compared number being a fixed value (which means combination of n [numbers between the two numbers] taken 2). If even the biggest number in the sequence subtracted with the currently compared number does not exceed D, then use a combination of the whole elements taken 3.
N can be as big as 10^5 with the smallest being 1 and D can be as big as 10^9 with the smallest being 1 too.
Problem with my algorithm: overflow occurs when I do a combination of the 1st element and 10^5th element. How can I fix this? Is there a way to calculate that large amount of combination without actually doing the factorials?
EDIT:
Overflow occurs when worst case happens: currently compared number is still in index 0 while all other numbers, when subtracted with the currently compared number, is still smaller than D. For example, the value of number at index 0 is 1, the value of number at index 10^5 is 10^5 + 1 and D is 10^9. Then, my algorithm will attempt to calculate the factorial of 10^5 - 0 which then overflows. The factorial will be used to calculate the combination of 10^5 taken 3.
When you seek for items in value range D in sorted list, and get index difference M, then you should calculate C(M,3).
But for such combination number you don't need to use huge factorials:
C(M,3) = M! / (6 * (M-3)!) = M * (M-1) * (M-2) / 6
To diminish intermediate results even more:
A = (M - 1) * (M - 2) / 2
A = (A * M) / 3
You didn't add the C++ tag to your question, so let me write the answer in Python 3 (it should be easy to translate it to C++):
N = int(input("N = "))
D = int(input("D = "))
v = [int(input("v[{}] = ".format(i))) for i in range (0, N)]
count = 0
i, j = 0, 1
while j + 1 < N:
j += 1
while v[j] - v[i] > D:
i += 1
d = j - i
if d >= 2:
count += (d - 1) * d // 2 # // is the integer division
print(count)
The idea is to move up the upper index of the triples j, while dragging the lower index i at the greatest distance j-i=d where v[j]-v[i]<=D. For each i-j pair, there are 1+2+3+...+d-1 possible triples keeping j fixed, i.e., (d-1)*d/2.
Introduction: Infix products for a group
Suppose I have a group
G = (G, *)
and a list of elements
A = {0, 1, ..., n} ⊂ ℕ
x : A -> G
If our goal is to implement a function
f : A × A -> G
such that
f(i, j) = x(i) * x(i+1) * ... * x(j)
(and we don't care about what happens if i > j)
then we can do that by pre-computing a table of prefixes
m(-1) = 1
m(i) = m(i-1) * x(i)
(with 1 on the right-hand side denoting the unit of G) and then implementing f as
f(i, j) = m(i-1)⁻¹ * m(j)
This works because
m(i-1) = x(0) * x(1) * ... * x(i-1)
m(j) = x(0) * x(1) * ... * x(i-1) * x(i) * x(i+1) * ... * x(j)
and so
m(i)⁻¹ * m(j) = x(i) * x(i+1) * ... * x(j)
after sufficient reassociation.
My question
Can we rescue this idea, or do something not much worse, if G is only a monoid, not a group?
For my particular problem, can we do something similar if G = ([0, 1] ⊂ ℝ, *), i.e. we have real numbers from the unit line, and we can't divide by 0?
Yes, if G is ([0, 1] ⊂ ℝ, *), then the idea can be rescued, making it possible to compute ranged products in O(log n) time (or more accurately, O(log z) where z is the number of a in A with x(a) = 0).
For each i, compute the product m(i) = x(0)*x(1)*...*x(i), ignoring any zeros (so these products will always be non-zero). Also, build a sorted array Z of indices for all the zero elements.
Then the product of elements from i to j is 0 if there's a zero in the range [i, j], and m(j) / m(i-1) otherwise.
To find if there's a zero in the range [i, j], one can binary search in Z for the smallest value >= i in Z, and compare it to j. This is where the extra O(log n) time cost appears.
General monoid solution
In the case where G is any monoid, it's possible to do precomputation of n products to make an arbitrary range product computable in O(log(j-i)) time, although its a bit fiddlier than the more specific case above.
Rather than precomputing prefix products, compute m(i, j) for all i, j where j-i+1 = 2^k for some k>=0, and 2^k divides both i and j. In fact, for k=0 we don't need to compute anything, since the values of m(i, i+1) is simply x(i).
So we need to compute n/2 + n/4 + n/8 + ... total products, which is at most n-1 things.
One can construct an arbitrary interval [i, j] from at O(log_2(j-i+1)) of these building blocks (and elements of the original array): pick the largest building block contained in the interval and append decreasing sized blocks on either side of it until you get to [i, j]. Then multiply the precomputed products m(x, y) for each of the building blocks.
For example, suppose your array is of size 10. For example's sake, I'll assume the monoid is addition of natural numbers.
i: 0 1 2 3 4 5 6 7 8 9
x: 1 3 2 4 2 3 0 8 2 1
2: ---- ---- ---- ---- ----
4 6 5 8 3
4: ----------- ----------
10 13
8: ----------------------
23
Here, the 2, 4, and 8 rows show sums of aligned intervals of length 2, 4, 8 (ignoring bits left over if the array isn't a power of 2 in length).
Now, suppose we want to calculate x(1) + x(2) + x(3) + ... + x(8).
That's x(1) + m(2, 3) + m(4, 7) + x(8) = 3 + 6 + 13 + 2 = 24.
Suppose we have a matrix of size NxN of numbers where all the rows and columns are in increasing order, and we want to find if it contains a value v. One algorithm is to perform a binary search on the middle row, to find the elements closest in value to v: M[row,col] < v < M[row,col+1] (if we find v exactly, the search is complete). Since the matrix is sorted we know that v is larger than all elements in the sub-matrix M[0..row, 0..col] (the top-left quadrant of the matrix), and similarly it's smaller than all elements in the sub-matrix M[row..N-1, col+1..N-1] (the bottom right quadrant). So we can recursively search the top right quadrant M[0..row-1, col+1..N-1] and the bottom left quadrant M[row+1..N-1, 0..col].
The question is what is the complexity of this algorithm ?
Example: Suppose we have the 5x5 matrix shown below and we are searching for the number 25:
0 10 20 30 40
1 11 21 31 41
2 12 22 32 42
3 13 23 33 43
4 14 24 34 44
In the first iteration we perform binary search on the middle row and find the closest element which is smaller than 25 is 22 (at row=2 col=2). So now we know 25 is larger than all items in the top-left 3x3 quadrant:
0 10 20
1 11 21
2 12 22
Similary we know 25 is smaller than all elements in the bottom right 3x2 quadrant:
32 42
33 43
34 44
So, we recursively search the remaining quadrants - the top right 2x2:
30 40
31 41
and the bottom left 2x3:
3 13 23
4 14 24
And so on. We essentially divided the matrix into 4 quadrants (which might be of different sizes depending on the result of the binary search on the middle row), and then we recursively search two of the quadrants.
The worst-case running time is Theta(n). Certainly this is as good as it gets for correct algorithms (consider an anti-diagonal, with elements less than v above and elements greater than v below). As far as upper bounds go, the bound for an n-row, m-column matrix is O(n log(2 + m/n)), as evidenced by the correct recurrence
m-1
f(n, m) = log m + max [f(n/2, j) + f(n/2, m-1 - j)],
j=0
where there are two sub-problems, not one. This recurrence is solvable by the substitution method.
?
f(n, m) ≤ c n log(2 + m/n) - log(m) - 2 [hypothesis; c to be chosen later]
m-1
f(n, m) = log m + max [f((n-1)/2, j) + f((n-1)/2, m-j)]
j=0
m-1
≤ log m + max [ c (n/2) log(2 + j/(n/2)) - log(j) - 2
+ c (n/2) log(2 + (m-j)/(n/2))] - log(m-j) - 2]
j=0
[fixing j = m/2 by the concavity of log]
≤ log m + c n log(2 + m/n) - 2 log(m/2) - 4
= log m + c n log(2 + m/n) - 2 log(m) - 2
= c n log(2 + m/n) - log(m) - 2.
Set c large enough that, for all n, m,
c n log(2 + m/n) - log(m) - 2 ≥ log(m),
where log(m) is the cost of the base case n = 1.
If you find your element after n steps, then the searchable range has size N = 4^n. Then, time complexity is O(log base 4 of N) = O(log N / log 4) = O(0.5 * log N) = O(log N).
In other words, your algorithm is two times faster then binary search, which is equal to O(log N)
A consideration on binary search on matrices:
Binary search on 2D matrices and in general ND matrices are nothing different than binary search on sorted 1D vectors. Infact C for instance store them in row-major fashion(as concat of rows from: [[row0],[row1]..[rowk]]
This means one can use the well-known binary search on matrix as following (with complexity log(n*m)):
template<typename T>
bool binarySearch_2D(T target,T** matrix){
int a=0;int b=NCELLS-1;//ROWS*COLS
bool found=false;
while(!found && a <= b){
int half=(a+b)/2;
int r=half/COLS;
int c=half-(half/COLS)*COLS;
int v =matrix[r][c];
if(v==target)
found=true;
else if(target > v)
a=half+1;
else //target < v
b=half-1;
}
return found;
}
The complexity of this algorithm will be -:
O(log2(n*n))
= O(log2(n))
This is because you are eliminating half of the matrix in one iteration.
EDIT -:
Recurrence relation -:
Assuming n to be the total number of elements in the matrix,
=> T(n) = T(n/2) + log(sqrt(n))
=> T(n) = T(n/2) + log(n^(1/2))
=> T(n) = T(n/2) + 1/2 * log(n)
Here, a = 1, b = 2.
Therefore, c = logb(a) = log2(1) = 0
=> n^c = n^0
Also, f(n) = n^0 * 1/2 * log(n)
According to case 2 of Master Theorem,
T(n) = O((log(n))^2)
You can use a recursive function and apply the master theorem to find the complexity.
Assume n is the number of elements in the matrix.
Cost for one step is binary search on sqrt(n) elements and you get two problems, in worst case same size each with n/4 elements: 2*T(n/4). So we have:
T(n)=2*T(n/4)+log(sqrt(n))
equal to
T(n)=2*T(n/4)+log(n)/2
Now apply master theorem case 1 (a=2, b=4, f(n)=log(n)/2 and f(n) in O(n^log_b(a))=O(n^(1/2)) therefore we have case 1)
=> Total running time T(n) is in O(n^(a/b)) = O(n^(1/2))
or equal to
O(sqrt(n))
which is equal to height or width of the matrix if both sides are the same.
Let's assume that we have the following matrix:
1 2 3
4 5 6
7 8 9
Let's search for value 7 using binary search as you specified:
Search nearest value to 7 in middle row: 4 5 6, which is 6.
Hmm we have a problem, 7 is not in the following submatrix:
6
9
So what to do? One solution would be to apply binary search to all rows, which has a complexity of nlog(n). So walking the matrix is a better solution.
Edit:
Recursion relation:
T(N*N) = T(N*N/2) + log(N)
if we normalize the function to one variable with M = N^2:
T(M) = T(M/2) + log(sqrt(M))
T(M) = T(M/2) + log(M)/2
According to Master Theorem Case #2, complexity is
(log(M))^2
=> (2log(N))^2
=> (log(N))^2
Edit 2:
Sorry I answered your question from my mobile, now when you think about it, M[0...row-1, col+1...N-1] doesn't make much sense right? Consider my example, if you search for a value that is smaller than all values in the middle row, you'll always end up with the leftmost number. Similarly, if you search for a value that is greater than all values in the middle row, you'll end up with the rightmost number. So the algorithm can be reworded as follows:
Search middle row with custom binary search that returns 1 <= idx <= N if found, idx = 0 or idx = N+1 if not found. After binary search if idx = 0, start the search in the upper submatrix: M[0...row][0...N].
If the index is N + 1 start the search in the lower submatrix: M[row+1...N][0...N]. Otherwise, we are done.
You suggest that complexity should be: 2T(M/4) + log(M)/2. But at each step, we divide the whole matrix by two and only process one of them.
Moreover, if you agree that T(N*N) = T(N*N/2) + log(N) is correct, than you can substitute all N*N expressions with M.