一个数组先升序再降序,求最大值?例如[1,2,2,2,2,3,1],用最优时间复杂度,算法实现获取最大值3
一个数组先升序再降序,求最大值?例如[1,2,2,2,2,3,1],用最优时间复杂度,算法实现获取最大值3
===更新
去掉了递归、数组复制,函数返回值改为索引以方便处理空数组的情况
public class MaxFinder {
public static void main(String[] args) {
int[] arr = {2,3,3,3,7,13,22};
System.out.println(arr[getMaxPos(arr)]);
}
public static int getMaxPos(int[] arr) {
int len = arr.length;
if(len >= 2) {
int min = 0,
max = len-1; // 最大值位置的索引范围闭区间
int mid, left;
while(max-min > 1) {
mid = min + (max-min)/2;
left = mid-1;
while(left >= min) {
if(arr[left] > arr[mid]) {
max = left;
break;
}else if(arr[left] < arr[mid]) {
min = mid;
break;
}else {
if(left == min) {
min = mid;
}
left--;
}
}
}
return arr[max]>arr[min]? max : min;
}else if(len == 1) {
return 0;
}else{
return -1;
}
}
}
===原答案
public class Test {
public static void main(String[] args) {
int[] arr = {1,2,2,2,2,3,1};
System.out.println(getMax(arr));
}
public static int getMax(int[] arr) {
int len = arr.length;
if(len > 2) {
int mid = len/2,
left = mid -1;
while(left >= 0) {
if(arr[left] > arr[mid]) {
return getMax(Arrays.copyOfRange(arr, 0, left+1));
}else if(arr[left] < arr[mid]){
return getMax(Arrays.copyOfRange(arr, mid, arr.length));
}else{
left--;
}
}
// come here only if arr[0] to arr[mid] are all the same
return getMax(Arrays.copyOfRange(arr, mid, arr.length));
}else if(len == 2) {
return arr[0] > arr[1] ? arr[0] : arr[1];
}else{
return arr[0];
}
}
}
可能是最短的代码:
function getMax(arr) {
if (arr.length < 2)
return arr[0]
else{
let mid = parseInt(arr.length/2)
let flag = arr[mid-1] - arr[mid]
if(flag > 0)
return getMax(arr.slice(0,mid))
else if(flag < 0)
return getMax(arr.slice(mid))
else {
let a = getMax(arr.slice(0,mid))
let b = getMax(arr.slice(mid))
return a>b?a:b;
}
}
}
性能测试(1,000,000
个数,与.sort()
比速度)结果如下图
测试代码如下:
var a = [];
var max = 1000000;
var k = parseInt(Math.random()*max);
for(var i = 0;i<k;i++)
a[i] = i*2+ (Math.random()>0.5?1:-1);
for(var i = k;i<max;i++)
a[i] = (max-i)*2+ (Math.random()>0.5?1:-1);
console.log(a);
var time1 = new Date()
console.log(getMax(a),new Date() - time1)
var time2 = new Date()
console.log(a.sort((x,y)=>x<y?-1:1)[a.length-1],new Date() - time2)
另外我监测了一下内存占用, 结果都是16~18MB 左右, 本来还担心因为用了slice
而导致拷贝复制过多引起内存占用过大, 看来 js 的垃圾回收还不错.
性能方便是 O(lgN)
需要注意的是 js 的数组.sort()
是先转 String 再进行比较,因此会出现[10,2].sort()
还是[10,2]
的结果,所以要添加一个比较函数.
2018-11-1
更新:参考评论的意见,增加对[2,2,2,2,2,...,2,2,3]
这类长重复字符串的处理, 但实测对于随机字符串性能变化不大
function getMax(arr) {
if (arr.length < 2)
return arr[0]
else{
let right = parseInt(arr.length/2)
let left = right - 1
while(right < arr.length && arr[left] == arr[right])
right++
if( right == arr.length || arr[left] > arr[right])
return getMax( arr.slice(0,left+1) )
else
return getMax( arr.slice(right) )
}
}
两种方案吧
第一种遍历一次,找到开始变小的那个i,a[i-1]就是最大值,复杂度O(n);
第二种,二分法,先去中间的三个值,判断这三个值是否单调,如果不是单调,中间那个就是最大值,否则根据单调方向,找到下一段数组进行二分查找。复杂度O(logn)
===更新===
经评论区@superman369指出,忽略了重复元素的情况,由于遇到重复的元素时没有足够的信息判断应该去左半边查找还是右半边查找,只能分两趟,取最大的那个。
public class Main {
public static void main(String[] args) {
int[] arr = {2, 2, 2, 3, 2, 2, 2, 2, 2, 2};
System.out.println(getMax(arr));
}
public static int getMax(int[] arr) {
int left = 0;
int right = arr.length - 1;
// 二分查找,遇到相同元素时,向右收缩
while (left < right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] > arr[mid + 1]) {
right = mid;
} else if (arr[mid] < arr[mid + 1]) {
left = mid + 1;
} else {
if (arr[left] > arr[right]) {
right--;
} else {
left++; //遇到相同元素,且无法判断方向时,向右收缩
}
}
}
int max = arr[left];
left = 0;
right = arr.length - 1;
// 二分查找,遇到相同元素时,向左收缩
while (left < right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] > arr[mid + 1]) {
right = mid;
} else if (arr[mid] < arr[mid + 1]) {
left = mid + 1;
} else {
if (arr[left] < arr[right]) {
left++;
} else {
right--; //遇到相同元素时,且无法判断方向时,向左收缩
}
}
}
return max > arr[left] ? max : arr[left];
}
}
两个二分查找其实是完全一样的,只是在遇到相同元素时,一个向左收缩,一个向右收缩。但是每一部分都使用二分查找。
===原答案===
这就是个很典型的二分查找的题啊:
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 2, 2, 2, 3, 1};
System.out.println(getMax(arr));
}
public static int getMax(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] > arr[mid + 1]) {
right = mid;
} else {
left = mid + 1;
}
}
return arr[left];
}
}
我们从右侧往左侧向最大值逼近就行了。
3 回答2.6k 阅读✓ 已解决
3 回答4.1k 阅读✓ 已解决
8 回答3.6k 阅读
4 回答2.8k 阅读✓ 已解决
2 回答2.7k 阅读✓ 已解决
3 回答2.5k 阅读✓ 已解决
3 回答1.7k 阅读✓ 已解决