Amol Pawar

Minimum jumps Problem Statement

Mastering Minimum Jumps Array Traversal: Efficient Kotlin Code for Minimum Jumps to Reach Array’s End

kotlin Minimum jumps

Solving problems related to array traversal and manipulation is a common task in programming interviews and competitive coding. One such interesting problem is finding the minimum number of jumps required to reach the end of an array. In this blog post, we will explore the problem, understand the underlying concepts, and implement a solution in Kotlin.

Minimum jumps Problem Statement

Problem Statement → Given an array of N integers arr[] where each element represents the max length of the jump that can be made forward from that element. Find the minimum jumps (minimum number of jumps) to reach the end of the array (starting from the first element). If an element is 0, then you can not move through that element.

Example:

Let’s consider an example to understand the problem better.

Kotlin
val array = intArrayOf(2, 3, 1, 1, 2, 4, 2, 0, 1, 1)

The minimum number of jumps (minimum jumps) required to reach the end of the array is 4. We can take the following jumps: 2 -> 3 -> 4 -> 1 -> 1.

Note: Return -1 if you can’t reach the end of the array.

Approach

To solve this problem, we can use a greedy approach. We start at the first element and, at each step, choose the next element that maximizes the range of reachable indices. We keep track of the current range, the next range, and the number of jumps made so far.

Solution

  1. To solve this problem we need to first break down the problem and understand it carefully.
  2. Let’s assume we have a simple array → [3,4,5,2,3,1,4,2]
  3. So the value at array[0] = 3, which represents the maximum or at most 3 jumps we can make towards the end of the array. that means it could be either 1 or 2 or 3. Let’s take another example array[2] = 5, here also we can jump from index 2 towards the end of the array either 1, 2, 3, 4, or 5 maximum jumps.
  4. Now suppose any array element is 0 in that case, we can’t jump, so that jump path won’t be part of our optimal solution.
  5. By taking care of the above two conditions, simple logic we can think that we need to iterate from the start position to till second last position in the array, why because we know from the start to the maximum position we can jump to the end of the array is second last position of the array.
  6. Now the big question comes to mind how will know which jump is optimal
  7. if 0 elements come somewhere in the array, will that affect our entire jump path, so to know that we will add the array index to the array element, so that, if the 0 value comes somewhere we know that our maximum jump will remain as it is, and we will skip that jump path and this is the basic logic of this problem, we need to add index and value at index in the array and then compare maximum jump reach.
  8. So to solve this problem we required three variables and initially, all are zero

maxReach it will help us to keep track of the maximum reach index we can reach from every current index in the array.

currentit will help us to keep track of the current position that we are moving ahead toward the end of the array.

jumpsit is required to find the minumum jumps( minimum number of jumps) or optimal solution to this problem.

Let’s implement this approach in Kotlin:

Kotlin
fun main() {
    val array = arrayOf(2, 3, 1, 1, 2, 4, 2, 0, 1, 1)

    println(findMinimumNumbersOfJumpsToEnd(array))
}

fun findMinimumNumbersOfJumpsToEnd(array: Array<Int>): Int {

    var maxReach = 0
    var current = 0
    var jumps = 0

    for (i in 0 until array.size) {   //iterate till end of array

        maxReach = Math.max(maxReach, i + array[i])  // 1

        if (current >= array.size - 1) { // 2
            break                     
        } else if (maxReach <= i) {   // 3
            return -1                 
        } else if (i == current) {    // 4
            current = maxReach
            jumps++
        }
    }

    return jumps
}
  1. Here is why we add “i + array[i]”, we are adding to know that at any point our array contains any 0 value, so in such case, our maxReach will remain that particular index.
  2. When our current reach to second last element of an array or beyond that, then we break the loop and return the final minimum jumps. we are handling this condition inside for loop that’s why we are not iterating for loop till the second last position in the array, otherwise, if we skip this one then, surely use array.size -1 in for loop.
  3. If maxReach is i means at i index definitely 0 value presents or if below i means maxReach is behind the i, in such case, we can’t reach the end, so return -1.
  4. Suppose we reached the current position (which is previously updated as maxReach) then, definitely, we found out the next maxReach index value, so now, here inside that block, we update that one, also as we are only interested in the maximum reach index among all possible jumps, that, we already found and updated in current, so we increment jumps by 1.

Conclusion

In this blog post, we explored the problem of finding the minimum jumps (minimum number of jumps) required to reach the end of an array. We implemented a solution in Kotlin using a greedy approach. This problem not only tests our algorithmic skills but also helps us understand how to efficiently traverse an array to minimize the number of steps taken.

Dry Run and Happy Coding 🙂

space complexity in kotlin

