How to solve "Lego Blocks" on HackerRank
The challenge is to find the number of possible walls of width and height made up of 4 different width Lego blocks, with no complete vertical breaks anywhere within. It turns out that there are actually two challenges here: one mathmatical and one technological. Let’s start with the mathematical one.
The mathmatical challenge
The mathmatical challenge is fairly straightforward: how do we calculate the number of possible walls of a given dimension in linear time. To solve it, we break each row into successively narrower rows, and calculate its possible permutations based on the possible permutations for the next narrower rows. For complete walls, we do something similar, which I describe later on.
I’ll explain the solution using mathematical notation first, but I also explain in detail in comments in the final code below. So feel free to skip this section if you prefer C++.
First, we need to find the number of permutations for a row of width . To get a row of any given width, we can add an -width block to each row that is narrower. Since we have 4 block widths from 1 to 4, we can find the number of permutations for a given row width by adding together the number of permutations for each of the 4 narrower widths. We start by specifying the number of permutations for row widths from 1 to 4, and then calculate the rest from there:
We can also phrase equation 1 as:
if we want to handle an arbitrary number of block widths from 1 to .
Now, we need to find the total number of possible permutations, , for a wall of width and height , as well as the number of invalid () and valid () ones. The total number of permutations is simply the number of row permutations, , to the power , and number of valid permutations is simply the difference between the number of total permutations and the number of invalid ones:
To find the number of invalid permutations (that is, permutations with a complete vertical break anywhere in the middle), we take advantage of the fact that a wall of width with a continuous break at width is equivalent to two adjacent walls, one of width and one of width . The total number of permutations for the two adjacent walls is the product of the number of possible permutations for the left wall and the number of possible permutations for the right.
So, for a full wall of width , we find the total number of invalid permutations by summing together the total number of possible permutations with breaks at each position from to :
where is the number of permutations for the left portion and is the number for the right. The left portion need consider only valid permutations, since breaks at width will already have been accounted for in previous sums for narrower widths. A break at with a break at , for instance, will have been counted in the previous iteration, where the break at was explicitly accounted for, and all breaks at will have been accounted for in the right portion of the wall (), where we count all possible permutations, which necessarily includes all possible full vertical breaks.
The technological challenge
The technological challenge is a bit unfortunate, since it depends on
semantics that aren’t documented anywhere in the problem specification: the
input sets contain queries which will cause integer overflows of the 32 bit
data types used by the boilerplate code, and the expected results depend on
those overflows being mitigated by taking the result of each mathematical
operation modulo . Moreover, the modulo operation is expected to
always return a positive value, which is not guaranteed by the C++ %
operator. So we need to define our own mod
function which makes any
negative result positive by adding the divisor to it:
1 2 3 4 5 6 7 8 9 |
|
To add one more wrinkle, among the operations we need to do that may overflow
is exponentiation and, in order to get the expected result, the exponentiation
function needs to mod its intermediate results at every step, rather than only
at the end. The C++ pow
function does not allow for this, so we need to
again define our own:
1 2 3 4 5 6 7 |
|
The final result
With all of that taken care of, we should now be able to put together our final result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|