Skip to content

Commit

Permalink
Small refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
ashtanko committed Jul 3, 2024
1 parent 0adb204 commit ac368f4
Show file tree
Hide file tree
Showing 11 changed files with 608 additions and 158 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,26 @@ optimizations.

## Complexity Report

* 6,782 lines of code (loc)
* 7,232 lines of code (loc)

* 3,511 source lines of code (sloc)
* 3,512 source lines of code (sloc)

* 2,669 logical lines of code (lloc)
* 2,667 logical lines of code (lloc)

* 2,728 comment lines of code (cloc)
* 3,173 comment lines of code (cloc)

* 405 cyclomatic complexity (mcc)

* 205 cognitive complexity

* 0 number of total code smells

* 77% comment source ratio
* 90% comment source ratio

* 151 mcc per 1,000 lloc

* 0 code smells per 1,000 lloc

## Findings (0)

generated with [detekt version 1.23.6](https://detekt.dev/) on 2024-06-19 21:14:06 UTC
generated with [detekt version 1.23.6](https://detekt.dev/) on 2024-07-03 22:58:56 UTC
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ tasks {

withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(KOTLIN_2_0)
}
}

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ mockito-kotlin = { module = "com.nhaarman.mockitokotlin2:mockito-kotlin", versio

[plugins]
kt-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kt-serializatin = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kt-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
Expand Down
116 changes: 86 additions & 30 deletions src/main/kotlin/dev/shtanko/algorithms/leetcode/AddBinary.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,64 +28,120 @@ import java.math.BigInteger

/**
* 67. Add Binary
* Given two binary strings a and b, return their sum as a binary string.
* @link https://leetcode.com/problems/add-binary/
* @see <a href="https://leetcode.com/problems/add-binary">Source</a>
*/
fun interface AddBinary {
operator fun invoke(
left: String,
right: String,
): String
operator fun invoke(left: String, right: String): String
}

/**
* Time complexity: O(max(N,M)), where N and M are lengths of the input strings a and b.
* Space complexity: O(max(N,M)) to keep the answer.
* # 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 sb = StringBuilder()
var i: Int = left.lastIndex
var j: Int = right.lastIndex
val resultBuilder = StringBuilder()
var leftIndex = left.lastIndex
var rightIndex = right.lastIndex
var carry = 0
while (i >= 0 || j >= 0) {
while (leftIndex >= 0 || rightIndex >= 0) {
var sum = carry
if (j >= 0) {
sum += right[j--] - '0'
if (rightIndex >= 0) {
sum += right[rightIndex--] - '0'
}
if (i >= 0) {
sum += left[i--] - '0'
if (leftIndex >= 0) {
sum += left[leftIndex--] - '0'
}
sb.append(sum % 2)
resultBuilder.append(sum % 2)
carry = sum / 2
}
if (carry != 0) {
sb.append(carry)
resultBuilder.append(carry)
}
sb.reverse().toString()
resultBuilder.reverse().toString()
}

/**
* Time complexity : O(N+M), where N and M are lengths of the input strings a and b.
* Space complexity: O(max(N,M)) to keep the answer.
* # 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 firstNum = BigInteger(left, 2)
var secondNum = BigInteger(right, 2)
var firstNumber = BigInteger(left, 2)
var secondNumber = BigInteger(right, 2)
val zero = BigInteger.ZERO
var carry: BigInteger
var result: BigInteger
while (secondNum != zero) {
result = firstNum.xor(secondNum)
carry = firstNum.and(secondNum).shiftLeft(1)
firstNum = result
secondNum = carry
var sum: BigInteger
while (secondNumber != zero) {
sum = firstNumber.xor(secondNumber)
carry = firstNumber.and(secondNumber).shiftLeft(1)
firstNumber = sum
secondNumber = carry
}
firstNum.toString(2)
firstNumber.toString(2)
}
}
}
30 changes: 26 additions & 4 deletions src/main/kotlin/dev/shtanko/algorithms/leetcode/AppealSum.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import dev.shtanko.algorithms.Constants.ALPHABET_LETTERS_COUNT

/**
* 2262. Total Appeal of A String
* @link https://leetcode.com/problems/total-appeal-of-a-string/
* @see <a href="https://leetcode.com/problems/total-appeal-of-a-string">
* Source</a>
* Functional interface representing an appeal sum operation on a String.
*/
fun interface AppealSum {
Expand All @@ -42,10 +43,31 @@ fun interface AppealSum {
}

/**
* Performs the appeal sum operation on the given String using dynamic programming.
* # Intuition
* The problem is to calculate the sum of the appeal of all substrings of a
* given string. The appeal of a string is defined as the number of distinct
* characters in it. The challenge is to efficiently compute this for all
* substrings.
*
* @param str The input String to perform the appeal sum on.
* @return The result of the appeal sum operation.
* # Approach
* We can use dynamic programming to solve this problem. The key idea is to keep
* track of the last position where each character appeared. For each character
* in the string, we can calculate the contribution to the total appeal based on
* its position and the previous positions of the same character. We maintain an
* array `prev` where `prev[c - 'a']` keeps the last index of character `c`. For
* each character at index `i`, the contribution to the appeal is calculated as
* `i + 1 - prev[c - 'a']`. We then update the `prev` array with the current
* index.
*
* # Complexity
* - Time complexity: O(n)
* The time complexity is linear with respect to the length of the string, since
* we process each character exactly once.
*
* - Space complexity: O(1)
* The space complexity is constant, as we use a fixed-size array of length 26
* to keep track of the last positions of each
* character (assuming only lowercase English letters).
*/
val appealSumDp = AppealSum { str: String ->
var cur: Long = 0
Expand Down
Loading

0 comments on commit ac368f4

Please sign in to comment.