Optimizing Memory Usage: A Deep Dive into Space Complexity in Kotlin for Efficient Algorithm Implementation

Space Complexity

Note → Time and space complexity are high-level measures of scalability. They don’t measure the actual speed of the algorithm itself.

Space Complexity

The time complexity of an algorithm isn’t the only performance metric against which algorithms are ranked. Another important metric is its space complexity, which is a measure of the amount of memory it uses.

So basically, It is made up of Auxiliary space (extra or temporary space) and Input space (all possible inputs).

Kotlin
fun printSorted(numbers: List<Int>) {
    val sorted = numbers.sorted()
    for (element in sorted) {
        println(element)
    }
}

Since numbers.sorted() produces a new list with the same size of numbers, the space complexity of printSorted is O(n)

While the above function is simple and elegant, there may be some situations in which you want to allocate as little memory as possible. You could rewrite the above function like this:

Kotlin
fun printSorted(numbers: List<Int>) {
   
    if (numbers.isEmpty()) return
    
    var currentCount = 0
    var minValue = Int.MIN_VALUE
   
    for (value in numbers) {
        if (value == minValue) {
            println(value)
            currentCount += 1
        }
    }
    while (currentCount < numbers.size) {
        
        var currentValue = numbers.max()!!
        for (value in numbers) {
            if (value < currentValue && value > minValue) {
                currentValue = value
            }
        }
        
        for (value in numbers) {
            if (value == currentValue) {
                println(value)
                currentCount += 1
            }
        }
        
        minValue = currentValue
    }
}

The above algorithm only allocates memory for a few variables. Since the amount of memory allocated is constant and does not depend on the size of the list, the space complexity is O(1).

Tradeoff → This is in contrast with the previous function, which allocates an entire list to create the sorted representation of the source array. The tradeoff here is that you sacrifice time and code readability to use as little memory as possible. This is common in a problem since a problem cannot have both low computing time and low memory consumption. you either use more memory and solve the problem more quickly or use more time to solve the problem but with limited memory.

Note → The best algorithm/program should have the minimum space complexity. The lesser space used, the faster it executes.

time complexity in kotlin

Decoding Time Complexity: A Comprehensive Guide to Understanding and Implementing Efficient Algorithms in Kotlin

Time Complexity in Kotlin

Note → Time and space complexity are high-level measures of scalability. They don’t measure the actual speed of the algorithm itself.

Time complexity in Kotlin

Time complexity is a measure of the time required to run an algorithm as the input size increases and we use Big O notation to represent it. Let’s go through the most common time complexities and learn how to identify them.

Constant Time: As input data increases, the amount of time the algorithm takes does not change.

Kotlin
fun checkFirst(names: List<String>) {
    if (names.firstOrNull() != null) {
        println(names.first())
    } else {
        println("no names")
    }
}

The size of names does not affect the running time of this function. Whether names has 10 items or 10 million items, this function only checks the first element of the list.

The Big O notation for constant time is O(1)

Linear Time: As the amount of data increases, the running time increases by the same amount.

Kotlin
fun printNames(names: List<String>) {
    for (name in names) {
        println(name)
    }
}

This function prints all the names in a String list. As the input list increases in size, the number of iterations is increased by the same amount.

The Big O notation for linear time is O(n).

Note → All constants are dropped in the final Big O notation, In other words, O(2n + 6) is surprisingly equal to O(n).

Quadratic Time: This time complexity refers to an algorithm that takes time proportional to the square of the input size. most commonly referred to as n squared. As the size of the input data increases, the amount of time it takes for the algorithm to run increases drastically. Thus, n-squared algorithms don’t perform well at scale.

Java
fun multiplicationBoard(size: Int) {
    for (number in 1..size) {
        print(" | ")
        for (otherNumber in 1..size) {
            print("$number x $otherNumber = ${number * otherNumber} | ")
        }
        println()
    }
}

If the input is 10, it’ll print the full multiplication board of 10 × 10. That’s 100 print statements. If you increase the input size by one, it’ll print the product of 11 numbers with 11 numbers, resulting in 121 print statements.

The Big O notation for quadratic time is O(n²)

Note: No matter how inefficiently a linear time O(n) algorithm is written, for a sufficiently large n, the linear time algorithm will always execute faster than a super-optimized quadratic algorithm.

Logarithmic Time: An algorithm that can repeatedly drop half of the required comparisons will have logarithmic time complexity. As input data increases, the time it takes to execute the algorithm increases at a slower rate.

