Advanced Algorithms Problems ("Nice Triangle"): Prime number Pyramid where every number depends on numbers above it - algorithm

I'm currently studying for an advanced algorithms and datastructures exam, and I simply can't seem to solve one of the practice-problems which is the following:
1.14) "Nice Triangle"
A "nice" triangle is defined in the following way:
There are three different numbers which the triangle consists of, namely the first three prime numbers (2, 3 and 5).
Every number depends on the two numbers below it in the following way.
Numbers are the same, resulting number is also the same. (2, 2 => 2)
Numbers are different, resulting number is the remaining number. (2, 3 => 5)
Given an integer N with length L, corresponding to the base of the triangle, determine the last element at the top
For example:
Given N = 25555 (and thus L = 5), the triangle looks like this:
2
3 5
2 5 5
3 5 5 5
2 5 5 5 5
=> 2 is the result of this example
What does the fact that every number is prime have to do with the problem?
By using a naive approach (simply calculating every single row), one obtains a time-complexity of O(L^2).
However, the professor said, it's possible with O(L), but I simply can't find any pattern!!!

I'm not sure why this problem would be used in an advanced algorithms course, but yes, you can do this in O(l) = O(log n) time.
There are a couple ways you can do it, but they both rely on recognizing that:
For the problem statement, it doesn't matter what digits you use. Lets use 0, 1, and 2 instead of 2, 3, and 5. Then
If a and b are the input numbers and c is the output, then c = -(a+b) mod 3
You can build the whole triangle using c = a+b mod 3 instead, and then just negate every second row.
Now the two ways you can do this in O(log n) time are:
For each digit d in the input, calculate the number of times (call it k) that it gets added into the final sum, add up all the kd mod 3, and then negate the result if you started with an even number of digits. That takes constant time per digit. Alternatively:
recognize that you can do arithmetic on n-sized values in constant time. Make a value that is a bit mask of all the digits in n. That takes 2 bits each. Then by using bitwise operations you can calculate each row from the previous one in constant time, for O(log n) time altogether.
Here's an implementation of the 2nd way in python:
def niceTriangle(n):
# a vector of 3-bit integers mod 3
rowvec = 0
# a vector of 1 for each number in the row
onevec = 0
# number of rows remaining
rows = 0
# mapping for digits 0-9
digitmap = [0, 0, 0, 1, 1, 2, 2, 2, 2, 2]
# first convert n into the first row
while n > 0:
digit = digitmap[n % 10]
n = n//10
rows += 1
onevec = (onevec << 3) + 1
rowvec = (rowvec << 3) + digit
if rows%2 == 0:
# we have an even number of rows -- negate everything
rowvec = ((rowvec&onevec)<<1) | ((rowvec>>1)&onevec)
while rows > 1:
# add each number to its neighbor
rowvec += (rowvec >> 3)
# isolate the entries >= 3, by adding 1 to each number and
# getting the 2^2 bit
gt3 = ((rowvec + onevec) >> 2) & onevec
# subtract 3 from all the greater entries
rowvec -= gt3*3
rows -= 1
return [2,3,5][rowvec%4]

Related

Split sequence of numbers from 1 to n^2 in n subsequences so they all have the same sum

Given the number n and a sequence of numbers from 1 to n^2 how to split it in n subsequences so all of the subsequences have the same sum and length of n ?
For example if n = 3 answer could be:
3 4 8 = 15
2 6 7 = 15
1 5 9 = 15
So I feel this problem can be solved by making few observations to the problem.
For example, let's say we have n=3. Then n^2=9.
Now total sum of all the numbers from 1 to 9 = 9 * (9+1) / 2 = 45.
So, now we can split 45 into three equal groups each having sum = 45/3 = 5.
Similarly:-
n = 4, sum of 1 to 16 numbers = 16 * 17/2 = 136. each group sum = 136/4 = 34.
n = 5, sum of 1 to 25 numbers = 25 * 26/2 = 25*13. each group sum = 25*13/5 = 65.
Now, we know what should be sum of each set of groups in order to split numbers into n sub sequences.
Now Another observation that we make is whether our n is odd or even.
For n being even, the splitting it very easy.
n = 2, so we have numbers 1 to 4.
1 4
2 3.
Let's assume a matrix of n x n , in above case it will be 2 x 2.
Rules for even n:-
1. Keep a counter = 1.
2. Fill the first column (1 to n), incrementing the counter by 1.
3. When we reach at the bottom of the column, for column 2, we do a reverse iteration (n to 1) and fill them with counter by incrementing it by 1.
You can verify this technique will work by taking n=2,4,6 ... and filling the array.
Now let's see how to fill this matrix n x n for n odd.
Rules for odd n:-
1. Keep a counter = 1.
2. Fill the first column (1 to n), incrementing the counter by 1.
3. Now this case is slightly different from even case, from the next column onwards,
we don't reverse our calculation from n to 1 but we keep moving ahead in column.
Let's understand this step by looking at an example.
Let's take n=3.
Our first column will be 1,2,3.
Now for the second column we start at bottom column which is n in our example it's 3.
Fill the n = 3 with value 4. next row value = (n+1)%n = 0, which gets 5, next row = (n+1+1)%n = 1 , which gets value 6. Now all the column 2 values are filled, let's move onto next column i.e third.
We will start at row = 1 , so row 1 column 3 will get 7, then row 2 column 3 will get 8 and then row 0 column 3 will get 9.
Hope this helps!

