Lesson 5
Mastering Array Manipulation and the Two-Pointer Technique in Kotlin
Introduction

Hello there! Are you ready to solve another engaging problem today? We have a practical task that will enhance your problem-solving skills. It involves critical aspects of programming — dealing with arrays and using techniques such as sorting and the two-pointer method. So, let's jump in!

Task Statement

Alright, our task is as follows. Suppose you have two equally long arrays, AA and BB, with a length varying from 11 to 10001000, with each element being a unique positive integer ranging from 11 up to 10610^6. Your challenge is to craft a Kotlin function that identifies the closest number in array BB to 2B[i]2 \cdot B[i] for each ii. Once this number is identified, say, for the specific ii, it is B[j]B[j]. We want to create an array from A[j]A[j]s in the order of increasing ii.

To illustrate this, let's consider an example. We have:

Kotlin
1val A = intArrayOf(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110) 2val B = intArrayOf(4, 12, 3, 9, 6, 1, 5, 8, 37, 25, 100)

After running your function, the resulting array should look like this:

Kotlin
1val result = intArrayOf(80, 100, 50, 20, 20, 60, 40, 20, 110, 90, 110)

Let's walk through the first few steps:

The first item in BB is 44 at index=0index=0. Double this number is 88. The closest number to 88 in array BB is 88, which is at index=7index=7. The number at the same index in array AA is 8080, so we add 8080 to our new array.

The second item in BB is 1212 at index=1index=1. Double this number is 2424. The closest number to 2424 in BB is 2525, which is at index=9index=9. The corresponding index in AA has the number 100100. So, we add 100100 to our new array.

The third item in BB is 33 at index=2index=2. Double this number is 66. The closest number to 66 in BB is 66, which is at index=4index=4. The corresponding index in AA has the number 5050. So, we add 5050 to our new array.

We continue this process for the rest of the elements in BB.

Solution Building: Step 1

Let's embark on our solution-building journey by constructing a sorted array for array B. This array will include pairs of values value and their corresponding indices index from array B. Here, value represents the element in B, while index denotes the index at which value is found in array B.

This sorted array will be similar to an associative array, storing "value-index" pairs. It not only organizes the data for efficient retrieval but also makes it easier for us to traverse the array. Here's the introductory part of our Kotlin function, including the complete sorted array:

Kotlin
1fun findAndReplace(A: IntArray, B: IntArray): IntArray { 2 val B_sorted = B.mapIndexed { index, value -> index to value } 3 .sortedBy { it.second }

In the above code, we generate an array of pairs comprising the values from B and their respective indices using a mapIndexed function. Then, the sortedBy function arranges these pairs in ascending order of their values.

Solution Building: Step 2

Now that our sorted array (or associative array) is ready, we initiate the right pointer, j, and the result array, res. The former assists in ensuring our search remains within the boundaries of B_sorted, while the latter will be our final output containing the replaced elements from array A. We'll update res as we progress through the solution.

Kotlin
1 var j = 0 // Initialize right pointer 2 val res = IntArray(A.size) // Initialize the result array
Solution Building: Step 3

The primary logic of the problem lies in this step. We iterate over each item in B_sorted using its index i. For every item, we calculate the target, which is double the value of the element at the current index in B_sorted. We adjust the position of the right pointer, j, until it points to the closest number that is less than the target (2B_sorted[i].second2 \cdot B\_sorted[i].second). The operation continues as long as j is within the bound for B_sorted, and the next number in the array is smaller than the target.

Once we've identified a number greater than or equal to the target, we compare it with the previous number to see which one is closer to the target. If the next number is closer, we advance j by one step.

Kotlin
1 for ((index, bValue) in B_sorted.withIndex()) { 2 val target = 2 * bValue.second // The target is twice the current number in the sorted B 3 while (j < B.size - 1 && B_sorted[j + 1].second < target) { 4 j++ // Move the right pointer to find a number closer to the target 5 } 6 if (j < B.size - 1 && 7 kotlin.math.abs(B_sorted[j + 1].second - target) < kotlin.math.abs(target - B_sorted[j].second)) { 8 j++ // Move the right pointer one more step if the next number is closer to the target 9 }
Solution Building: Step 4

In this final step, we employ the indices from B_sorted to alter the appropriate elements in array A. Based on the position of the right pointer j, we replace the corresponding element in res with the element in A located at the same index.

Kotlin
1 res[B_sorted[index].first] = A[B_sorted[j].first] 2 // Collect the corresponding element from A at the same index as the closest number in B_sorted 3 } 4 5 return res 6} 7 8fun main() { 9 val A = intArrayOf(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110) 10 val B = intArrayOf(4, 12, 3, 9, 6, 1, 5, 8, 37, 25, 100) 11 val result = findAndReplace(A, B) 12 println(result.joinToString(prefix = "[", postfix = "]")) 13}

And there you have it! You've successfully crafted a Kotlin function capable of producing the required output. This function iteratively replaces each element in array A according to the defined logic and returns the modified array.

Complexity Analysis

It's vital to have an understanding of the computational complexity of our Two-Pointer approach and why it's effective for this problem.

  1. Time Complexity: The main steps of our solution involve sorting the array BB and traversing it with two pointers. Sorting an array of nn elements has a time complexity of O(nlogn)O(n \log n). The two-pointer traversal of the sorted array adds an O(n)O(n) time complexity. Thus, the overall time complexity of our solution is O(nlogn)O(n \log n) for the sorting operation, which dominates the linear time traversal.

  2. Space Complexity: Apart from the input data, our solution needs space for B_sorted and res, both of which are arrays having the same length as the input. Therefore, our solution has a linear space complexity of O(n)O(n), where nn is the length of the input arrays.

Lesson Summary

Great job! You've successfully tackled a high-level task that involved manipulating arrays and implementing advanced techniques such as sorting and the two-pointer method. Through this exercise, you've honed your Kotlin coding skills further and demonstrated your ability to solve a complex, real-world challenge.

Now it's your turn to master these techniques. We encourage you to practice solving similar challenges using the skills you've learned from this lesson. The more you practice, the better your problem-solving skills will become. So, get started and enjoy your journey in coding with Kotlin!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.