Java
fun pseudoBinaryContains(value: Int, numbers: List<Int>): Boolean {
    if (numbers.isEmpty()) return false
    val middleIndex = numbers.size / 2
    if (value <= numbers[middleIndex]) {
        for (index in 0..middleIndex) {
            if (numbers[index] == value) {
                return true
            }
        }
    } else {
        for (index in middleIndex until numbers.size) {
            if (numbers[index] == value) {
                return true
            }
        }
    }
    return false
}

When you have an input size of 100, halving the comparisons means you save 50 comparisons. If the input size was 10,000, halving the comparisons means you save 5,000 comparisons.

The Big O notation for logarithmic time complexity is O(log n).

Note:

Is it log base 2, log base 10, or the natural log?

In the above example, log base 2 applies. However, since Big O notation only concerns itself with the shape of the performance, the actual base doesn’t matter. The more input data you can drop after each pass, the faster the algorithm will be.

Quasilinear Time: Another common time complexity you’ll encounter is quasilinear time. Algorithms in this category perform worse than linear time but dramatically better than quadratic time. An example of a quasilinear time algorithm is Kotlin’s sort method.

The Big-O notation for quasilinear time complexity is O(n log n) which is a multiplication of linear and logarithmic time.

Note → For small data sets, the time complexity is usually irrelevant. A quasilinear algorithm can be slower than a linear algorithm.

Other Time Complexities: Other time complexities do exist as well, these time complexities include polynomial time, exponential time, factorial time, and more.

  • Polynomial — O(n²), O(n³), etc
  • Exponential — O(2ⁿ)
  • Factorial — O(n!)

Simplifying Android In-App Billing with Google-IAP Library (Play Billing Library Version 5.0.0)

Google-IAP Library
Android In-App Billing / Google-IAP Library

In the dynamic realm of Android app development, the process of implementing In-App Billing can be both challenging and time-consuming. To ease the burden on budding Android developers, today I am excited to share an easy-to-implement solution: the Google-IAP library, specifically tailored for In-App Billing. This library not only streamlines the implementation process but also minimizes the code required for handling in-app purchases.

Overview of Android In-App Billing / Google-IAP Library

The Google-IAP library is designed to simplify the integration of In-App Billing into Android applications. It stands out for its minimalist approach, offering developers a quick and efficient solution for testing and fast-paced development. With minimal lines of code, the library enables seamless in-app purchase handling, making it an ideal choice for novice Android developers.

Advantages of Google-IAP Library

  1. Minimal Code: One of the standout features of the Google-IAP library is its minimalistic approach to code. Developers can achieve in-app billing functionality with just a few lines of code, reducing the complexity and making the integration process more accessible for beginners.
  2. Fast Development: Time is of the essence in the world of app development, and the Google-IAP library acknowledges this reality. By providing a straightforward and efficient solution, it enables developers to implement in-app purchases rapidly, accelerating the overall development process.
  3. Ease of Testing: The library comes with built-in features that facilitate testing. This is especially beneficial during the development phase, allowing developers to ensure that the in-app billing functionality works as expected without the need for extensive debugging.

The Importance of Understanding Google Play Billing Library

While the Google-IAP library offers a convenient solution for quick implementation, it is crucial to emphasize the significance of understanding and considering the official Google Play Billing Library. Google Play Billing Library is a Google product, ensuring continuous updates and support, and is the recommended tool for handling in-app purchases on the Android platform.

Recommendation for Developers

As a best practice, it is strongly recommended that developers first gain a thorough understanding of the Google Play Billing Library and attempt to use it in their projects. Google’s product comes with the assurance of ongoing updates and support, ensuring compatibility with the latest Android versions and addressing any potential issues.

Caution Regarding Third-Party Libraries: While third-party libraries like Google-IAP can offer quick solutions, there is always a level of uncertainty regarding future updates and support. Relying solely on third-party libraries may lead to complications if they are not actively maintained. To avoid potential consequences in the long run, it is advisable to prioritize the official Google Play Billing Library for in-app billing implementations.

Conclusion

In conclusion, the Google-IAP (Play Billing Library Version 5.0.0) emerges as a handy tool for Android developers, especially those looking for a quick and easy solution for in-app billing. However, it is imperative to balance expediency with long-term stability. Developers are encouraged to first understand and consider the Google Play Billing Library, harnessing the power of an official Google product for robust and future-proof in-app purchase implementations. By doing so, developers can strike a balance between speed and reliability in their Android app development journey.

Kotlin Interoperability

Mastering Kotlin Interoperability: Seamless Integration for Cross-Language Harmony

Kotlin Interoperability →

We can call the java function from kotlin and the kotlin function from java and can have JAVA and KOTLIN files within the same application.

How this is possible for that we need to understand the following thing?