Simple math task: 2 numbers given, we have to find the third. I just need a formula for it

I need a formula for counting the number of combinations within a given limit of numbers. There must only be 2 numbers given, we have to find the third.
For example, for 2(number of repetitions) and 3(limit number), the result would be 3, because there are 3 combinations for the digits: 1 and 2, 1 and 3, 2 and 3.
For 2 and 4 the result is 6,
For 3 and 5 the result is 10,
For 6 and 7 the result is 7, etc.
The first number has to be smaller than the second.
A formula is needed for figuring out the result, if the first number is A, the second is B, what would C is going to be?
You're describing combination. The formula is going to be C = B! / (A!*(B-A)!) (where ! is the factorial operation). It's also worth noting that the first number can be equal to the second -- there should only be one repetition in that case. By convention 0! == 1 and it is OK where both numbers are equal because C(n, n) = 1 and this means n!/(n! * 0!).
Unfortunately, since factorial grows very quickly (21! is too large for a 64-bit unsigned integer), you probably can't compute this directly. Wikipedia has a few algorithms you can use here.

Divide n into x random parts

What I need to achieve is basically x dice rolls = n sum but backwards.
So let's create an example:
The dice has to be rolled 5 times (min. sum 5, max. sum 30) which means:
x = 5
Let's say in this case the sum that was rolled is 23 which means:
n = 23
So what I need is to get the any of the possible single dice roll combinations (e.g. 6, 4, 5, 3, 5)
What I could make up in my mind so far is:
Create 5 random numbers.
Add them up and get the sum.
Now divide every single random number by the sum and multiply by the wanted number 23.
The result is 5 random numbers that equal the wanted number 23.
The problem is that this one returns random values (decimals, values below 1 and above 6) depending on the random numbers. I can not find a way to edit the formula to only return integers >= 1 or <= 6.
If you don't need to scale it up by far the easiest way is to re-randomize it until you get the right sum. It takes milliseconds on any modern cpu. Not pretty tho.
#!/usr/local/bin/lua
math.randomseed(os.time())
function divs(n,x)
local a = {}
repeat
local s = 0
for i=1,x do
a[i] = math.random(6)
s = s + a[i]
end
until s==n
return a
end
a = divs(23,5)
for k,v in pairs(a) do print(k,v) end
This was an interesting problem. Here's my take:
EDIT: I missed the fact that you needed them to be dice rolls. Here's a new take. As a bonus, you can specify the number of sides of the dices in an optional parameter.
local function getDiceRolls(n, num_rolls, num_sides)
num_sides = num_sides or 6
assert(n >= num_rolls, "n must be greater than num_rolls")
assert(n <= num_rolls * num_sides, "n is too big for the number of dices and sides")
local rolls = {}
for i=1, num_rolls do rolls[i] = 1 end
for i=num_rolls+1, n do
local index = math.random(1,num_rolls)
while rolls[index] == num_sides do
index = (index % num_rolls) + 1
end
rolls[index] = rolls[index] + 1
end
return rolls
end
-- tests:
print(unpack(getDiceRolls(21, 4))) -- 6 4 6 5
print(unpack(getDiceRolls(21, 4))) -- 5 5 6 5
print(unpack(getDiceRolls(13, 3))) -- 4 3 6
print(unpack(getDiceRolls(13, 3))) -- 5 5 3
print(unpack(getDiceRolls(30, 3, 20))) -- 9 10 11
print(unpack(getDiceRolls(7, 7))) -- 1 1 1 1 1 1 1
print(unpack(getDiceRolls(7, 8))) -- error
print(unpack(getDiceRolls(13, 2))) -- error
If the # of rolls does not change wildly, but the sum does, then it would be worth creating a lookup table for combinations of a given sum. You would generate every combination, and for each one compute the sum, then add the combination to a list associated to that sum. The lookup table would look like this:
T = {12 = {{1,2,3,4,2},{2,5,3,1,1},{2,2,2,3,3}, ...}, 13=....}
Then when you want to randomly select a combo for n=23, you look in table for key 23, the list has all combos with that sum, now just randomly pick one of them. Same for any other number.

