计算青蛙到达河对岸所需的最少跳跃次数

Posted

技术标签:

【中文标题】计算青蛙到达河对岸所需的最少跳跃次数【英文标题】:Count the minimum number of jumps required for a frog to get to the other side of a river 【发布时间】:2019-01-08 12:53:40 【问题描述】:

我正在处理下面提供的 Codility 问题,

斐波那契数列使用以下递归公式定义:

F(0) = 0
F(1) = 1
F(M) = F(M - 1) + F(M - 2) if M >= 2

一只小青蛙想去河的另一边。青蛙最初位于河的一岸(位置-1)并想到达另一岸(位置N)。青蛙可以跳过任何距离 F(K),其中 F(K) 是第 K 个斐波那契数。幸好河边有很多树叶,青蛙可以在树叶之间跳跃,但只能在N位置的河岸方向。

河流上的叶子表示在一个由 N 个整数组成的数组 A 中。数组 A 的连续元素表示河流上从 0 到 N - 1 的连续位置。数组 A 只包含 0 和/或 1:

0 表示没有叶子的位置; 1 表示包含叶子的位置。 目标是计算青蛙可以到达河对岸(从位置 -1 到位置 N)的最小跳跃次数。青蛙可以在 -1 和 N 位置(河岸)之间以及每个包含叶子的位置之间跳跃。

例如,考虑数组 A,这样:

A[0] = 0
A[1] = 0
A[2] = 0
A[3] = 1
A[4] = 1
A[5] = 0
A[6] = 1
A[7] = 0
A[8] = 0
A[9] = 0
A[10] = 0

青蛙可以跳三个长度为 F(5) = 5、F(3) = 2 和 F(5) = 5。

写一个函数:

class Solution  public int solution(int[] A); 

给定一个由 N 个整数组成的数组 A,返回青蛙可以到达河对岸的最小跳跃次数。如果青蛙无法到达河的另一边,该函数应返回 -1。

例如,给定:

A[0] = 0
A[1] = 0
A[2] = 0
A[3] = 1
A[4] = 1
A[5] = 0
A[6] = 1
A[7] = 0
A[8] = 0
A[9] = 0
A[10] = 0

该函数应返回 3,如上所述。

假设:

N是[0..100,000]范围内的整数; 数组 A 的每个元素都是一个整数,可以具有以下值之一:0、1。 复杂性:

预计最坏情况时间复杂度为O(N*log(N)); 预期的最坏情况空间复杂度为O(N)(不包括输入参数所需的存储空间)。

我写了以下解决方案,

class Solution 
    private class Jump 
        int position;
        int number;

        public int getPosition() 
            return position;
        

        public int getNumber() 
            return number;
        

        public Jump(int pos, int number) 
            this.position = pos;
            this.number = number;
        
    

    public int solution(int[] A) 

        int N = A.length;

        List<Integer> fibs = getFibonacciNumbers(N + 1);

        Stack<Jump> jumps = new Stack<>();
        jumps.push(new Jump(-1, 0));

        boolean[] visited = new boolean[N];

        while (!jumps.isEmpty()) 

            Jump jump = jumps.pop();

            int position = jump.getPosition();
            int number = jump.getNumber();

            for (int fib : fibs) 

                if (position + fib > N) 
                    break;
                 else if (position + fib == N) 
                    return number + 1;
                 else if (!visited[position + fib] && A[position + fib] == 1) 

                    visited[position + fib] = true;
                    jumps.add(new Jump(position + fib, number + 1));
                
            
        

        return -1;
    


    private List<Integer> getFibonacciNumbers(int N) 

        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < 2; i++) 
            list.add(i);
        

        int i = 2;

        while (list.get(list.size() - 1) <= N) 

            list.add(i, (list.get(i - 1) + list.get(i - 2)));
            i++;
        

        for (i = 0; i < 2; i++) 
            list.remove(i);
        

        return list;
    




    public static void main(String[] args) 

    int[] A = new int[11];

    A[0] = 0;
    A[1] = 0;
    A[2] = 0;
    A[3] = 1;
    A[4] = 1;
    A[5] = 0;
    A[6] = 1;
    A[7] = 0;
    A[8] = 0;
    A[9] = 0;
    A[10] = 0;

    System.out.println(solution(A));
   

然而,虽然正确性看起来不错,但性能还不够高。代码中是否存在错误,如何提高性能?

