-1

Working on the following algorithm:

Given an array of non-negative integers, you are initially positioned at the first index of the array.

Each element in the array represents your maximum jump length at that position.

Determine if you are able to reach the last index.

For example:

  • A = [2,3,1,1,4], return true.
  • A = [3,2,1,0,4], return false.

Below is my solution. It tries every single potential step, and then memoizes accordingly. So if the first element is three, the code takes three steps, two steps, and one step, and launches three separate functions from there. I then memoized with a hash. My issue is that the code works perfectly fine, but it's timing out for very large inputs. Memoizing helped, but only a little bit. Am I memoizing correctly or is backtracking the wrong approach here?

def can_jump(nums)
    @memo = {}
    avail?(nums, 0)
end

def avail?(nums, index)
    return true if nums.nil? || nums.empty? || nums.length == 1 || index >= nums.length - 1
    current = nums[index]
    true_count = 0
    until current == 0  #try every jump from the val to 1
        @memo[index + current] ||= avail?(nums, index + current)
        true_count +=1 if @memo[index + current] == true
        current -= 1
    end

    true_count > 0

end
Jordan Running
  • 102,619
  • 17
  • 182
  • 182
segue_segway
  • 1,490
  • 1
  • 22
  • 40
  • 1
    This is probably a better fit for [Code Review Stack Exchange](//codereview.stackexchange.com/). – Jordan Running Feb 23 '17 at 19:36
  • The most efficient way to solve this is with dynamic programming, starting with the next to last index and working back. Each index is marked `true` or `false`. I'll put up a solution that does that. – Cary Swoveland Feb 23 '17 at 19:36
  • @Jordan, I disagree. The question if more about the algorithm to employ than the way the algorithm should be implemented. – Cary Swoveland Feb 23 '17 at 19:38
  • @CarySwoveland so my approach + usage of memoization is correct in implementation but could've been done in a more optimal way? – segue_segway Feb 23 '17 at 19:44
  • @Jordan, by keeping the question here we get both the algorithm and the Ruby code. – Cary Swoveland Feb 23 '17 at 19:46
  • @Jordan when referring other sites, it is often helpful to point that [cross-posting is frowned upon](http://meta.stackexchange.com/tags/cross-posting/info) – gnat Feb 23 '17 at 22:05

2 Answers2

2

Your code is O(n^2), but you can produce the result in O(n) time and O(1) space. The idea is to work backwards through the array keeping the minimum index found so far from which you can reach index n-1.

Something like this:

def can_jump(nums)
    min_index = nums.length - 1
    for i in (nums.length - 2).downto(0)
        if nums[i] + i >= min_index
            min_index = i
        end
    end
    min_index == 0
end

print can_jump([2, 3, 1, 1, 4]), "\n"
print can_jump([3, 2, 1, 0, 4]), "\n"
Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
  • Do you mind explaining why it's O(N^2)? Every index should be calculated from only once--after that avail?(nums, index) is saved away into a hash table. So isn't this just O(N)? – segue_segway Feb 23 '17 at 22:06
  • It's because each call to `avail?` iterates over a range of values of `current`. An example of an O(n^2) case: `[n-1, n-2, ..., 0]`. On this, even if higher values are already computed and memoized, `avail?(nums, i)` takes O(`nums.length - i`) time. Adding up over all values of `i` gives you O(n^2) time. – Paul Hankin Feb 23 '17 at 22:15
  • Good work, Paul. You're working backward, @Jordan's working forward, both using the same idea. – Cary Swoveland Feb 23 '17 at 22:34
  • @PaulHankin 'current' variable is just an integer in the array correct? The value doesn't scale with the size of the input. I get your point about my way being slower as a result, but isn't the cost of iterating through current's range O(1)? – segue_segway Feb 24 '17 at 00:21
  • @Sunny I gave you an example array above which runs in O(n^2). The value of `current` may or may not scale like `n`. When it does (for a significant enough fraction of the array), your code will be O(n^2), but it may always be small and then your code will be O(n). In the data that you're running on, is `current` guaranteed to be small? – Paul Hankin Feb 24 '17 at 00:30
  • @PaulHankin I see what you're saying. The array is populated with random integers, so I'm saying that the integer current is not related to the size of the array, therefore it doesn't scale with N and so it's O(N) (albeit a very slow O(N)). But I think I got what you're saying! – segue_segway Feb 24 '17 at 00:35
2

Here's a () algorithm:

  1. Initialize to 0.
  2. For each number in :
    1. If is greater than , neither nor any subsequent number can be reached, so
      • return false.
    2. If + is greater than , set to +.
  3. If is greater than or equal to the last index in
    • return true.
    • Otherwise return false.

Here's a Ruby implementation:

def can_jump(nums)
  max_reach = 0
  nums.each_with_index do |num, idx|
    return false if idx > max_reach
    max_reach = [idx+num, max_reach].max
  end
  max_reach >= nums.size - 1
end

p can_jump([2,3,1,1,4]) # => true
p can_jump([3,2,1,0,4]) # => false

See it on repl.it: https://repl.it/FvlV/1

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
  • Your solution is really irritating. I spent quite a bit of time on my answer, which I had to delete. What your method is doing is clear from the code, but I couldn't understand the explanation. – Cary Swoveland Feb 23 '17 at 22:21
  • Okay. Are you irritated because it's unclear or are you irritated because you had to (?) delete your answer? – Jordan Running Feb 23 '17 at 22:23
  • Whoops! I meant (in my now-deleted comment) "The latter. Time to take my dog for a walk. Very clever, btw." – Cary Swoveland Feb 23 '17 at 22:36