Most efficient way to add individual digits of a number

I am working on an algorithm to determine whether a given number is prime and came across this website. But then I though of trying my own logic. I can easily eliminate numbers ending in 2,4,5,6,8 (and 0 for numbers above 5), so I am left with 1,3,7 and 9 as the possible last digit. Now, if the last digit is 3, I can add up the individual digits to check if it is divisible by 3. I don't want to perform modulus(%) operation and add them. Is there a much more efficient way to sum the digits in a decimal number? Maybe using bitwise operations... ?
% or modulus operator would be faster than adding individul digits. But if you really want to do this, you can unroll your loop partly in such a way that multiples of 3 are escaped automatically.
For ex:
2 is prime
3 is prime
candidate = 5
while(candidate <= limit - 2 * 3) // Unrolling loop for next 2 * 3 number
{
if ( CheckPrime(candidate) ) candidate is prime;
candidate += 2;
if ( CheckPrime(candidate) ) candidate is prime;
candidate += 4; // candidate + 2 is multiple of 3 (9, 15, 21 etc)
}
if(candidate < limit) CheckPrime(candidate);
In above method we are eliminating multiples of 3 instead of checking the divisibility of 3 by adding the digits.
You had a good observation. Incidentally it is called wheel factorization to find prime. I have done for wheel size = 6 (2*3), but you can do the same for larger wheel size also, for ex: 30(2*3*5). The snippet above is also called as all prime number are of type 6N±1.
(because 6N+3 is multiple of 3)
p.s. Not all numbers ending at 2 and 5 are composite. Number 2 and 5 are exceptions.
You might consider the following but i think modulus is fastest way :-
1. 2^n mod 3 = 1 if n is even and = 2 if n is odd
2. odd bits and even bits cancel each out as their sum is zero modulo 3
4. so the absolute difference of odd and even bits is the remainder
5. As difference might be again greater than 3 you need to again calculate modulo 3
6. step 5 can be done recursively
Pseudo code :-
int modulo3(int num) {
if(num<3)
return num;
int odd_bits = cal_odd(num);
int even_bits = cal_even(num);
return module3(abs(even_bits-odd_bits));
}

Dynamic programming: can interval of even 1's and 0's be found in linear time?