【问题讨论】:

也许你不需要一个列表,只需要一个 for 循环。也许 Flopshot:如果你没有什么可贡献的,那就考虑什么都不贡献。在这里活泼是没有意义的。这是一个写得很好的问题。我认为这不是主题,但可以肯定的是,OP 付出了相当多的努力,不应该被嘲笑。 @MarcosVasconcelos 你是什么意思? @GhostCat 我反对,因为问题是关于如何提高性能。我不需要任何帮助来改进设计等 @flopshot 对不起,你错了。 trivia 对你来说可能是其他人的真正问题。乱扔垃圾的是无数糟糕写的 no-attempt-drop-me-codez 问题。当您真正关心这些时,请花一些时间来提高您的声誉,这样您就可以参与对内容的投票,可能是关闭/关闭/......无论投票。这就是贡献的含义,而不是对写得好的问题不屑一顾。 【参考方案1】:

通过简单的 BFS 获得 100%:

public class Jump 
    int pos;
    int move;
    public Jump(int pos, int move) 
        this.pos = pos;
        this.move = move;
    


public int solution(int[] A) 

    int n = A.length;
    List < Integer > fibs = fibArray(n + 1);
    Queue < Jump > positions = new LinkedList < Jump > ();
    boolean[] visited = new boolean[n + 1];

    if (A.length <= 2)
        return 1;

    for (int i = 0; i < fibs.size(); i++) 
        int initPos = fibs.get(i) - 1;
        if (A[initPos] == 0)
            continue;
        positions.add(new Jump(initPos, 1));
        visited[initPos] = true;
    

    while (!positions.isEmpty()) 
        Jump jump = positions.remove();
        for (int j = fibs.size() - 1; j >= 0; j--) 
            int nextPos = jump.pos + fibs.get(j);
            if (nextPos == n)
                return jump.move + 1;
            else if (nextPos < n && A[nextPos] == 1 && !visited[nextPos]) 
                positions.add(new Jump(nextPos, jump.move + 1));
                visited[nextPos] = true;
            
        
    


    return -1;



private List < Integer > fibArray(int n) 
    List < Integer > fibs = new ArrayList < > ();
    fibs.add(1);
    fibs.add(2);
    while (fibs.get(fibs.size() - 1) + fibs.get(fibs.size() - 2) <= n) 
        fibs.add(fibs.get(fibs.size() - 1) + fibs.get(fibs.size() - 2));
    
    return fibs;

【讨论】:

我刚刚发现没有必要使用访问过的数组。您可以使用最少的跳跃次数来找到最佳解决方案。在这里查看:martinkysel.com/codility-fibfrog-solution【参考方案2】:

您可以应用knapsack 算法来解决这个问题。 在我的解决方案中,我预先计算了斐波那契数。并应用背包算法求解。它包含重复的代码,没有太多时间来重构它。相同代码的在线ide在repl

import java.util.*;
class Main 

public static int solution(int[] A) 

    int N = A.length;
    int inf=1000000;
    int[] fibs=1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025;
    int[] moves = new int[N+1];
     for(int i=0; i<=N; i++)
        moves[i]=inf;
     
    for(int i=0; i<fibs.length; i++)
        if(fibs[i]-1<N && A[fibs[i]-1]==1)
            moves[ fibs[i]-1 ] = 1;
        
        if(fibs[i]-1==N)
           moves[N] = 1;
        
    

    for(int i=0; i<N; i++)
        if(A[i]==1)
        for(int j=0; j<fibs.length; j++)
            if(i-fibs[j]>=0 && moves[i-fibs[j]]!=inf && moves[i]>moves[i-fibs[j]]+1)
                moves[i]=moves[i-fibs[j]]+1;
                            
        
         System.out.println(i + " => " + moves[i]);
    

     for(int i=N; i<=N; i++)
        for(int j=0; j<fibs.length; j++)
            if(i-fibs[j]>=0 && moves[i-fibs[j]]!=inf && moves[i]>moves[i-fibs[j]]+1)
                moves[i]=moves[i-fibs[j]]+1;
                            
        
         System.out.println(i + " => " + moves[i]);
    

    if(moves[N]==inf) return -1;
    return moves[N];


public static void main(String[] args) 

int[] A = new int[4];

A[0] = 0;
A[1] = 0;
A[2] = 0;
A[3] = 0;
System.out.println(solution(A));
 

