Removing a loop to make code run faster (Kotlin) (Big O) - kotlin

I'm trying a leetcode challenge and am struggling to pass the challenge due to the speed of my code:
class Solution {
fun longestPalindrome(s: String): String {
var longestPal = ""
var substring = ""
for (i in 0..s.length) {
for (j in i + 1..s.length) {
substring = s.substring(i, j)
if (substring == substring.reversed() && substring.length > longestPal.length) {
longestPal = substring
}
}
}
return longestPal
}
}
I'm a newb and not familiar with Big O notation.
I imagine if I could use just one loop I would be able to speed this code up significantly but am not sure how I would go about this.

(Not saying this is the best approach, but that is a start)
Palindromes can only be found between two same letters. So one idea is to go through the string once, and keep track of letter indexes. When you encounter a letter you already saw before, and the difference in indexes is longer than the current longest palindrome, you check for a palindrome:
fun longestPal(s: String): String {
val letters = mutableMapOf<Char, MutableList<Int>>()
var longest = ""
s.indices.forEach { index ->
val indicesForCurrentChar = letters[s[index]] ?: mutableListOf()
for (i in indicesForCurrentChar) {
if ((index - i) < longest.length) break // (1) won't be the longest anyway
val sub = s.substring(i, index + 1)
if (sub == sub.reversed()) longest = sub
}
indicesForCurrentChar.add(index)
letters[s[index]] = indicesForCurrentChar
}
return longest
}
What is costly here is the palindrome check itself (sub == sub.reversed). But the check in (1) should contain it (think of a string which is the repetition of the same letter).
I would be curious to know what other suggest online.

Your code runs in O(n^3) time, a loop within a loop within a loop, because that reversed() call iterates up to the size of the input string. You can look up Manacher's algorithm for an explanation of how to do it in linear time (O(n)), no nested iteration at all.

Related

Slice() nested for loop values i and j Kotlin

