更好的青蛙穿越算法

Posted

技术标签:

【中文标题】更好的青蛙穿越算法【英文标题】:A Better Frog Crossing Algorithm 【发布时间】:2013-09-24 07:50:08 【问题描述】:

我正在通过 Codility 解决以下问题:

一只小青蛙想去河的另一边。青蛙当前位于位置 0,并且想要到达位置 X。树叶从树上掉到河面上。 给定一个非空的零索引数组 A,由 N 个表示落叶的整数组成。 A[K] 表示在时间 K 时一片叶子落下的位置,以分钟为单位。 目标是找到青蛙可以跳到河对岸的最早时间。只有当树叶出现在从 1 到 X 过河的每个位置时,青蛙才能过河。

我使用了以下解决方案,但只得到了 81 分:

代码在 C# 中。

using System;
using System.Collections.Generic;

class Solution 
    public int solution(int X, int[] A) 
        bool[] tiles = new bool[X];

        for (int i = 0; i < A.Length; i++)
        
            tiles[A[i] - 1] = true;

            bool complete = true;

            for (int j = 0; j < tiles.Length; j++)
            
                if (!tiles[j])
                
                    complete = false;
                    break;
                
            

            if (complete)
                return i;
        

        return -1;
    

我的算法运行时间为 O(NX)。有什么更好的算法只需要 O(N)?

【问题讨论】:

【参考方案1】:

这是Python3中的解决方案

def solution(target, data_list):
    check_set = set()
    for i, value in enumerate(data_list):
        if value <= target:
             check_set.add(target)
        if len(check_set) == data_list:
            return i

    return -1

【讨论】:

这似乎没有添加其他答案尚未添加的任何新内容,请添加一些额外细节,说明这与已发布的其他答案有何不同。【参考方案2】:

这是我的 100% 解决方案,时间复杂度为 O(N)。请记住,您可以在这些作业中使用泛型和 Linq。

public int solution(int X, int[] A)

    SortedSet<int> leaves = new SortedSet<int>();
    for (int i = 0; i < A.Length; i++)
    
        leaves.Add(A[i]);
        if (leaves.Count() == X) return i;
    
    return -1;

【讨论】:

【参考方案3】:

这在 O(N) 中运行并返回 100%:

    public int solution(int X, int[] A) 
            Hashtable spaces = new Hashtable();
            int counter = 0;
            foreach(int i in A)
            
                //Don't insert duplicate keys OR 
                //keys greater than requested path length
                if (!spaces.ContainsKey(i) && i <= X)
                    spaces.Add(i, i);

                //If Hashtable contents count = requested number of leaves,
                //then we're done
                if (spaces.Count == X)
                    return (counter);

                counter++;
            

            return -1;
    

【讨论】:

【参考方案4】:

把你的代码改成这样:

public int solution(int X, int[] A) 

    bool[] tiles = new bool[X];
    int todo = X;

    for (int i = 0; i < A.Length; i++)
    
        int internalIndex = A[i] - 1;
        if (internalIndex < X && !tiles[internalIndex])
        
            todo--;
            tiles[internalIndex] = true;
        

        if (todo == 0)
            return i;
    

    return -1;

该算法只需要O(A.length) 时间,因为它始终跟踪我们还需要用树叶填充多少“洞”。

这里是怎么做的?

todo 是构建树叶“桥梁”所需的树叶数量。每当一片叶子落下时,我们首先检查在它落下的位置是否已经一片叶子。如果不是,我们减少todo,填补这个漏洞并继续。 只要todo 到达0,整条河都被覆盖了;)

【讨论】:

谢谢!我知道有比我更好的解决方案,我只是无法跳出框框思考。 例如,给定 X = 7 和数组 A 使得: A[0] = 1 A[1] = 3 A[2] = 1 A[3] = 4 A[4] = 2 A[5] = 5 函数应该返回 3,你能解释一下吗? 函数为什么要返回3?即使在 all 叶子索引A 你给了之后,河流仍然没有被叶子覆盖!一般:7 条大小的河流怎么能在 3 分钟后被树叶覆盖? 不错。只是添加“if (internalIndex 【参考方案5】:

这是我基于 HashSet 的变体。 结果是here

public int solution(int X, int[] A)

  HashSet<int> hash = new HashSet<int>();

  for(int i=0;i<A.Length;i++)
  
    if(A[i]<=X)
      
          hash.Add(A[i]);

           if(hash.Count == X)
             return i;
      
  
  return -1;

【讨论】:

您的算法看起来非常简单直接。您的解决方案看起来像排列检查。你是如何得出这个想法的?老实说,我只是在阅读 Codility 问题时迷路了。【参考方案6】:

下面是另一种使用字典的方法:

public int solution(int X, int[] A) 
        int result = -1;
         Dictionary<int, int> jumps = new Dictionary<int, int>();
            int res =  (X*(X+1))/2;
            int sum = 0;

            for (int i = 0; i < A.Length; i++)
            
                if (!jumps.ContainsKey(A[i]))
                
                    sum = sum + A[i];
                    jumps.Add(A[i],i);
                    if (sum == res)
                    
                        result = i;
                        break;
                    
                
            
            return result;
    

上面的代码是创建直到 X 的整数之和,即如果 X=5,那么我们使用高斯公式 (X*(X+1))/2 计算 (1+2+3+4+5),这个将允许我们稍后知道是否添加或发生了总跃点。该值将与添加到字典中的不同步骤的总和进行比较。 根据描述“只有当叶子出现在从 1 到 X 过河的每个位置时,青蛙才能越过。” 我尝试使用列表而不是 dic,但它在某些性能测试中失败了,当我们通过键进行查找时,Dictionay 对象的强大功能就出现了。

【讨论】:

【参考方案7】:

100% 使用 C#

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Collections;

  public int solution(int X, int[] A)

    // write your code in C# 5.0 with .NET 4.5 (Mono)

    int N = A.Length;
    int step = 0;
    List<int> k = new List<int>();


    for (int i = 0; i < X; i++)
    
        k.Add(0);
    

    //Inserts an element into the ArrayList at the specified index.
    for (int i = 0; i < N; i++)
    
        int diff = A[i] - 1;

        k[diff] = A[i];

        if (i >= X-1 && (k.Contains(0) == false))
        
           return i;

        

    


    return -1;


【讨论】:

【参考方案8】:

这是我在 C99 中的解决方案,可能不是最优雅的。但我希望是可读和可以理解的。这是我的测试的链接。 https://codility.com/demo/results/demoNGRG5B-GMR/

int solution(int X, int A[], int N) 

    if (X <= 1) return 0; //if we only need to go 1 step we are already there
    if (N == 0) return -1;//if we don't have timing we can't predict 

    int B[X+1];

    for (int i=0; i <= X; i++) 
        B[i] = -1; //i set default value to -1 so i can see later if we missed a step. 
    

    for (int i=0; i < N; i++) 
        if (A[i] <= X && (B[A[i]] == -1 || B[A[i]] > i)) B[A[i]] = i; //prepare my second array here with timing data 
    

    int max_min = 0; //store the highest timing later on.

    for (int i=1; i <= X; i++) 
        if (B[i] == -1) return -1; //if we have any elements with -1 then we didn't cross the river
        if (max_min < B[i]) max_min = B[i]; //keep setting the highest timing seen the steps.
    

    return max_min;

【讨论】:

我认为在for (int i=0; i &lt;= X; i++) int B[X]; 之后的比较 iB 中的整数数组。稍后您将使用从 1 开始的基于数组的索引,它还将访问数组边缘之外的一个整数。 @MichaelPetch 感谢您指出错误。由于某种原因,它没有在代码中出错,知道为什么会这样吗? 由于是缓冲区溢出,我只能假设他们在编译和运行应用程序时不会进行任何类型的运行时内存检查。我知道他们甚至没有检测到内存泄漏。您的程序很可能可以正常工作,因为在您的情况下缓冲区溢出并不是灾难性的,并且程序可以继续。但这仍然是一个错误,只是codility 没有看到。【参考方案9】:

我碰巧这个练习有点晚了。除了C90,我看到很多语言都被覆盖了。像许多人一样,我确实通过创建辅助阵列找到了解决方案。我使用了典型的calloc,然后是free。我的第一个解决方案与其他人发布的类似:

int solution(int X, int A[], int N)

    int *n = calloc(X, sizeof(*A));
    int index;

    for (index = 0; index < N; index++) 
        if (n[A[index] - 1] == 0) 
            n[A[index] - 1] = 1;
            if (--X == 0) 
                free(n);
                return index;
            
        
    

    free(n);
    return -1;

我意识到我可以完全摆脱没有第二个数组,因为我们正在处理有符号整数并且codility 网站也说Elements of input arrays can be modified。它还说 each element of array A is an integer within the range [1..X] 。由于原始输入数组A 总是有正数,我可以利用它来发挥我的优势。我可以使用数组int A[]ints 的符号位 来表示我是否已经(或没有)看到特定的叶子位置。新版本的代码使用函数abs来处理每个数组元素中的绝对值,以达到索引的目的。我设置符号位以表明我已经访问过特定的叶子位置,并且我使用abs 检查索引处的实际值 without 以了解我是否已经访问过该位置。我的最终解决方案如下:

int solution(int X, int A[], int N)


    int index;
    int leaftimeidx;

    for (index = 0; index < N; index++) 
        leaftimeidx = abs(A[index]) - 1;

        if (A[leaftimeidx] > 0) 
            A[leaftimeidx] *= -1;

            if (--X == 0)
                return index;
        
    
    return -1;

我的解决方案的两种变体都通过了所有测试。

【讨论】:

【参考方案10】:

Ruby 解决方案(100/100 on Codility):

def solution(x, a)

  check_array = (0..a.length).to_a
  check_array.each  |i| check_array[i]=0 

  a.each_with_index do |element, i|

      if (check_array[element]==0)
          check_array[element]=1
          x -= 1
      end

      return i if (x==0)
  end

  return -1

end

【讨论】:

【参考方案11】:

100% 得分:FrogRiverOne 的 PHP 代码:Ajeet Singh

function solution($X, $A) 
    for ($i = 0; $i < count($A); $i++)        
        if (!isset($position_achieved[$A[$i]]))
            $X--;   // reduce X by one position is achieved
            $position_achieved[$A[$i]] = true;
        
        if (!$X)
            return $i;
        
    
    return -1;    

【讨论】:

【参考方案12】:

这是一个简单的 C++ 解决方案:

int solution(int X, vector<int> &A)

  vector<bool> removed( X );

  for( size_t i = 0; i < A.size(); i++ )
  
    if( removed[ A[i] - 1 ] == false )
    
      removed[ A[i] - 1 ] = true; 
      X--;

      if(X == 0)
      
        return i;
       
    
  

  return -1; 

【讨论】:

【参考方案13】:

这是我提出的 Python 解决方案(100/100 on Codility):

def solution(X, A):
    N = len(A)
    count = [0] * (X+1)
    steps = 0
    for k in xrange(N):
        if not count[A[k]]:
            count[A[k]] = 1
            steps += 1
            if steps == X:
                return k
    return -1

【讨论】:

【参考方案14】:

这让我得到 100/100

public int solution(int X, int[] A)

    int z = -1;

    long combA = ((long) X)*(((long) X) + 1)/2;
    long sumA = 0;

    int[] countA = new int[X];

    for (int i = 0; i < A.Length; i++)
    
        countA[A[i] - 1] += 1;

        if (countA[A[i] - 1] > 1)
        
            countA[A[i] - 1] = 1;
        
        else
        
            sumA += A[i];
        


        if (sumA == combA)
        
            z = i;
            break;
        

    

    return z;

【讨论】:

【参考方案15】:

虽然我同意你得到 100 分,但它并不满足所有测试用例

对于1、3、1、4、2、3、5、4的样本数据

如果您尝试找到 3,它应该返回 5,但给出的答案会引发异常

一个更正的版本是,因为在位置 2 失败的叶子在第四分钟后得到满足

    public int solution(int X, int[] A)
    
                int steps = -1;
        bool[] tiles = new bool[X];

        int todo = X;
        for (int i = 0; i < A.Length; i++)
        
            steps += 1;
            int internalIndex = A[i] - 1;
            if (internalIndex < tiles.Length)
            
                if (!tiles[internalIndex])
                

                    todo--;

                    tiles[internalIndex] = true;

                
            
            if (todo == 0)

                return steps;
        
        return -1;
    

【讨论】:

以上是关于更好的青蛙穿越算法的主要内容,如果未能解决你的问题,请参考以下文章

pku_oj: w11-03讨厌的青蛙(C++枚举算法)

“星际穿越”版的农民世界?相比而言,MetaKrypton拥有更好的前景

“星际穿越”版的农民世界?相比而言,MetaKrypton拥有更好的前景

“星际穿越”版的农民世界?相比而言,MetaKrypton拥有更好的前景

如何保证使用 ortools 计算的路线不会穿越特定区域?

穿越火线错误码1,1,540 [穿越火线]