The array can be seen as the in-order sequence of a binary tree, which would also be the tree of recursion if you would apply a naive algorithm.
That binary tree has the following properties:
It is perfect
Its height h corresponds to the number of binary digits needed to represent n. So in the case of 9 (0b1001), h = 4.
It has 2h-1 nodes, which is also the number of elements in the array representation.
Nodes at depth i of the tree, all have the same value and correspond to the i th least significant bit of n. Let's call this bit ni with i in [1, h].
By consequence the total value of all nodes together is the sum of 2i-1ni for i in [1, h], which is exactly n.
A left (or right) subtree of a node at depth i, will correspond to the tree you would get for floor(n/2i).
To find the node corresponding to index j (when 1-based), you would take the binary representation of j using exactly h binary digits (prepadded with "0" if necessary) and then
as long as that representation has more than one "1" in it, choose the left branch when the left-most digit is "0", and right otherwise. Then remove that digit and repeat.
We could then determine what the value is of each left subtree that is omitted when a move to the right is made, include the 1 in the node itself, and take the total of those values.
This total would represent the number of 1 values at the left of index j.
This in turn could be used to know the total of 1 values in any range.
Turned into an algorithm, you would get something like the snippet below. You can run it, enter the values for n, i, j and see the resulting sum:
function sumBetween(n, i, j) {
if (j < i) return sumBetween(n, j, i); // put range in ascending order
return sumUntil(n, j) - sumUntil(n, i - 1);
}
function sumUntil(n, i) {
let numDigits = n.toString(2).length;
let bitMask = 1 << numDigits;
if (i >= bitMask) i = bitMask - 1; // Reduce i when exceeding the "array" size
if (i < 0) i = 0;
let sum = 0;
for (bitMask >>= 1; i > 0 && bitMask > 0; bitMask >>= 1) {
let goRight = i & bitMask; // Extract a bit from i
let nodeValue = n % 2; // Get value at current node of binary tree
n >>= 1; // Go down one level in the tree
if (goRight) sum += nodeValue + n; // Add node value and values in omitted left subtree
i -= goRight; // Clear bit in i
}
return sum;
}
// I/O handling
let inputs = document.querySelectorAll("input");
let result = document.querySelector("span");
document.addEventListener("input", refresh);
function refresh() {
let [n, i, j] = Array.from(inputs, input => input.value).map(Number);
let sum = sumBetween(n, i, j);
result.textContent = sum;
}
refresh();
input { width: 4em }
n: <input type="number" value="9"><br>
i: <input type="number" value="6"><br>
j: <input type="number" value="9"><br>
sum: <span></span>
The algorithm for getting the sum of a range has a time complexity of O(logn), independent of the values for i and j. The space complexity is O(1) -- there's no need to populate an array.