0

I have a number represented as a large binary string (length can be up to 1 million bits). The number is recursively split as follows:

At each step, divide the number x into two parts: floor(x/2) and ceil(x/2)

Recursively apply this division to both parts until the parts become 1.

I want to compute how many 1s appear only at the deepest (last) level of this recursion tree.

example: X=111

Level 0: 7

Level 1: 3, 4

Level 2: 1, 2, 2, 2

Level 3: 1, 1, 1, 1, 1, 1 ← final level (depth = 3)

There are 6 ones at the deepest level (level 3). So, answer is 6.

note : please output the result modulo 10^9+7.

6
  • the result at depth 3 is 7 not 6 they is no need for recursive split each number split is x>1 and they sum is always x so you end up with x*1 with x being the number of 1 Commented Jul 16 at 1:47
  • @Aren, it seems you missed a point, namely the phrase that says "...only at the deepest level...". Commented Jul 16 at 13:26
  • He did say recursively apply this division to both part until the parts become 1, the deepest level will only have 1s, the amount of 1 at the deepest level will be equal to the number with 7 you get 1 (7 times) but with a number x you get 1(x times) Commented Jul 16 at 17:56
  • Could you clarify on what he meant ? Commented Jul 16 at 17:59
  • @Aren, draw the recursion tree for input 7. Not all leaves of the tree are at the same level. Those leaves that are not at the deepest level of the tree should not be counted. The asker gave details of level 2 which includes a 1. That 1 should not be counted as it is not on the 'final level' Commented Jul 16 at 18:44

2 Answers 2

0

I made something, it can handle 10**6 bits fine but the more you increase the load the heavier it becomes you can comment out outputs and try it out with 10**6

Without output print() won't block I/O and it'll run faster

