在一个更大的数组中查找另一个数组

30

最近我被要求为一份工作编写3个测试程序。这些程序将只使用核心的Java API和我选择的任何测试框架。应在适当的位置实现单元测试。

虽然我没有收到任何反馈,但我认为他们不喜欢我的解决方案(否则我会从他们那里听到),所以我决定在这里展示我的程序,并问这个实现是否可以被认为是好的,如果不是,那么为什么?

为了避免混淆,我现在只问了第一个问题。

实现一个函数,在更大的另一个数组中找到一个数组。 它应该接受两个数组作为参数,并返回第二个数组首次完全出现在第一个数组中的索引。例如,findArray([2,3,7,1,20], [7,1]) 应该返回 2。

我没有尝试寻找任何现有的解决方案,而是想自己做。

可能的原因: 1. 应该是静态的。 2. 应该使用行注释而不是块注释。 3. 没有先检查空值(我知道,太晚发现了)。 4. ?

更新:
很多原因已经被提出,很难选择一个答案,因为许多答案都有好的解决方案。正如@adietrich提到的那样,我倾向于相信他们想让我展示核心API的知识(他们甚至要求编写函数,而不是编写算法)。

我认为保住这份工作最好的方法是提供尽可能多的解决方案,包括: 1. 使用Collections.indexOfSubList()方法实现,以显示我对核心集合API的了解。 2. 实现一种蛮力方法,但提供更优雅的解决方案。 3. 使用搜索算法实现,例如Boyer-Moore。 4. 使用System.arraycopy()和Arrays.equal()的组合来实现。虽然不是性能最佳的解决方案,但它将展示我对标准数组例程的了解。

感谢大家的答案!
更新结束。

以下是我编写的程序:

package com.example.common.utils;

/**
 * This class contains functions for array manipulations.
 * 
 * @author Roman
 *
 */
public class ArrayUtils {

    /**
     * Finds a sub array in a large array
     * 
     * @param largeArray
     * @param subArray
     * @return index of sub array
     */
    public int findArray(int[] largeArray, int[] subArray) {

        /* If any of the arrays is empty then not found */
        if (largeArray.length == 0 || subArray.length == 0) {
            return -1;
        }

        /* If subarray is larger than large array then not found */
        if (subArray.length > largeArray.length) {
            return -1;
        }

        for (int i = 0; i < largeArray.length; i++) {
            /* Check if the next element of large array is the same as the first element of subarray */
            if (largeArray[i] == subArray[0]) {

                boolean subArrayFound = true;
                for (int j = 0; j < subArray.length; j++) {
                    /* If outside of large array or elements not equal then leave the loop */
                    if (largeArray.length <= i+j || subArray[j] != largeArray[i+j]) {
                        subArrayFound = false;
                        break;
                    }
                }

                /* Sub array found - return its index */
                if (subArrayFound) {
                    return i;
                }

            }
        }

        /* Return default value */
        return -1;
    }

}

测试代码:

package com.example.common.utils;

import com.example.common.utils.ArrayUtils;

import junit.framework.TestCase;

public class ArrayUtilsTest extends TestCase {

    private ArrayUtils arrayUtils = new ArrayUtils();

    public void testFindArrayDoesntExist() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {8,9,10};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistSimple() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {3,4,5};

        int expected = 2;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistFirstPosition() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {1,2,3};

        int expected = 0;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistLastPosition() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {5,6,7};

