如何找出数组中第二大的数?(一道面试算法题的思考)

  前两天面试的过程中问道的一个算法题,题目不算难,但是一步步分析优化的过程我觉得挺受启发,所以拿出来分享一下。

  题目要求很简单,就是找出给定数组中第二大的数,略微思考之后我给出了下面的答案,即使用执行两次迭代,使用冒泡排序将两个最大值移动到数组末尾,数组中倒数第二个值即为要求的第二大的值。

    public static int getSecondBiggestNum(int []arr)
        {
            int temp;
            for (int j = 0; j < 2; j++)
            {
                for (int i = 0; i < arr.Length - 1; i++)
                {
                    if (arr[i] > arr[i + 1])
                    {
                        temp = arr[i];
                        arr[i] = arr[i + 1];
                        arr[i + 1] = temp;
                    }
                }
            }
            return arr[arr.Length - 2];
        }    

  时间复杂度为2n,效率还可以,使用数组{8,6,2,7,3}来测试返回结果也没有问题,但是忽略了一个细节,题目中没有说明数组中的值是不重复的,所以对于{8,8,6,2,7,3}来说,返回结果就是错误的。

  于是我考虑可以在两次迭代之后判断最后两个值是否相等,如果相等的话再继续第三次冒泡排序,这样以此类推,总能找到第二大的,但是很快这种想法被否定了,因为在极端情况下(如{8,8,8,8,8,3})算法的复杂度是n2,效率太低。

  那么接着分析,首先肯定是要有一轮迭代找出最大值的,那么既然已经找出最大值了,是不是可以在第二轮迭代中直接将数组中其他小于最大值的数移到一个临时数组中呢,然后对临时数组再做一次冒泡就可以求出最大值,时间复杂度大概是3n,貌似可以接受。

      public static int getSecondBiggestNum(int []arr)
        {
            int temp;
            int []tempArr = new int[arr.Length-1];
            for (int i = 0; i < arr.Length - 1; i++)
            {
                if (arr[i] > arr[i + 1])
                {
                    temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
            for (int i = 0; i < arr.Length - 1; i++)
            {
                if (arr[i] != arr[arr.Length-1])
                {
                    tempArr[i] = arr[i];
                }
            }
            for (int i = 0; i < tempArr.Length - 1; i++)
            {
                if (tempArr[i] > tempArr[i + 1])
                {
                    temp = tempArr[i];
                    tempArr[i] = tempArr[i + 1];
                    tempArr[i + 1] = temp;
                }
            }

            return tempArr[tempArr.Length - 1];
        }

  但是新的问题又来了,因为引入了新的临时数组变量,增加了空间复杂度。那么不用临时数组是不是可以呢?既然最大值已经求出来了,那么完全可以在第二轮迭代的时候通过与最大值的比较得出第二大的值。

public static int getSecondBiggestNum(int []arr)
        {
            int temp =0;
            //第一轮迭代求最大值
            for (int i = 0; i < arr.Length - 1; i++)
            {
                if (arr[i] > arr[i + 1])
                {
                    temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
            //第二轮迭代求第二大值
            for (int i = 0; i < arr.Length - 1; i++)
            {
                if (temp <arr[i] && arr[i] != arr[arr.Length-1] ) 
          {
            temp
= arr[i];
          }
       }

  return temp;
}

  貌似可以了,但是仔细看第一次迭代,只是求最大值,没有必要进行排序,排序也是要耗费内存降低效率的,所以再定义一个临时变量记录最大值。

        public static int getSecondBiggestNum(int []arr)
        {
            int temp =0;
            int biggestNum = arr[0];
            //第一轮迭代求最大值
            for (int i = 0; i < arr.Length ; i++)
            {
                if (biggestNum < arr[i])
                    biggestNum = arr[i];
            }
            //第二轮迭代求第二大值
            for (int i = 0; i < arr.Length; i++)
            {
                if (temp < arr[i] && arr[i] != biggestNum)
                {
                    temp = arr[i];
                }
            }
            return temp;
        }

  这次终于可以了,但是使用{1,1,1,1,1,1}这种数组验证时,返回了0,明显是错误的值,这时候应该给出提示,按照具体需求返回一个值,具体的代码这里就不再写了。

  这道题确实不难实现,很容易找到思路,但是实现功能是一方面,分析还有没有可优化的地方是另一方面,而这个分析优化的过程也是自己不断进步的过程。

  (委托系列文章稍后会继续)

原文地址:https://www.cnblogs.com/Legolas/p/Find-SecondLargest-Number.html