【讨论】:

它确实将性能提高到了 66%,但仍然不能令人满意 你确定吗?我得到了 100% 的正确性和复杂性【参考方案3】:

javascript 100%

function solution(A) 
    function fibonacciUntilNumber(n) 
        const fib = [0,1];
        while (true) 
            let newFib = fib[fib.length - 1] + fib[fib.length - 2];
            if (newFib > n) 
                break;
            
            fib.push(newFib);
        
        return fib.slice(2);
    
    A.push(1);
    const fibSet = fibonacciUntilNumber(A.length);
    if (fibSet.includes(A.length)) return 1;
    const reachable = Array.from(length: A.length, () => -1);

    fibSet.forEach(jump => 
        if (A[jump - 1] === 1) 
            reachable[jump - 1] = 1;
        
    )

    for (let index = 0; index < A.length; index++) 
        if (A[index] === 0 || reachable[index] > 0) 
            continue;
        
        let minValue = 100005;
        for (let jump of fibSet) 
            let previousIndex = index - jump;
            if (previousIndex < 0) 
                break;
            
            if (reachable[previousIndex] > 0 && minValue > reachable[previousIndex]) 
                minValue = reachable[previousIndex];
            
        
        if (minValue !== 100005) 
            reachable[index] = minValue + 1;
        
    
    return reachable[A.length - 1];

【讨论】:

【参考方案4】:

Python 100% 答案。

对我来说,最简单的解决方案是将所有叶子都定位在 -1 的一个 fib jump 内。然后将这些叶子中的每一个视为 index[0] 并从那里找到所有跳转。 每个世代或跳跃都记录在一个集合中,直到世代包含 len(A) 或找不到更多的跳跃。

def gen_fib(n):
    fn = [0,1]
    i = 2
    s = 2
    while s < n:
        s = fn[i-2] + fn[i-1]
        fn.append(s)
        i+=1
    return fn

def new_paths(A, n, last_pos, fn):
    """
    Given an array A of len n.
    From index last_pos which numbers in fn jump to a leaf?
    returns list: set of indexes with leaves.
    """
    paths = []
    for f in fn:
        new_pos = last_pos + f
        if new_pos == n or (new_pos < n and A[new_pos]):
            paths.append(new_pos)
    return path

def solution(A):
    n = len(A)
    if n < 3:
        return 1

    # A.append(1) # mark final jump
    fn = sorted(gen_fib(100000)[2:]) # Fib numbers with 0, 1, 1, 2..  clipped to just 1, 2..
    # print(fn)
    paths = set([-1]) # locate all the leaves that are one fib jump from the start position.

    jump = 1
    while True:
        # Considering each of the previous jump positions - How many leaves from there are one fib jump away
        paths =  set([idx for pos in paths for idx in new_paths(A, n, pos, fn)])

        # no new jumps means game over!
        if not paths:
            break

        # If there was a result in the new jumps record that
        if n in paths:
            return jump
            
        jump += 1

    return -1

https://app.codility.com/demo/results/training4GQV8Y-9ES/

https://github.com/niall-oc/things/blob/master/codility/fib_frog.py

【讨论】:

【参考方案5】:

在 C 中得到 100% 的解决方案。

typedef struct state 
    int pos;
    int step;
state;
int solution(int A[], int N) 

    int f1 = 0;
    int f2 = 1;
    int count = 2;
    // precalculating count of maximum possible fibonacci numbers to allocate array in next loop. since this is C language we do not have flexible dynamic structure as in C++
    while(1)
    
        int f3 =  f2 + f1;
        if(f3 > N)
            break;
        f1 = f2;
        f2 = f3;
        ++count;
    
    int fib[count+1];
    fib[0] = 0;
    fib[1] = 1;
    int i = 2;
    // calculating fibonacci numbers in array
    while(1)
    
        fib[i] =  fib[i-1] + fib[i-2];
        if(fib[i] > N)
            break;
        ++i;
    
    // reversing the fibonacci numbers because we need to create minumum jump counts with bigger jumps
    for(int j = 0, k = count; j < count/2; j++,k--)
    
        int t = fib[j];
        fib[j] = fib[k];
        fib[k] = t;
    
    state q[N];
    int front = 0 ;
    int rear = 0;
    q[0].pos = -1;
    q[0].step = 0;
    int que_s = 1;
    while(que_s > 0)
    
        state s =  q[front];
        front++;
        que_s--;
        for(int i = 0; i <= count; i++)
        
            int nextpo = s.pos + fib[i];
            if(nextpo == N)
            
                return s.step+1;
            
            else if(nextpo > N || nextpo < 0 || A[nextpo] == 0)
           
                continue;  
            
            else
            
                q[++rear].pos = nextpo;
                q[rear].step = s.step + 1;
                que_s++;
                A[nextpo] = 0;
            
        
    
    return -1;