#import time
## division
def div(n):
    if n == 1:
        return (1,)
    return (n // 2 + 1, n // 2) if n % 2 else (n // 2, n // 2)
## check if all items is 1s
def is_complete(lst):
    return all(x == 1 for x in lst)

## list without 1s
## use between levels
def delete(lst):
    return [x for x in lst if x != 1]

def next_level(lst):
    new_lst = []
    for x in lst:
        new_lst.extend(div(x))
    return new_lst

inp = int(input("-> "))
#inp = 10**5
#inp = 10**6
#inp = 10**7
level = 0
current = [inp]

## keep looping until all equals 1
start = time.time()
while not is_complete(current):
    current = delete(current)
    print(f"level {level}: {current}")
    current = next_level(current)
    level += 1
#print(f"took: {time.time()-start} seconds to finish")
Mod = 10**9+7
## comment prints for faster result
## leave only the prints you need
print(f"level {level}: {current}")
print(f"Number of 1s(ones): {len(current)}")
print(f"Modulo is: {len(current)%Mod}")

It took ~5(4.9) On my machine when using 10**7 (with outputs )

Sign up to request clarification or add additional context in comments.

Comments

0

A recursive approach would divide the number by 2 according to the given rules, which means the string of binary digits is stripped from its rightmost digit, and of that digit was a 1, that should become a "carry" for one of the two partitions (since it must add this 1 to the half). Instead of stripping the digit, you could just have an index point to the last digit without really altering the input string.

Also, you would benefit from memoization, based on that index and whether there is a carry or not. This would make the algorithm run in linear time in terms of the number of digits in the input string.

Here is an implementation in JavaScript:

const MOD = 1000000007;

// Recursive helper function for the main function further down:
function recur(dp, s, index, carry) {
    if (dp[carry][index] == 0) { // Not memoized yet?
        if (+s[index] === carry) { // Number is even, so subtrees are equal:
            const res = recur(dp, s, index - 1, carry);
            dp[carry][index] = [(res[0] * 2) % MOD, res[1] + 1];
        } else { // Number is odd, so one of them gets a carry
            const left = recur(dp, s, index - 1, 0);
            const right = recur(dp, s, index - 1, 1);
            if (left[1] == right[1]) { // Both subtrees have same height
                dp[carry][index] = [(left[0] + right[0]) % MOD, left[1] + 1]; // Add up
            } else { // The right subtree is higher:
                dp[carry][index] = [right[0], right[1] + 1]; // Keep count from largest subtree only
            }
        }
    }
    return dp[carry][index];
}

// Main function: s is the input string of binary digits.
function recursive(s) {
    // Prepare for memoization:
    const dp = [Array(s.length).fill(0), Array(s.length).fill(0)]; // 1st dimension is carry (0 or 1), 2nd is index in s
    // Fill in base cases:
    dp[0][0] = [1, 0]; // Reaching leaf without carry: count is 1, subtree height is 0
    dp[1][0] = [2, 1]; // Reaching leaf with carry: count is 2, subtree height is 1.
    return recur(dp, s, s.length - 1, 0)[0];
}

With the final snippet below you can run it.

Iterative approach

When we analyse this problem a bit more, we find that the leaves of the binary tree that would be traversed through recursion, are all located in the two deepest levels of that tree. This can be shown like this:

A shortest root-to-leaf path in the tree is the one to the leftmost leaf, i.e. where each edge represents a division by 2 and flooring the result. There are ⌊log2𝑛⌋ such edges on this path. A longest root-to-leaf path is achieved by the path to the rightmost leaf. Here each edge represents a division by 2 and ceiling the result. There are ⌈log2𝑛⌉ edges on that path. The difference between these two path lengths is at most 1, and so the leaves of any such tree can be at the bottom two levels only.

The leaves that are on the one-but-bottom level, are the nodes that would become parents if the input value were to increase to the next power of 2. So the distance of 𝑛 to the least power of 2 that is not less than 𝑛, is the number of leaves that are not at the bottom level.

This is 2⌈log2𝑛⌉−𝑛.

As we know the total number of leaves is 𝑛, the desired outcome is 𝑛−(2⌈log2𝑛⌉−𝑛) or 2𝑛−2⌈log2𝑛⌉.

In other words, we can leave out the most significant (leftmost) digit, which will always be a 1, and evaluate what remains and multiply it by 2. Only when the input has no other 1-digit (when it is a power of 2) we need to return that power itself, not 0.

Here is code for that approach:

function iterative(s) {
    let power = 1;
    let n = 0;
    for (let i = s.length - 1; i > 0; i--) {
        power = (power * 2) % MOD
        if (s[i] == "1") n = (n + power) % MOD;
    }
    return s.lastIndexOf("1") ? n : power;
}

The following runnable snippet calculates the result for 𝑛 up to 10000, using both above described solutions, and asserts that the outcomes are the same:

const MOD = 1000000007;

function recur(dp, s, index, carry) {
    if (dp[carry][index] == 0) { // Not memoized yet?
        if (+s[index] === carry) { // Number is even, so subtrees are equal:
            const res = recur(dp, s, index - 1, carry);
            dp[carry][index] = [(res[0] * 2) % MOD, res[1] + 1];
        } else { // Number is odd, so one of them gets a carry
            const left = recur(dp, s, index - 1, 0);
            const right = recur(dp, s, index - 1, 1);
            if (left[1] == right[1]) { // Both subtrees have same height
                dp[carry][index] = [(left[0] + right[0]) % MOD, left[1] + 1]; // Add up
            } else { // The right subtree is higher:
                dp[carry][index] = [right[0], right[1] + 1]; // Keep count from largest subtree only
            }
        }
    }
    return dp[carry][index];
}

function recursive(s) {
    // Prepare for memoization:
    const dp = [Array(s.length).fill(0), Array(s.length).fill(0)]; // 1st dimension is carry (0 or 1), 2nd is index in s
    // Fill in base cases:
    dp[0][0] = [1, 0]; // Reaching leaf without carry: count is 1, subtree height is 0
    dp[1][0] = [2, 1]; // Reaching leaf with carry: count is 2, subtree height is 1.
    return recur(dp, s, s.length - 1, 0)[0];
}

function iterative(s) {
    let power = 1;
    let n = 0;
    for (let i = s.length - 1; i > 0; i--) {
        power = (power * 2) % MOD
        if (s[i] == "1") n = (n + power) % MOD;
    }
    return s.lastIndexOf("1") ? n : power;
}

// Test that both solutions return the same values for 0 < n < 10000
for (let n = 1; n < 10000; n++) {
    const bin = n.toString(2); // The input is a string of binary digits
    const a = recursive(bin);
    const b = iterative(bin);
    if (a !== b) throw "diff for n " + n;
}
console.log("The results are consistent for 0 < n < 10000");

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.