这是一道本人面试时手撕算法环节的真题。
给定一个整数数组,这个数组中的数字有这样一个规律:前面部分非严格递增,后面部分非严格递减,写出找到其中最大值的算法。
说明:所谓非严格递增是指arr[i]<=arr[i+1],相应的非严格递减是指arr[i]<=arr[i+1]。例如下面的这些例子:
int[] arr1 = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 5, 5, 5, 5, 4, 4, 4, 4, 2, 2, 2, 1, 1};
int[] arr2 = new int[]{7, 7, 7, 5, 5, 5, 5, 4, 4, 4, 4, 2, 2, 2, 1, 1};
int[] arr3 = new int[]{1, 2, 3, 4, 5, 5, 5, 5, 5, 6, 7, 7, 7};
int[] arr4 = new int[]{7, 7, 7, 7, 7, 7};
题解:
要求数组中的最大值,最简单的办法就是遍历,从头开始,直至出现arr[i+1]<arr[i],那么此时的arr[i]当然就是数组中的最大值。但是面试的时候面试官肯定不想让你这样做,因为这个时间复杂度为O(N)。
由于前半部分递增,后半部分递减,我们可以想到二分查找,因为二分查找算法用来查找有序或者部分有序的数组中的元素时间复杂度为log(N)。
下面是java代码,其中比较曲折的一点在于,每一次找到中间的元素之后都需要通过while循环来找到这一段相等数字的左右边界。例如:2,2,3,3,3,4,如果mid位置的值是中间的那个3,那么就需要找到左右两边与他不相等的值2和4,这样就能够判断出来,此时的mid位于爬坡的位置,需要将下边界指向4的位置。
代码中还有详细的注释,保证理解。
public class FindMaxNum {
/**
* 查找先非严格递增后非严格递减的数组中的最大值
* 利用二分查找法
*/
private static int findMaxNum(int[] arr) {
int index = 0;
int length = arr.length;
for (int i = 0, j = length - 1; i < j; ) {//i是下边界,j是上边界
int mid = (i + j) / 2;
//下面两个while循环找到左右不相等的边界或者数组的边界
int left = mid - 1;
while (arr[left] == arr[mid] && left > 0) {
left--;
}
int right = mid + 1;
while (arr[right] == arr[mid] && right < length - 1) {
right++;
}
//通过判断当前mid坐在位置,上坡,下坡或者坡顶来找到最大值
if (arr[mid] > arr[left] && arr[mid] < arr[right]) {
i = right;
} else if (arr[mid] < arr[left] && arr[mid] > arr[right]) {
j = left;
} else if (arr[mid] >= arr[left] && arr[mid] >= arr[right]) {//这个地方需要以等号作为条件,因为可能最大值在边界处
index = mid;
break;
}
}
return arr[index];
}
public static void main(String[] args) {
int[] arr1 = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 5, 5, 5, 5, 4, 4, 4, 4, 2, 2, 2, 1, 1};
int[] arr2 = new int[]{7, 7, 7, 5, 5, 5, 5, 4, 4, 4, 4, 2, 2, 2, 1, 1};
int[] arr3 = new int[]{1, 2, 3, 4, 5, 5, 5, 5, 5, 6, 7, 7, 7};
int[] arr4 = new int[]{7, 7, 7, 7, 7, 7};
int maxNum = findMaxNum(arr1);
System.out.println(maxNum);
}
}