Found the following inteview q on the web:
You have an array of
0s and 1s and you want to output all the intervals (i, j) where the
number of 0s and numbers of 1s are equal. Example
pos = 0 1 2 3 4 5 6 7 8
0 1 0 0 1 1 1 1 0
One interval is (0, 1) because there the number
of 0 and 1 are equal. There are many other intervals, find all of them
in linear time.
I think there is no linear time algo, as there may be n^2 such intervals.
Am I right? How can I prove that there are n^2 such ?
This is the fastest way I can think of to do this, and it is linear to the number of intervals there are.
Let L be your original list of numbers and A be a hash of empty arrays where initially A[0] = [0]
sum = 0
for i in 0..n
if L[i] == 0:
sum--
A[sum].push(i)
elif L[i] == 1:
sum++
A[sum].push(i)
Now A is essentially an x y graph of the sum of the sequence (x is the index of the list, y is the sum). Every time there are two x values x1 and x2 to an y value, you have an interval (x1, x2] where the number of 0s and 1s is equal.
There are m(m-1)/2 (arithmetic sum from 1 to m - 1) intervals where the sum is 0 in every array M in A where m = M.length
Using your example to calculate A by hand we use this chart
L # 0 1 0 1 0 0 1 1 1 1 0
A keys 0 -1 0 -1 0 -1 -2 -1 0 1 2 1
L index -1 0 1 2 3 4 5 6 7 8 9 10
(I've added a # to represent the start of the list with an key of -1. Also removed all the numbers that are not 0 or 1 since they're just distractions) A will look like this:
[-2]->[5]
[-1]->[0, 2, 4, 6]
[0]->[-1, 1, 3, 7]
[1]->[8, 10]
[2]->[9]
For any M = [a1, a2, a3, ...], (ai + 1, aj) where j > i will be an interval with the same number of 0s as 1s. For example, in [-1]->[0, 2, 4, 6], the intervals are (1, 2), (1, 4), (1, 6), (3, 4), (3, 6), (5, 6).
Building the array A is O(n), but printing these intervals from A must be done in linear time to the number of intervals. In fact, that could be your proof that it is not quite possible to do this in linear time to n because it's possible to have more intervals than n and you need at least the number of interval iterations to print them all.
Unless of course you consider building A is enough to find all the intervals (since it's obvious from A what the intervals are), then it is linear to n :P
A linear solution is possible (sorry, earlier I argued that this had to be n^2) if you're careful to not actually print the results!
First, let's define a "score" for any set of zeros and ones as the number of ones minus the number of zeroes. So (0,1) has a score of 0, while (0) is -1 and (1,1) is 2.
Now, start from the right. If the right-most digit is a 0 then it can be combined with any group to the left that has a score of 1. So we need to know what groups are available to the left, indexed by score. This suggests a recursive procedure that accumulates groups with scores. The sweep process is O(n) and at each step the process has to check whether it has created a new group and extend the table of known groups. Checking for a new group is constant time (lookup in a hash table). Extending the table of known groups is also constant time (at first I thought it wasn't, but you can maintain a separate offset that avoids updating each entry in the table).
So we have a peculiar situation: each step of the process identifies a set of results of size O(n), but the calculation necessary to do this is constant time (within that step). So the process itself is still O(n) (proportional to the number of steps). Of course, actually printing the results (either during the step, or at the end) makes things O(n^2).
I'll write some Python code to test/demonstrate.
Here we go:
SCORE = [-1,1]
class Accumulator:
def __init__(self):
self.offset = 0
self.groups_to_right = {} # map from score to start index
self.even_groups = []
self.index = 0
def append(self, digit):
score = SCORE[digit]
# want existing groups at -score, to sum to zero
# but there's an offset to correct for, so we really want
# groups at -(score+offset)
corrected = -(score + self.offset)
if corrected in self.groups_to_right:
# if this were a linked list we could save a reference
# to the current value. it's not, so we need to filter
# on printing (see below)
self.even_groups.append(
(self.index, self.groups_to_right[corrected]))
# this updates all the known groups
self.offset += score
# this adds the new one, which should be at the index so that
# index + offset = score (so index = score - offset)
groups = self.groups_to_right.get(score-self.offset, [])
groups.append(self.index)
self.groups_to_right[score-self.offset] = groups
# and move on
self.index += 1
#print self.offset
#print self.groups_to_right
#print self.even_groups
#print self.index
def dump(self):
# printing the results does take longer, of course...
for (end, starts) in self.even_groups:
for start in starts:
# this discards the extra points that were added
# to the data after we added it to the results
# (avoidable with linked lists)
if start < end:
print (start, end)
#staticmethod
def run(input):
accumulator = Accumulator()
print input
for digit in input:
accumulator.append(digit)
accumulator.dump()
print
Accumulator.run([0,1,0,0,1,1,1,1,0])
And the output:
dynamic: python dynamic.py
[0, 1, 0, 0, 1, 1, 1, 1, 0]
(0, 1)
(1, 2)
(1, 4)
(3, 4)
(0, 5)
(2, 5)
(7, 8)
You might be worried that some additional processing (the filtering for start < end) is done in the dump routine that displays the results. But that's because I am working around Python's lack of linked lists (I want to both extend a list and save the previous value in constant time).
It may seem surprising that the result is of size O(n^2) while the process of finding the results is O(n), but it's easy to see how that is possible: at one "step" the process identifies a number of groups (of size O(n)) by associating the current point (self.index in append, or end in dump()) with a list of end points (self.groups_to_right[...] or ends).
Update: One further point. The table of "groups to the right" will have a "typical width" of sqrt(n) entries (this follows from the central limit theorem - it's basically a random walk in 1D). Since an entry is added at each step, the average length is also sqrt(n) (the n values shared out over sqrt(n) bins). That means that the expected time for this algorithm (ie with random inputs), if you include printing the results, is O(n^3/2) even though worst case is O(n^2)
Answering directly the question:
you have to constructing an example where there are more than O(N) matches:
let N be in the form 2^k, with the following input:
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 (here, N=16)
number of matches (where 0 is the starting character):
length #
2 N/2
4 N/2 - 1
6 N/2 - 2
8 N/2 - 3
..
N 1
The total number of matches (starting with 0) is: (1+N/2) * (N/2) / 2 = N^2/8 + N/4
The matches starting with 1 are almost the same, expect that it is one less for each length.
Total: (N^2/8 + N/4) * 2 - N/2 = N^2/4
Every interval will contain at least one sequence of either (0,1) or (1,0). Therefore, it's simply a matter of finding every occurance of (0,1) or (1,0), then for each seeing if it is adjacent to an existing solution or if the two bookend elements form another solution.
With a bit of storage trickery you will be able to find all solutions in linear time. Enumerating them will be O(N^2), but you should be able to encode them in O(N) space.

Resources