ashtanko/the-algorithms

View on GitHub
src/main/kotlin/dev/shtanko/algorithms/leetcode/AddBinary.kt

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
/*
 * MIT License

 * Copyright (c) 2022 Oleksii Shtanko

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
*/

package dev.shtanko.algorithms.leetcode

import java.math.BigInteger

/**
 * 67. Add Binary
 * @see <a href="https://leetcode.com/problems/add-binary">Source</a>
 */
fun interface AddBinary {
    operator fun invoke(left: String, right: String): String
}

/**
 * # Approach 1: Bit by BitComputation
 *
 * # Intuition
 * The problem is to add two binary strings. This can be approached in a manner
 * similar to how we add two decimal numbers, but instead, we work with binary
 * numbers. The key is to handle the binary digits (bits) from right to left,
 * managing the carry at each step just as we do in base-10 addition.
 *
 * # Approach
 * We use two pointers starting at the end of both binary strings and move
 * towards the beginning. At each step, we calculate the sum of the
 * corresponding bits along with the carry from the previous step. The sum of
 * two bits can be 0, 1, or 2 (where 2 in binary is 10, so we have a carry of 1)
 * We append the result bit to a StringBuilder and update the carry. If one
 * string is longer than the other, the excess bits are added directly to the
 * result. Finally, if there's a carry left after the main loop, it's appended
 * to the result as well. The result string is then reversed to get the correct
 * binary sum.
 *
 * # Complexity
 * - Time complexity: O(max(n, m))
 * The time complexity is linear with respect to the length of the longer
 * binary string, since we process each bit exactly once.
 *
 * - Space complexity: O(max(n, m))
 * The space complexity is also linear with respect to the length of the longer
 * binary string. This accounts for the space used by the StringBuilder to store
 * the result.
 */
val addBinaryBitByBitComputation = AddBinary { left: String, right: String ->
    val resultBuilder = StringBuilder()
    var leftIndex = left.lastIndex
    var rightIndex = right.lastIndex
    var carry = 0
    while (leftIndex >= 0 || rightIndex >= 0) {
        var sum = carry
        if (rightIndex >= 0) {
            sum += right[rightIndex--] - '0'
        }
        if (leftIndex >= 0) {
            sum += left[leftIndex--] - '0'
        }
        resultBuilder.append(sum % 2)
        carry = sum / 2
    }
    if (carry != 0) {
        resultBuilder.append(carry)
    }
    resultBuilder.reverse().toString()
}

/**
 * # Approach 2: Bit Manipulation
 *
 * # Intuition
 * Adding two binary numbers can be efficiently handled using bit manipulation
 * techniques. Instead of performing bit-by-bit addition manually, we can
 * leverage the properties of bitwise operations to simplify the process.
 * This approach is inspired by how binary addition works at the hardware level.
 *
 * # Approach
 * We use the properties of `XOR` and `AND` bitwise operations to perform binary
 * addition. The `XOR` operation helps us find the sum without considering the
 * carry, while the `AND` operation helps us determine where the carry bits are.
 * By repeatedly performing these operations and shifting the carry left until
 * there are no more carry bits, we can obtain the final binary sum.
 *
 * 1. Convert the binary strings to `BigInteger` to handle large binary numbers.
 * 2. Use a loop to repeatedly apply the `XOR` and `AND` operations:
 *    - `firstNum.xor(secondNum)` gives the sum without carry.
 *    - `firstNum.and(secondNum).shiftLeft(1)` gives the carry.
 *    - Update `firstNum` to the result of `XOR` and `secondNum` to the carry.
 * 3. Continue the loop until `secondNum` (the carry) is zero.
 * 4. Convert the result back to a binary string.
 *
 * Special cases:
 * - If one of the inputs is blank, return the non-blank input.
 * - If both inputs are blank, return an empty string.
 *
 * # Complexity
 * - Time complexity: O(max(n, m))
 * The time complexity depends on the length of the binary strings, as each bit
 * must be processed. The loop runs for the number of bits in the longer string.
 *
 * - Space complexity: O(max(n, m))
 * The space complexity depends on the storage required for the `BigInteger`
 * representations of the binary strings, which is proportional to the number
 * of bits in the longer string.
 */
val addBinaryBitManipulation = AddBinary { left: String, right: String ->
    when {
        left.isBlank() && right.isNotBlank() -> right
        left.isNotBlank() && right.isBlank() -> left
        left.isBlank() && right.isBlank() -> ""
        else -> {
            var firstNumber = BigInteger(left, 2)
            var secondNumber = BigInteger(right, 2)
            val zero = BigInteger.ZERO
            var carry: BigInteger
            var sum: BigInteger
            while (secondNumber != zero) {
                sum = firstNumber.xor(secondNumber)
                carry = firstNumber.and(secondNumber).shiftLeft(1)
                firstNumber = sum
                secondNumber = carry
            }
            firstNumber.toString(2)
        }
    }
}