算法笔试利器--对数器的使用

对数器的作用

对数器是通过用大量测试数据来验证算法是否正确的一种方式。在算法笔试的时候,我们经常只能确定我们写出的算法在逻辑上是大致正确的,但是谁也不能一次性保证绝对的正确。特别是对于一些复杂的题目,例如贪心算法,我们往往无法在有限时间内用数学公式来推导证明我们程序的正确性。而且在线的OJ一般只会给出有数的几个简单的samples,可能我们的算法在这些简单的samples偶然通过了,但是在一些复杂的samples处却出现了问题。这时我们无法使用复杂的samples来分析调试我们的代码,人工设计样例来测试代码的效率又太低,而且不能确保考虑各种特殊情况。因此,能随机产生不同情况的数据的对数器就派上了用场。

对数器,简而言之,就是一个绝对正确的方法和能产生大量随机样例的随机器的组合。看到这里,有些童鞋有疑问了。既然我们知道了绝对正确的方法,直接用这个方法不就好了嘛?

请注意,算法追求的不是解决问题,而是高效率的解决问题。对于一个数组的排序,如果笔试中要求的时间复杂度是$O(NlogN)$$,但是你却写了一个冒泡排序的算法交上去了,这时OJ就会提示:

Time Limit Exceeded

而在对数器中,我们要求的绝对正确的算法是没有时间和空间复杂度的限制的,唯一的要求是确保绝对正确。因为只有绝对正确,我们才能通过样例的比对,发现我们的代码是在哪里出了错误。

相关概念

  1. 有一个你想要测的方法a
  2. 实现一个绝对正确但是复杂度不好的方法b
  3. 实现一个随机样本产生器;
  4. 实现对比算法ab的方法;
  5. 把方法a和方法b比对多次来验证方法a是否正确;
  6. 如果有一个样本使得比对出错,打印样本分析是哪个方法出错;
  7. 当样本数量很多时比对测试依然正确,可以确定方法a已经正确。

其中要注意以下几点:

  • 随机产生的样本应该是小数据集,但是要进行多次(10w+)的对比。小数据集是因为方便对比分析,多次比对是要覆蓋所有的随机情况。
  • 算法b要保持正确性。

示例

冒泡排序的对数器:

  1. 要测的方法

    public static void bubbleSort(int[] arr)
    {
        if (arr==null || arr.length < 2)
            return;
        for (int end = arr.length - 1;end>0; end--)
        {
            for (int i = 0; i < end; i++)
            {
                if (arr[i] > arr[i+1])
                    swap(arr, i, i+1);
            }
        }
    }
  2. 实现一个绝对正确,可以复杂度不是很好的方法b

    // 可以直接用一些库函数来进行测试
    public static void rightMethod(int[] arr)
    {
        Arrays.sort(arr);
    }
  3. 实现一个随机样本产生器

    // 随机数生成器
    public static int[] generateRandomArray(int size, int value)
    {
    //Math.random() -> double [0,1)
    //(int) ((size + 1) * Math.random()) -> [0,size]整数
    // 生成长度随机[0, size]的数组
    int[] arr = new int[(int) ((size+1) * Math.random())];
    for (int i = 0; i < arr.length; i++)
    {
        // 一个随机数减去另一个随机数,生成[-value, value]的随机数
        arr[i] = (int) ((value+1) * Math.random()) - (int) (value * Math.random());
    }
    return arr;
    }
  4. 实现比对的方法

    // 判断两个数组是否相等
    public static boolean isEqual(int[] arr1, int[] arr2)
    {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null))
            return false;
        if (arr1 == null && arr2 == null)
            return true;
        if (arr1.length != arr2.length)
            return  false;
        for (int i = 0; i < arr1.length; i++)
        {
            if (arr1[i] != arr2[i])
                return false;
        }
        return true;
    }
  5. 将方法a和方法b进行多次的比对
  6. 如果有一个样本使得本次比对出错,则打印该样本,分析方法错在哪里
  7. 当样本数量足够多时,比对测试依然正确,则可以确定我们写的方法a是正确的

    public static void main(String[] args) 
    {
        int testTime = 500000;
        int size = 10;
        int value = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++)
        {
            int[] arr1 = generateRandomArray(size, value);
            int[] arr2 = copyArray(arr1);
            int[] arr3 = copyArray(arr1);
            bubbleSort(arr1);
            rightMethod(arr2);
            if (!isEqual(arr1, arr2))
            {
                succeed = false;
                printArray(arr3);
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "error----");
        int[] arr = generateRandomArray(size, value);
        printArray(arr);
        bubbleSort(arr);
        printArray(arr);
    
    }

小提示

很多童鞋进行笔试前,都是背一些记在小本本上的代码,然后匆匆上阵。写出的算法的正确性完全靠OJ的判断,当程序卡在一个2000行的数组样例处出现错误时,就完全傻了……这T喵叫我怎么去进行调试分析啊。而有对数器的小伙伴就不一样了,由于使用的都是小样本,出现错误时也方面进行分析。而且进行了多次测试,确保覆蓋了所有的特殊情况。因此笔试前我们可以准备一些对数器模版,如数组排序的对数器,链表的对数器等等。后续我也会更新一些对数器的模版,有java版本和python版本。

记住哦,offer都是留给有准备的人~~

数组排序

后续版本,陆续更新中…..

点赞