【讨论】:

我将用 Java 编写它并稍后进行测试。如果性能看起来更好,会更新你。【参考方案6】:

//100% 关于 codility 动态编程解决方案。 https://app.codility.com/demo/results/training7WSQJW-WTX/

class Solution 
  public int solution(int[] A) 
    int n = A.length + 1;
    int dp[] = new int[n];
    for(int i=0;i<n;i++) 
        dp[i] = -1;
    
    int f[] = new int[100005];
    f[0] = 1;
    f[1] = 1;
    for(int i=2;i<100005;i++) 
        f[i] = f[i - 1] + f[i - 2];
    
    for(int i=-1;i<n;i++) 
        if(i == -1 || dp[i] > 0) 
            for(int j=0;i+f[j] <n;j++) 
                if(i + f[j] == n -1  || A[i+f[j]] == 1) 
                    if(i == -1) 
                        dp[i + f[j]] = 1;
                     else if(dp[i + f[j]] == -1) 
                        dp[i + f[j]] = dp[i] + 1;
                     else 
                        dp[i + f[j]] = Math.min(dp[i + f[j]], dp[i] + 1);
                    
                
            
            
    

    return dp[n - 1];


【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。【参考方案7】:

Ruby 100% 解决方案

def solution(a)
  f = 2.step.inject([1,2]) |acc,e| acc[e] = acc[e-1] + acc[e-2]; break(acc) if acc[e] > a.size + 1;acc .reverse
  mins = []

  (a.size + 1).times do |i|
    next mins[i] = -1 if i < a.size && a[i] == 0

    mins[i] = f.inject(nil) do |min, j|
        k = i - j
        next min if k < -1
        break 1 if k == -1
        next min if mins[k] < 0
        [mins[k] + 1, min || Float::INFINITY].min
    end || -1
  end

  mins[a.size]
end

【讨论】:

【参考方案8】:

我已经将之前的C解决方案翻译成Java,发现性能有所提升。

import java.util.*;


class Solution 

        private static class State 

        int pos;
        int step;

        public State(int pos, int step) 

            this.pos = pos;
            this.step = step;
        
    


    public static int solution(int A[]) 

        int N = A.length;

        int f1 = 0;
        int f2 = 1;

        int count = 2;

        while (true) 

            int f3 = f2 + f1;

            if (f3 > N) 
                break;
            

            f1 = f2;
            f2 = f3;

            ++count;
        


        int[] fib = new int[count + 1];

        fib[0] = 0;
        fib[1] = 1;

        int i = 2;

        while (true) 

            fib[i] = fib[i - 1] + fib[i - 2];

            if (fib[i] > N) 
                break;
            

            ++i;
        

        for (int j = 0, k = count; j < count / 2; j++, k--) 

            int t = fib[j];

            fib[j] = fib[k];
            fib[k] = t;
        

        State[] q = new State[N];

        for (int j = 0; j < N; j++) 

            q[j] = new State(-1,0);
        

        int front = 0;
        int rear = 0;

        // q[0].pos = -1;
        // q[0].step = 0;

        int que_s = 1;

        while (que_s > 0) 

            State s = q[front];

            front++;
            que_s--;

            for (i = 0; i <= count; i++) 

                int nextpo = s.pos + fib[i];

                if (nextpo == N) 
                    return s.step + 1;
                

                //
                else if (nextpo > N || nextpo < 0 || A[nextpo] == 0) 
                    continue;
                

                //
                else 

                    q[++rear].pos = nextpo;
                    q[rear].step = s.step + 1;

                    que_s++;

                    A[nextpo] = 0;
                
            
        

        return -1;
    

【讨论】:

【参考方案9】:

100% 的 JavaScript。 灵感来自here。

