Adding Two Negabinary Numbers
MedPrompt
Given two numbers arr1 and arr2 in base -2, return the result of adding them together.
Each number is given in array format: as an array of 0s and 1s, from most significant bit to least significant bit. For example, arr = [1,1,0,1] represents the number (-2)^3 + (-2)^2 + (-2)^0 = -3. A number arr in array, format is also guaranteed to have no leading zeros: either arr == [0] or arr[0] == 1.
Return the result of adding arr1 and arr2 in the same format: as an array of 0s and 1s with no leading zeros.
Example 1:
Input: arr1 = [1,1,1,1,1], arr2 = [1,0,1]
Output: [1,0,0,0,0]
Explanation: arr1 represents 11, arr2 represents 5, the output represents 16.Example 2:
Input: arr1 = [0], arr2 = [0]
Output: [0]Example 3:
Input: arr1 = [0], arr2 = [1]
Output: [1]
Constraints:
1 <= arr1.length, arr2.length <= 1000arr1[i]andarr2[i]are0or1arr1andarr2have no leading zeros
Approaches
2 approaches with complexity analysis and trade-offs.
This approach follows a straightforward, multi-step process. First, it converts the two negabinary numbers from their array format into standard base-10 (decimal) integers. Since the values can become very large, java.math.BigInteger is used to prevent overflow. After conversion, the two decimal numbers are added together. Finally, the resulting sum is converted from its decimal representation back into the negabinary array format.
Algorithm
-
- Implement a helper function
toDecimalthat takes a negabinary array and converts it to itsjava.math.BigIntegerdecimal equivalent. This is done by iterating through the array and summing uparr[i] * (-2)^power.
- Implement a helper function
-
- Implement a second helper function
fromDecimalthat converts aBigIntegerback to a negabinary array. This involves a loop where in each step, the number is divided by -2, and the remainder is taken as the next digit. A special adjustment is needed if the remainder is negative.
- Implement a second helper function
-
- In the main function, use
toDecimalto convert both input arraysarr1andarr2intoBigIntegers.
- In the main function, use
-
- Add these two
BigIntegers using theaddmethod.
- Add these two
-
- Pass the resulting sum to the
fromDecimalfunction to get the final negabinary array.
- Pass the resulting sum to the
-
- Return the result.
Walkthrough
The core idea is to switch from the negabinary system to the familiar decimal system to perform the addition, and then switch back.
Step 1: Negabinary to Decimal Conversion
A function is created to convert an array like [1,1,0,1] to its decimal value. This is calculated as 1*(-2)^3 + 1*(-2)^2 + 0*(-2)^1 + 1*(-2)^0 = -8 + 4 + 0 + 1 = -3. We must use BigInteger to handle the large numbers that can arise from arrays up to 1000 elements long.
import java.math.BigInteger; private BigInteger toDecimal(int[] arr) { BigInteger result = BigInteger.ZERO; BigInteger powerOfNegTwo = BigInteger.ONE; BigInteger negTwo = new BigInteger("-2"); for (int i = arr.length - 1; i >= 0; i--) { if (arr[i] == 1) { result = result.add(powerOfNegTwo); } powerOfNegTwo = powerOfNegTwo.multiply(negTwo); } return result;}Step 2: Addition
The two BigIntegers obtained from step 1 are simply added together.
BigInteger num1 = toDecimal(arr1);BigInteger num2 = toDecimal(arr2);BigInteger sum = num1.add(num2);Step 3: Decimal to Negabinary Conversion This is the reverse of step 1. We convert the decimal sum back to a negabinary array. The standard algorithm is repeated division by the base (-2) and recording the remainders. However, standard remainders can be negative. We need remainders to be 0 or 1. If a remainder is negative (-1), we adjust it to 1 and add 1 to the quotient to compensate.
private int[] fromDecimal(BigInteger n) { if (n.equals(BigInteger.ZERO)) { return new int[]{0}; } java.util.List<Integer> list = new java.util.ArrayList<>(); BigInteger negTwo = new BigInteger("-2"); while (!n.equals(BigInteger.ZERO)) { BigInteger[] divAndRem = n.divideAndRemainder(negTwo); BigInteger quotient = divAndRem[0]; BigInteger remainder = divAndRem[1]; if (remainder.compareTo(BigInteger.ZERO) < 0) { remainder = remainder.add(BigInteger.valueOf(2)); quotient = quotient.add(BigInteger.ONE); } list.add(remainder.intValue()); n = quotient; } java.util.Collections.reverse(list); return list.stream().mapToInt(i -> i).toArray();}Complexity
Time
O((max(N, M))^2). The conversion steps involve loops where `BigInteger` multiplication and division are performed. These operations are not constant time; their complexity depends on the number of bits in the operands, which is proportional to the array lengths.
Space
O(max(N, M)), where N and M are the lengths of the input arrays. This space is used to store the `BigInteger` representations and the final result array.
Trade-offs
Pros
The approach is conceptually simple, breaking the problem into distinct, understandable steps: convert, add, convert back.
It leverages the powerful and well-tested
BigIntegerclass, abstracting away the complexities of large number arithmetic.
Cons
Significantly less efficient due to the computational overhead of
BigIntegerarithmetic, especially for multiplication and division operations.The conversion from decimal back to negabinary requires careful handling of negative remainders, which can be complex and error-prone.
This approach simulates the process of column-by-column addition directly on the negabinary numbers, much like performing addition by hand. It iterates from the least significant bit (rightmost) to the most significant bit (leftmost), calculating the sum and a carry at each position. The main distinction from standard binary addition is the unique logic for calculating and propagating the carry in a base -2 system.
Algorithm
-
- Initialize two pointers,
iandj, to point to the last element ofarr1andarr2respectively.
- Initialize two pointers,
-
- Initialize a
carryvariable to0and aLinkedList<Integer>to store result bits.
- Initialize a
-
- Loop while
i >= 0orj >= 0orcarry != 0.
- Loop while
-
- In each iteration, retrieve the current bits from
arr1[i]andarr2[j]. If a pointer is out of bounds, the bit is considered0. Decrement the pointers.
- In each iteration, retrieve the current bits from
-
- Calculate the
sumof the two bits and the currentcarry.
- Calculate the
-
- The result bit for the current position is
sum & 1. Add this bit to the front of the result list.
- The result bit for the current position is
-
- Calculate the new carry for the next position (to the left) using the formula
carry = -(sum >> 1).
- Calculate the new carry for the next position (to the left) using the formula
-
- After the loop, the result list holds the sum in negabinary form, possibly with leading zeros.
-
- Remove any leading zeros. If the result is all zeros, return
[0].
- Remove any leading zeros. If the result is all zeros, return
-
- Convert the final list to an integer array and return it.
Walkthrough
This method avoids conversions to other bases and works directly with the negabinary representation. It mimics manual addition.
We iterate from right to left, maintaining a carry. The sum at each position k is arr1[k] + arr2[k] + carry. The value at this position must be represented by a digit d_k (0 or 1) and a carry to the next position. The relationship is sum = d_k + (-2) * carry_new.
From this, we can derive the rules:
- The digit
d_kissum mod 2. A simple way to compute this issum & 1. - The new carry is
carry_new = (sum - d_k) / (-2). This can be simplified. For any integersum,(sum - (sum & 1))is always an even number. Dividing by -2 is equivalent to dividing by 2 and negating. In Java, the bitwise right shift>>onsumeffectively calculatesfloor(sum / 2). The expression-(sum >> 1)correctly computes the new carry for all possiblesumvalues (-1, 0, 1, 2, 3).
For example:
- If
sum = 2(1+1+0):d_k = 2 & 1 = 0.carry_new = -(2 >> 1) = -1. - If
sum = -1(0+0-1):d_k = -1 & 1 = 1.carry_new = -(-1 >> 1) = -(-1) = 1.
The algorithm proceeds until all input digits are processed and the carry becomes zero. Finally, any leading zeros in the result are removed.
import java.util.LinkedList; public int[] addNegabinary(int[] arr1, int[] arr2) { int i = arr1.length - 1; int j = arr2.length - 1; int carry = 0; LinkedList<Integer> result = new LinkedList<>(); while (i >= 0 || j >= 0 || carry != 0) { int bit1 = (i >= 0) ? arr1[i--] : 0; int bit2 = (j >= 0) ? arr2[j--] : 0; int sum = bit1 + bit2 + carry; result.addFirst(sum & 1); carry = -(sum >> 1); } // Remove leading zeros int firstOneIndex = -1; for (int k = 0; k < result.size(); k++) { if (result.get(k) == 1) { firstOneIndex = k; break; } } if (firstOneIndex == -1) { // Result is 0 return new int[]{0}; } // Convert LinkedList to array, skipping leading zeros int[] finalResult = new int[result.size() - firstOneIndex]; for (int k = 0; k < finalResult.length; k++) { finalResult[k] = result.get(firstOneIndex + k); } return finalResult;}Complexity
Time
O(max(N, M)), where N and M are the lengths of the input arrays. The algorithm performs a single pass through the digits of the numbers.
Space
O(max(N, M)) to store the result. The space used for pointers and the carry is constant.
Trade-offs
Pros
Highly efficient with an optimal time complexity.
Avoids the overhead and complexity of large number libraries like
BigIntegerby working with small, constant-size integers.It is a direct and elegant solution that operates within the problem's native negabinary representation.
Cons
The carry propagation logic,
carry = -(sum >> 1), is non-intuitive compared to standard binary addition and requires a solid understanding of negabinary properties or careful derivation.
Solutions
Solution
public class Solution { public int[] AddNegabinary(int[] arr1, int[] arr2) { int i = arr1.Length - 1, j = arr2.Length - 1; List < int > ans = new List < int > (); for (int c = 0; i >= 0 || j >= 0 || c != 0; --i, --j) { int a = i < 0 ? 0 : arr1[i]; int b = j < 0 ? 0 : arr2[j]; int x = a + b + c; c = 0; if (x >= 2) { x -= 2; c -= 1; } else if (x == -1) { x = 1; c = 1; } ans.Add(x); } while (ans.Count > 1 && ans[ans.Count - 1] == 0) { ans.RemoveAt(ans.Count - 1); } ans.Reverse(); return ans.ToArray(); }}class Solution {public int[] addNegabinary(int[] arr1, int[] arr2) { int i = arr1.length - 1, j = arr2.length - 1; List<Integer> ans = new ArrayList<>(); for (int c = 0; i >= 0 || j >= 0 || c != 0; --i, --j) { int a = i < 0 ? 0 : arr1[i]; int b = j < 0 ? 0 : arr2[j]; int x = a + b + c; c = 0; if (x >= 2) { x -= 2; c -= 1; } else if (x == -1) { x = 1; c += 1; } ans.add(x); } while (ans.size() > 1 && ans.get(ans.size() - 1) == 0) { ans.remove(ans.size() - 1); } Collections.reverse(ans); return ans.stream().mapToInt(x->x).toArray(); }}class Solution {public: vector<int> addNegabinary(vector<int> &arr1, vector<int> &arr2) { int i = arr1.size() - 1, j = arr2.size() - 1; vector<int> ans; for (int c = 0; i >= 0 || j >= 0 || c; --i, --j) { int a = i < 0 ? 0 : arr1[i]; int b = j < 0 ? 0 : arr2[j]; int x = a + b + c; c = 0; if (x >= 2) { x -= 2; c -= 1; } else if (x == -1) { x = 1; c += 1; } ans.push_back(x); } while (ans.size() > 1 && ans.back() == 0) { ans.pop_back(); } reverse(ans.begin(), ans.end()); return ans; }};class Solution: def addNegabinary(self, arr1: List[int], arr2: List[int]) -> List[int]: i, j = len(arr1) - 1, len(arr2) - 1 c = 0 ans = [] while i >= 0 or j >= 0 or c: a = 0 if i < 0 else arr1[i] b = 0 if j < 0 else arr2[j] x = a + b + c c = 0 if x >= 2: x -= 2 c -= 1 elif x == - 1: x = 1 c += 1 ans . append(x) i, j = i - 1, j - 1 while len(ans) > 1 and ans[- 1] == 0: ans . pop() return ans[:: - 1]Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.