Your iterative solution
This is generally fine, with the only exception that it operates on an array of strings – I'll come back to that later.
Variables should be defined in the narrowest scope where they are used. This applies to
var temp: [String], j: Int
which are used only in the inner loop. Both variable names are non-descriptive and can be improved (e.g.: j
actually counts the number of consecutive equal numbers).
Your recursive solution
Here
let arr = Array(str).map{Int(String($0))}
the conversion from str
to an array is not needed: A Swift string is a collection of its characters, and you can call map()
directly on the string. You also know that the string contains only decimal digits. Therefore you can force-unwrap here, replacing several force-unwraps in the following code:
let arr = str.map{ Int(String($0))! }
This
var final = temp.last! final.count += 1 temp.removeLast() temp.append(final)
is more simply and more efficiently done as
temp[temp.count - 1].count += 1
And this loop
var result = "" for key in temp{ result += "\(key.0)\(key.1)" } return result
can be simplified using map()
:
return temp.map { "\($0.count)\($0.val)" }.joined()
Generally, this looks less efficient to me than the first approach, because a string is converted to an integer array in each recursion step.
Redesign
In order to avoid unnecessary conversions between strings, characters, and integers, I would suggest to operate on an integer array generally, and create the string answer only as the final step.
Instead of an inner loop or recursion I would put the code to compute the next iteration into a separate function. That makes the code easier to read and to test:
class Solution { func countAndSay(_ n: Int) -> String { var seq = [1] for _ in 1..<n { seq = transform(seq) } return seq.map(String.init).joined() } func transform(_ seq: [Int]) -> [Int] { // ... TODO ... } }
This works with both your solutions. For your first solution the transform function would be something like this:
func transform(_ seq: [Int]) -> [Int] { var result = [Int]() // The transformed sequence var current = seq[0] // The current number var count = 1 // Counts # of consecutive equal numbers for num in seq.dropFirst() { if num == current { count += 1 } else { result.append(count) result.append(current) current = num count = 1 } } result.append(count) result.append(current) return result }
Instead of comparing against arr[i-1]
the current number is “remembered” in a local variable, so that we can iterate over the sequence (without its first element) instead of iterating over the array indices.
Your second solution creates an array of (count, value)
tuples first, and therefore does not need to handle the case of the last run separately. If you start with an empty array of tuples (and use optional chaining to compare against the last value) then you even don't have to handle the first value separately:
func transform(_ seq: [Int]) -> [Int] { var pairs: [(count: Int, val: Int)] = [] for num in seq { if num == pairs.last?.val { pairs[pairs.count - 1].count += 1 } else { pairs.append((count: 1, val: num)) } } return Array(pairs.map { [$0.count, $0.val] }.joined()) }
In my tests this was a bit faster than your original solution, but slower than the first approach, probably because of the intermediate array.
A third solution
Here is another possible solution, which combines the advantages of both approaches. Instead of traversing the array elements, we directly search for the next index of a number different from the current. Neither the first nor the last run has be be treated specially:
func transform(_ seq: [Int]) -> [Int] { var result = [Int]() var fromIndex = seq.startIndex // Start index of current run while fromIndex != seq.endIndex { let current = seq[fromIndex] // Start index of next run: let toIndex = seq[(fromIndex+1)...].firstIndex(where: { $0 != current }) ?? seq.endIndex result.append(toIndex - fromIndex) result.append(current) fromIndex = toIndex } return result }