function solution(A) 
  const createFibs = n => 
    const fibs = Array(n + 2).fill(null)
    fibs[1] = 1
    for (let i = 2; i < n + 1; i++) 
      fibs[i] = fibs[i - 1] + fibs[i - 2]
    
    return fibs
  
  const createJumps = (A, fibs) => 
    const jumps = Array(A.length + 1).fill(null)
    let prev = null
    for (i = 2; i < fibs.length; i++) 
      const j = -1 + fibs[i]
      if (j > A.length) break
      if (j === A.length || A[j] === 1) 
        jumps[j] = 1
        if (prev === null) prev = j
      
    
    if (prev === null) 
      jumps[A.length] = -1
      return jumps
    
    while (prev < A.length) 
      for (let i = 2; i < fibs.length; i++) 
        const j = prev + fibs[i]
        if (j > A.length) break
        if (j === A.length || A[j] === 1) 
          const x = jumps[prev] + 1
          const y = jumps[j]
          jumps[j] = y === null ? x : Math.min(y, x)
        
      
      prev++
      while (prev < A.length) 
        if (jumps[prev] !== null) break
        prev++
      
    
    if (jumps[A.length] === null) jumps[A.length] = -1
    return jumps
  
  const fibs = createFibs(26)
  const jumps = createJumps(A, fibs)
  return jumps[A.length]


const A = [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0]
console.log(A)
const s = solution(A)
console.log(s)

【讨论】:

【参考方案10】:

您应该使用队列而不是堆栈。这是广度优先搜索的一种形式,您的代码需要访问首先添加到队列中的节点以获得最小距离。 堆栈使用后进先出机制来移除项目,而队列使用先进先出机制。 我复制并粘贴了您的确切代码,但使用了队列而不是堆栈,我得到了 100% 的编码。

【讨论】:

如果您可以使用示例代码实现该场景并将您的答案发布为对社区有用的内容,那总是会更好。【参考方案11】:

100% C++ solution

更多答案my github

灵感来自here

解决方案1:自下而上,使用动态编程算法(将计算值存储在数组中)

vector<int> getFibonacciArrayMax(int MaxNum) 
    if (MaxNum == 0)
        return vector<int>(1, 0);
    vector<int> fib(2, 0);
    fib[1] = 1;
    for (int i = 2; fib[fib.size()-1] + fib[fib.size() - 2] <= MaxNum; i++)
        fib.push_back(fib[i - 1] + fib[i - 2]);
    return fib;


int solution(vector<int>& A) 
    int N = A.size();
    A.push_back(1);
    N++;
    vector<int> f = getFibonacciArrayMax(N);
    const int oo = 1'000'000;
    vector<int> moves(N, oo);
    
    for (auto i : f)
        if (i - 1 >= 0 && A[i-1])
            moves[i-1] = 1;
    
    for (int pos = 0; pos < N; pos++) 
        if (A[pos] == 0)
            continue;

        for (int i = f.size()-1; i >= 0; i--) 
            if (pos + f[i] < N && A[pos + f[i]]) 
                moves[pos + f[i]] = min(moves[pos]+1, moves[pos + f[i]]);
            
        
    
    if (moves[N - 1] != oo) 
        return moves[N - 1];
    
    return -1;

解决方案2:自上而下使用set容器:


#include <set>

int solution2(vector<int>& A) 
    int N = A.size();

    vector<int> fib = getFibonacciArrayMax(N);

    set<int> positions;
    positions.insert(N);
    for (int jumps = 1; ; jumps++)
    
        set<int> new_positions;
        for (int pos : positions)
        
            for (int f : fib)
            
                // return jumps if we reach to the start point
                if (pos - (f - 1) == 0)
                    return jumps;
                
                int prev_pos = pos - f;
                
                // we do not need to calculate bigger jumps.
                if (prev_pos < 0)
                    break;
                
                if (prev_pos < A.size() && A[prev_pos])
                    new_positions.insert(prev_pos);
            
        
        if (new_positions.size() == 0)
            return -1;
        positions = new_positions;
    

    return -1;

【讨论】:

以上是关于计算青蛙到达河对岸所需的最少跳跃次数的主要内容,如果未能解决你的问题,请参考以下文章

更好的青蛙穿越算法

第十三届蓝桥杯省赛 C++ A 组 F 题Java A 组 G题C组 H 题Python C 组 I 题——青蛙过河(AC)

POJ 1061 青蛙的约会(欧几里得扩展)

青蛙过河问题

DP青蛙过河

袋鼠过河