Let’s see first How Java Code Runs?

  1. Compile Time[Handled by JDK that contains Compiler] → Java code converted into Byte Code

MyDemo.java(java code) — — Compiler — → MyDemo.class(ByteCode)

2. Runtime[handled by JVM] → JVM runs that byte code

MyDemo.class(ByteCode) — —JVM— — → code up and Program Running

Now let’s see How Kotlin Code Runs?

  1. At Compile Time → Kotlin code converted into Byte Code

MyDemo.kt(kotlin code) — — Kotlin Compiler— — -> MyDemoKt.class(Byte Code)

2. At Runtime → JVM Runs that byte code

MyDemoKt.class(Byte Code) — -JVM — -> code up and Program Running

What happens actually when we run any kotlin file?

Assume the file name is MyFirst.kt

Kotlin
fun add(a: Int): Boolean {

    // Method Body
}

After compilation what happens to the MyFirst.kt file?
It is converted into MyFirstKt.class

Kotlin
class MyFirstKt {
  public static Boolean add(int a) {
 
   // Method Body
 }
}

that means kotlin code is just a wrapper for java code

Calling Kotlin function from Java file and vice-versa :

kotlin file name → MyFirst.kt

Java
fun main(args : Array<String>)
{

}

fun add(a: Int, b: Int): Int 
{
   return a + b
}

converted into a java wrapper class

Java
/**
* public class MyFirstKt
*  
*   public static void main(String[] args){
*
*   }
*
*   public static int add(int a, int b) {
*        return a + b;
*   }
*}
*
**/

As we have now two methods main and add in the form of java code so we can call it from any other java class

Java file name → MyJavaFile.java

Java
public class MyJavaFile {

    public static void main(String[] args) {

        int sum = MyFirstKt.add(3, 4);
        System.out.println("Printing sum from Java file : " + sum);
    }


    // we can call this method from the kotlin file
    public static int getArea(int l, int b) {
        return l * b;
    }
}

call above getArea() from kotlin like below

Kotlin
fun main(args: Array<String>) {
    var area = MyJavaFile.getArea(10, 5)
    println("Printing area from kotlin file : " + area)
}

Output : Printing area from kotlin file : 50

Note — We can change our kotlin file name to our own customized file name using @file:JvmName(“customized file name”) and then using that name we can access methods (means it gives name to wrapper class which generated after compilation and then using that name we can access methods e.g @file:JvmName(“MyCustomKotlinFileName”) it will change kotlin file name to MyCustomKotlinFileName.

Default Function with Interoperability with @JvmOverloads:

What is Default Function?

We can pass an argument with a default value to a function such function is called a Default function. if we pass any other value than default then that value will be overridden with a new passing value.

Kotlin
fun findVolume(length: Int, breadth: Int, height: Int = 10): Int {

    return length * breadth * height
}

var result = findVolume(2, 3)

print(result) // 2*3*10 = 60

var result = findVolume(2, 3, 20) // Overrides the default value means 10 will become 20

print(result) // 2*3*20 = 120

Note → Java doesn’t support Default Functionso we can use @JvmOverloads for Interoperability( means if we defined default function with @JvmOverloads in kotlin then we can happily access that function in the java class because it will become compatible with such change)

For example

Kotlin Code:

Kotlin

@file:JvmName("MyCustomKotlinFileName")

package com.myKotlin

fun main(args: Array<String>) {
    var result = findVolume(2, 3, 30)
    print(result)
}

// this annotation will just make this function compatible with java code
@JvmOverloads
fun findVolume(length: Int, breadth: Int, height: Int = 10): Int {

    return length * breadth * height
}

Java Code

Java

import com.myKotlin.MyCustomKotlinFileName

public class MyJavaFile {
    public static void main(String[] args) {

        int result1 = MyCustomKotlinFileName.findVolume(4, 7);
        System.out.println(result1)

        int result2 = MyCustomKotlinFileName.findVolume(4, 7, 40);
        System.out.println(result2)

    }
}

Output:

280 // It will use a default value that is 10 then this result will come 
1120 // It will use an overridden value that is 40 then this result comes

In conclusion, Kotlin Interoperability stands as a powerful bridge in the realm of programming, enabling seamless collaboration between different languages. As we’ve explored the various strategies and best practices, it becomes evident that mastering this aspect is crucial for developers aiming to create versatile and efficient applications. The ability of Kotlin to communicate effortlessly with other languages opens up a world of possibilities, allowing for the integration of diverse technologies and enhancing the overall development experience. Embrace the potential of Kotlin Interoperability to elevate your coding skills and empower your projects with the flexibility and compatibility needed for success in the ever-evolving landscape of software development.

Happy Coding 🙂

error: Content is protected !!