        int expected = 4;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayDoesntExistPartiallyEqual() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {6,7,8};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistPartiallyEqual() {

        int[] largeArray = {1,2,3,1,2,3,4,5,6,7};
        int[] subArray = {1,2,3,4};

        int expected = 3;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArraySubArrayEmpty() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArraySubArrayLargerThanArray() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {4,5,6,7,8,9,10,11};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistsVeryComplex() {

        int[] largeArray = {1234, 56, -345, 789, 23456, 6745};
        int[] subArray = {56, -345, 789};

        int expected = 1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

}

为什么如果任何一个数组为空,那么就会找不到呢?空数组总是任何数组的成员,对吧?同时,空数组也是空数组的成员。 - nanda
这是一个好观点,但在这种情况下返回0是不正确的...我认为在这里返回-1更合适... - Roman
15个回答

42

“只使用核心Java API”的要求也可能意味着他们想看看你是否会重新发明轮子。因此,除了您自己的实现外,为了安全起见,您可以提供一行代码解决方案:

public static int findArray(Integer[] array, Integer[] subArray)
{
    return Collections.indexOfSubList(Arrays.asList(array), Arrays.asList(subArray));
}

指出给定的示例包含无效的数组文字,可能是好的也可能不是一个好主意。


6
如果你使用Google的Guava库,那就更好了:Bytes.indexOf(byte[], byte[])。 - Tomer
如果数组非常大,比如1GB,并且子数组位于开头怎么办? - Anthony
1
@adietrich indexOfSubList 方法的复杂度是多少? - deadbug
Guava的这种方法采用了暴力破解。 - AbuNassar
1
这里有一个小提示,对于理解此解决方案的人来说,需要注意一点:如果传递给您的两个数组是 int[](或任何其他原始类型数组)的形式,那么此解决方案将无法工作,这正是 OP 在他的工作问题中所遇到的情况。 - UzumakiL

5
Clean and improved code 

public static int findArrayIndex(int[] subArray, int[] parentArray) {
    if(subArray.length==0){
        return -1;
    }
    int sL = subArray.length;
    int l = parentArray.length - subArray.length;
    int k = 0;
    for (int i = 0; i < l; i++) {
        if (parentArray[i] == subArray[k]) {
            for (int j = 0; j < subArray.length; j++) {
                if (parentArray[i + j] == subArray[j]) {
                    sL--;
                    if (sL == 0) {
                        return i;
                    }

                }

            }
        }

    }
    return -1;
}

这一行 for (int i = 0; i < l; i++) { 可以改成 for (int i = 0; i <= l; i++) {. 否则它无法找到最后一个元素。我尝试过了。 - User

4

要在一个更大的整数数组中查找一个整数数组,您可以使用与在更大的字符串中查找子字符串相同类型的算法。对于这个问题,已经有许多已知的算法(请参见维基百科)。特别是 Boyer-Moore 字符串搜索算法适用于大型数组。你试图实现的算法效率不高(维基百科将其称为“朴素”实现)。

对于您的问题:

  1. 是的,这样的方法应该是静态的
  2. 无所谓,那是品味的问题
  3. 空值检查可以被包含在内,或者您应该在 JavaDoc 中声明不允许空值,或者 JavaDoc 应该声明当任一参数为空时会抛出 NullPointerException。

3

嗯,就我所知:

  1. 是的,应该是静态的。

  2. 如果一家公司抱怨这个问题,那么它不值得为之工作。

  3. 是的,但你会怎么做?返回?还是抛出异常?按照现有的方式它将抛出异常。

  4. 我认为主要问题在于你的代码不够优雅。内部循环中有太多检查。冗余检查太多。

这只是我的初步想法:

public int findArray(int[] largeArray, int[] subArray) {

    int subArrayLength = subArray.length;

    if (subArrayLength == 0) {
        return -1;
    }

    int limit = largeArray.length - subArrayLength;

    int i=0;

    for (int i = 0; i <= limit; i++) {
        boolean subArrayFound = true;

        for (int j = 0; j < subArrayLength; j++) {
            if (subArray[j] != largeArray[i+j]) {
                subArrayFound = false;
                break;
            }

        /* Sub array found - return its index */
        if (subArrayFound) {
            return i;
        }
    }

    /* Return default value */
    return -1;
}

你可以为第一个元素保留该检查,这样你就不必为数组中的每个元素设置布尔值和for循环的开销。那么你会看到

public int findArray(int[] largeArray, int[] subArray) {

    int subArrayLength = subArray.length;

    if (subArrayLength == 0) {
        return -1;
    }

    int limit = largeArray.length - subArrayLength;

    for (int i = 0; i <= limit; i++) {
        if (subArray[0] == largeArray[i]) {
            boolean subArrayFound = true;

            for (int j = 1; j < subArrayLength; j++) {
                if (subArray[j] != largeArray[i+j]) {
                    subArrayFound = false;
                    break;
                }

            /* Sub array found - return its index */
            if (subArrayFound) {
                return i;
            }
        }
    }

    /* Return default value */
    return -1;
}

但正如其他人指出的那样,该算法本身相当暴力。他们可能正在寻找像 Boyer-Moore 这样的东西。 - EboMike
1
你的代码有错误(变量i声明了两次)。 - Java Main

3

以下是使用KMP模式匹配算法的方法。此解决方案需要O(n+m)时间复杂度,其中n = 大数组的长度m = 子数组的长度。如需更多信息,请查看:

https://en.wikipedia.org/wiki/KMP_algorithm

暴力搜索需要O(n*m)的时间复杂度。我刚刚检查了Collections.indexOfSubList方法,它也是O(n*m)。
public static int subStringIndex(int[] largeArray, int[] subArray) {
    if (largeArray.length == 0 || subArray.length == 0){
      throw new IllegalArgumentException();
}
    if (subArray.length > largeArray.length){
      throw new IllegalArgumentException();
}

    int[] prefixArr = getPrefixArr(subArray);
    int indexToReturn = -1;

    for (int m = 0, s = 0; m < largeArray.length; m++) {
      if (subArray[s] == largeArray[m]) {
        s++;
      } else {
        if (s != 0) {
          s = prefixArr[s - 1];
          m--;
        }
      }
      if (s == subArray.length) {
        indexToReturn = m - subArray.length + 1;
        break;
      }
    }

    return indexToReturn;
  }

  private static int[] getPrefixArr(int[] subArray) {
    int[] prefixArr = new int[subArray.length];
    prefixArr[0] = 0;

    for (int i = 1, j = 0; i < prefixArr.length; i++) {
      while (subArray[i] != subArray[j]) {
        if (j == 0) {
          break;
        }
        j = prefixArr[j - 1];
      }

      if (subArray[i] == subArray[j]) {
        prefixArr[i] = j + 1;
        j++;
      } else {
        prefixArr[i] = j;
      }

    }
    return prefixArr;
  }

1

之前发布的一些稍微优化过的代码:

public int findArray(byte[] largeArray, byte[] subArray) {
    if (subArray.length == 0) {
        return -1;
    }
    int limit = largeArray.length - subArray.length;
    next:
    for (int i = 0; i <= limit; i++) {
        for (int j = 0; j < subArray.length; j++) {
            if (subArray[j] != largeArray[i+j]) {
                continue next;
            }
        }
        /* Sub array found - return its index */
        return i;
    }
    /* Return default value */
    return -1;
}

0

我想用三种方法来实现:

  1. 不使用任何导入,即使用纯Java语句。

  2. 使用JAVA核心API - 在某种程度上或在很大程度上。

  3. 使用字符串模式搜索算法,如KMP等(可能是最优化的方法)。

以上都展示了1、2和3。这是我自己的第二种方法:

public static void findArray(int[] array, int[] subArray) {

        if (subArray.length > array.length) {
            return;
        }

        if (array == null || subArray == null) {
            return;
        }

        if (array.length == 0 || subArray.length == 0) {
            return;
        }

        //Solution 1
        List<Integer> master = Arrays.stream(array).boxed().collect(Collectors.toList());
        List<Integer> pattern = IntStream.of(subArray).boxed().collect(Collectors.toList());

        System.out.println(Collections.indexOfSubList(master, pattern));

        //Solution2
        for (int i = 0; i <= array.length - subArray.length; i++) {
            String s = Arrays.toString(Arrays.copyOfRange(array, i, i + subArray.length));

            if (s.equals(Arrays.toString(subArray))) {
                System.out.println("Found at:" + i);
                return;
            }
        }
        System.out.println("Not found.");
    }

0

首先是可能的原因:

  1. 是的。并且使用了一个带有私有构造函数的final类。
  2. 根本不应该使用这种类型的注释。代码应该是自解释的。
  3. 你基本上是通过访问length字段来隐式检查null,这将抛出NullPointerException。只有在largeArray.length == 0subArray == null的情况下才会发生这种情况。

更多潜在原因:

  • 该类不包含任何用于数组操作的函数,与文档所说的相反。
  • 该方法的文档非常简洁。应该说明在什么情况下会抛出哪些异常(例如NullPointerException),以及如果第二个数组未找到或为空时应该期望哪种返回值。
  • 代码比必要的复杂。
    1. 为什么第一个元素的相等性如此重要,以至于它有自己的检查?
    2. 在第一个循环中,假设第二个数组将被找到,这是无意的。
    3. 不需要的变量和跳转(booleanbreak),进一步降低了可读性。
    4. largeArray.length <= i+j难以理解。应该在循环之前进行检查,同时提高性能。
    5. 我会交换subArray[j] != largeArray[i+j]的操作数。对我来说更自然。
    6. 总体而言太长了。
  • 测试代码缺少更多边缘情况(null数组,第一个数组为空,两个数组都为空,第一个数组包含在第二个数组中,第二个数组包含多次等)。
  • 为什么最后一个测试用例的名称是testFindArrayExistsVeryComplex
练习缺少的是数组参数的组件类型规范,以及方法签名。组件类型是原始类型还是引用类型会有很大的区别。aditrich的解决方案假定为引用类型(因此可以进一步改进为泛型),而我的假定为原始类型(int)。
所以这是我的尝试,重点放在代码上/忽略文档和测试:
public final class ArrayUtils {
    // main method

    public static int indexOf(int[] haystack, int[] needle) {
        return indexOf(haystack, needle, 0);
    }

    // helper methods

    private static int indexOf(int[] haystack, int[] needle, int fromIndex) {
        for (int i = fromIndex; i < haystack.length - needle.length; i++) {
            if (containsAt(haystack, needle, i)) {
                return i;
            }
        }
        return -1;
    }

    private static boolean containsAt(int[] haystack, int[] needle, int offset) {
        for (int i = 0; i < needle.length; i++) {
            if (haystack[i + offset] != needle[i]) {
                return false;
            }
        }
        return true;
    }

    // prevent initialization

    private ArrayUtils() {}
}

0
    byte[] arr1 = {1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 1, 3, 4, 56, 6, 7};
    byte[] arr2 = {9, 1, 3};

    boolean i = IsContainsSubArray(arr1, arr2);

 public static boolean IsContainsSubArray(byte[] Large_Array, byte[] Sub_Array){
    try {
        int Large_Array_size, Sub_Array_size, k = 0;

        Large_Array_size = Large_Array.length;
        Sub_Array_size = Sub_Array.length;

        if (Sub_Array_size > Large_Array_size) {
            return false;
        }
        for (int i = 0; i < Large_Array_size; i++) {
            if (Large_Array[i] == Sub_Array[k]) {
                k++;
            } else {
                k = 0;
            }
            if (k == Sub_Array_size) {
                return true;
            }
        }
    } catch (Exception e) {
    }
    return false;
}

0
int findSubArr(int[] arr,int[] subarr)
{
    int lim=arr.length-subarr.length;

    for(int i=0;i<=lim;i++)
    {
        int[] tmpArr=Arrays.copyOfRange(arr,i,i+subarr.length);
        if(Arrays.equals(tmpArr,subarr))
            return i;   //returns starting index of sub array
    }
    return -1;//return -1 on finding no sub-array   
}

更新:

通过重复使用相同的 int 数组实例:

int findSubArr(int[] arr,int[] subarr)
{
    int lim=arr.length-subarr.length;
    int[] tmpArr=new int[subarr.length];
    for(int i=0;i<=lim;i++)
    {
        System.arraycopy(arr,i,tmpArr,0,subarr.length);
        if(Arrays.equals(tmpArr,subarr))
          return i; //returns starting index of sub array
    }
    return -1;//return -1 on finding no sub-array   

}

2
每次迭代都创建一个新数组?!? - EboMike
非常简短易懂的解决方案,但正如EboMike所指出的那样,需要创建太多的数组。 - Roman
这消除了(相对便宜的)创建空数组,但并未消除(昂贵的)数组复制。 - Marc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接