I'm wanting to slice a range which I can do in Javascfript but am struggling in kotlin.
my current code is:
internal class blah {
fun longestPalindrome(s: String): String {
var longestP = ""
for (i in 0..s.length) {
for (j in 1..s.length) {
var subS = s.slice(i, j)
if (subS === subS.split("").reversed().joinToString("") && subS.length > longestP.length) {
longestP = subS
}
}
}
return longestP
}
and the error I get is:
Type mismatch.
Required:
IntRange
Found:
Int
Is there a way around this keeping most of the code I have?
As the error message says, slice wants an IntRange, not two Ints. So, pass it a range:
var subS = s.slice(i..j)
By the way, there are some bugs in your code:
You need to iterate up to the length minus 1 since the range starts at 0. But the easier way is to grab the indices range directly: for (i in s.indices)
I assume j should be i or bigger, not 1 or bigger, or you'll be checking some inverted Strings redundantly. It should look like for (j in i until s.length).
You need to use == instead of ===. The second operator is for referential equality, which will always be false for two computed Strings, even if they are identical.
I know this is probably just practice, but even with the above fixes, this code will fail if the String contains any multi-code-unit code points or any grapheme clusters. The proper way to do this would be by turning the String into a list of grapheme clusters and then performing the algorithm, but this is fairly complicated and should probably rely on some String processing code library.
class Solution {
fun longestPalindrome(s: String): String {
var longestPal = ""
for (i in 0 until s.length) {
for (j in i + 1..s.length) {
val substring = s.substring(i, j)
if (substring == substring.reversed() && substring.length > longestPal.length) {
longestPal = substring
}
}
}
return longestPal
}
}
This code is now functioning but unfortunately is not optimized enough to get through all test cases.

Write a kotlin program that prints the number that is repeated the most in a consecutive way

I'm kind of stuck, I don't know how to make the second loop to start 1 position above the first loop in Kotlin.
I have an array (named myArray) with 10 elements, I need to Write a Kotlin program that prints the number that has the most consecutive repeated number in the array and also prints the number of times it appears in the sequence.
The program must parse the array from left to right so that if two numbers meet the condition, the one that appears first from left to right will be printed.
Longest: 3
Number: 8
fun main() {
val myArray: IntArray = intArrayOf(1,2,2,4,5,6,7,8,8,8)
for((index , value) in myArray.withIndex()){
var inx = index + 1
var count = 0
var longest = 0
var number = 0
for((inx,element) in myArray.withIndex()) {
if(value == element ){
count+=
}
}
if(longest < count){
longest = count
number = value
}
}
}
I'm against just dropping answers, but it is quite late for me, so I'll leave this answer here and edit it tomorrow with more info on how each part works. I hope that maybe in the meanwhile it will help you to gain some idea to where you might be going wrong.
val results = mutableMapOf<Int, Int>()
(0..myArray.size - 2).forEach { index ->
val current = myArray[index]
if (current == myArray[index + 1]) {
results[current] = (results[current] ?: 1) + 1
}
}
val (max, occurrences) = results.maxByOrNull { it.value } ?: run { println("No multiple occurrences"); return }
println("Most common consecutive number $max, with $occurrences occurrences")
Alternatively if the intArray would be a list, or if we allowed to change it to a list myArray.toList(), you could replace the whole forEach loop with a zipWithNext. But I'm pretty sure that this is a HW question, so I doubt this is the expected way of solving it.
myList.zipWithNext { a, b ->
if (a == b) results[a] = (results[a] ?: 1) + 1
}

Obtain the length of the selected string in kotlin

I want to obtain the index chars or words in a string
for example
tv.text=" hey how are you, are you okay"
val res=tv.text.indexOf('h')
(have any way for put string instead of char?
output res=0
index of return only first char with h but in my tv text I have more h chars
can we return all h chars indexs
You can use filter function to get all the string indices with the required character.
val text = " hey how are you, are you okay"
val charToSearch = 'h'
val occurrences = text.indices.filter { text[it] == charToSearch }
println(occurences)
Try it yourself
And, if you want to search for strings instead of a single character, you can do this:
text.indices.filter { text.startsWith(stringToSearch, it) }
The following should work (you try to find an index if you found one in the previous iteration and you begin the folloing iteration from the previously found character instance plus 1, so that you don't find the same again and again):
fun main() {
val word = " hey how are you, are you okay"
val character = 'h'
var index: Int = word.indexOf(character)
while (index >= 0) {
println(index)
index = word.indexOf(character, index + 1)
}
}
If you want to store the indexes for later usage you can also do the following:
fun main() {
val word = " hey how are you, are you okay"
val character = 'h'
val indexes = mutableListOf<Int>()
var index: Int = word.indexOf(character)
while (index >= 0) {
index = word.indexOf(character, index + 1)
indexes.add(index)
}
println(indexes)
}
If you just want all the indices matching a char you can do this:
text.indices.filter { text[it] == 'h' }
Finding string matches is trickier, you could use Kotlin's regionMatches function to check if the part of the string starting at index matches what you're looking for:
val findMe = "you"
text.indices.filter { i ->
text.regionMatches(i, findMe, 0, findMe.length)
}
You could use a regular expression too, so long as you're careful about validating the search pattern:
Regex(findMe).findAll(text)
.map { it.range.first() } // getting the first index of each matching range
.toList()

What is the most efficient way to generate random numbers from a union of disjoint ranges in Kotlin?

I would like to generate random numbers from a union of ranges in Kotlin. I know I can do something like
((1..10) + (50..100)).random()
but unfortunately this creates an intermediate list, which can be rather expensive when the ranges are large.
I know I could write a custom function to randomly select a range with a weight based on its width, followed by randomly choosing an element from that range, but I am wondering if there is a cleaner way to achieve this with Kotlin built-ins.
Suppose your ranges are nonoverlapped and sorted, if not, you could have some preprocessing to merge and sort.
This comes to an algorithm choosing:
O(1) time complexity and O(N) space complexity, where N is the total number, by expanding the range object to a set of numbers, and randomly pick one. To be compact, an array or list could be utilized as the container.
O(M) time complexity and O(1) space complexity, where M is the number of ranges, by calculating the position in a linear reduction.
O(M+log(M)) time complexity and O(M) space complexity, where M is the number of ranges, by calculating the position using a binary search. You could separate the preparation(O(M)) and generation(O(log(M))), if there are multiple generations on the same set of ranges.
For the last algorithm, imaging there's a sorted list of all available numbers, then this list can be partitioned into your ranges. So there's no need to really create this list, you just calculate the positions of your range s relative to this list. When you have a position within this list, and want to know which range it is in, do a binary search.
fun random(ranges: Array<IntRange>): Int {
// preparation
val positions = ranges.map {
it.last - it.first + 1
}.runningFold(0) { sum, item -> sum + item }
// generation
val randomPos = Random.nextInt(positions[ranges.size])
val found = positions.binarySearch(randomPos)
// binarySearch may return an "insertion point" in negative
val range = if (found < 0) -(found + 1) - 1 else found
return ranges[range].first + randomPos - positions[range]
}
Short solution
We can do it like this:
fun main() {
println(random(1..10, 50..100))
}
fun random(vararg ranges: IntRange): Int {
var index = Random.nextInt(ranges.sumOf { it.last - it.first } + ranges.size)
ranges.forEach {
val size = it.last - it.first + 1
if (index < size) {
return it.first + index
}
index -= size
}
throw IllegalStateException()
}
It uses the same approach you described, but it calls for random integer only once, not twice.
Long solution
As I said in the comment, I often miss utils in Java/Kotlin stdlib for creating collection views. If IntRange would have something like asList() and we would have a way to concatenate lists by creating a view, this would be really trivial, utilizing existing logic blocks. Views would do the trick for us, they would automatically calculate the size and translate the random number to the proper value.
I implemented a POC, maybe you will find it useful:
fun main() {
val list = listOf(1..10, 50..100).mergeAsView()
println(list.size) // 61
println(list[20]) // 60
println(list.random())
}
#JvmName("mergeIntRangesAsView")
fun Iterable<IntRange>.mergeAsView(): List<Int> = map { it.asList() }.mergeAsView()
#JvmName("mergeListsAsView")
fun <T> Iterable<List<T>>.mergeAsView(): List<T> = object : AbstractList<T>() {
override val size = this#mergeAsView.sumOf { it.size }
override fun get(index: Int): T {
if (index < 0 || index >= size) {
throw IndexOutOfBoundsException(index)
}
var remaining = index
this#mergeAsView.forEach { curr ->
if (remaining < curr.size) {
return curr[remaining]
}
remaining -= curr.size
}
throw IllegalStateException()
}
}
fun IntRange.asList(): List<Int> = object : AbstractList<Int>() {
override val size = endInclusive - start + 1
override fun get(index: Int): Int {
if (index < 0 || index >= size) {
throw IndexOutOfBoundsException(index)
}
return start + index
}
}
This code does almost exactly the same thing as short solution above. It only does this indirectly.
Once again: this is just a POC. This implementation of asList() and mergeAsView() is not at all production-ready. We should implement more methods, like for example iterator(), contains() and indexOf(), because right now they are much slower than they could be. But it should work efficiently already for your specific case. You should probably test it at least a little. Also, mergeAsView() assumes provided lists are immutable (they have fixed size) which may not be true.
It would be probably good to implement asList() for IntProgression and for other primitive types as well. Also you may prefer varargs version of mergeAsView() than extension function.
As a final note: I guess there are libraries that does this already - probably some related to immutable collections. But if you look for a relatively lightweight solution, it should work for you.

Fastest way to read in and print back out a 2d array in Kotlin?

The following code works for reading in a 2d array and printing it back out in Kotlin, however I imagine that with larger datasets swapping from a string to an int list would be rather slow, so is there a quicker way to do it?
fun main() {
var rowAndColumn = readLine()!!.split(" ")
var rows = rowAndColumn[0].toInt()
var columns = rowAndColumn[1].toInt()
val board = Array(rows) { IntArray(columns).toMutableList() }
for (i in 0 until rows) {
var stringColumn = readLine()!!.split("").toMutableList()
stringColumn.removeAll(listOf(""))
var column = stringColumn.map {it.toInt()}.toMutableList()
board[i] = column
}
for(i in 0 until rows) {
println(board[i].toString())
}
}
I measure this to be about 4-5 times faster than your method. It iterates Chars rather than splitting each line into Strings for each character.
var rowAndColumn = readLine()!!.split(" ")
var rows = rowAndColumn[0].toInt()
var columns = rowAndColumn[1].toInt()
val board = arrayOfNulls<IntArray?>(rows)
for (row in 0 until rows) {
board[row] = readLine()!!
.asIterable()
.mapNotNull {
val i = it.toInt() - '0'.toInt()
if (i in 0..1) i else null
}
.toIntArray()
}
board.requireNoNulls()
If you you don't mind ending up with an Array<List<Int>> instead of an Array<IntArray>, you can change that out on line 4 and remove the .toIntArray() call for a slight (~5%) improvement.
Caveat...I was reading from a text file, not the console input, so file access may have affected the comparison. Intuition tells me it's possible this would be even faster in comparison if file reading time were removed.