算法题 寻找 MountainArray 的 peek 求解惑

2020-11-01 14:12:00 +08:00
 amiwrong123

其实这个题是 https://leetcode-cn.com/problems/find-in-mountain-array/ 的一部分: 比如,array = [1,2,3,4,5,3,1],这个数组是由严格递增和严格递减的两个有序数组构成的,它的 peek 就是 5. 现在我改一下条件,要么这个数组是严格递增和严格递减的两个有序数组构成的(这种情况返回 peek 的索引),要么这个数组就只是一个严格递增或严格递减的数组(返回-1 )。

很明显,寻找 peek 可以用二分查找。

然后我发现这个算法有两种写法:

    public int findArrayPeek(int[] array) {
        int low = 0, high = array.length-1;

        int middle = -1;
        while (low <= high && (middle = (low + high)/2)-1 > 0 && middle+1 < array.length) {
            if (array[middle-1] < array[middle] && array[middle] > array[middle+1]) {
                return middle;
            } else if (array[middle-1] < array[middle] && array[middle] < array[middle+1]) {
                low = middle+1;
            } else {
                high = middle-1;
            }
        }

        return -1;
    }

    @Test
    public void findArrayPeekTest() {
        int[] array = {1,2,3,4,5,3,1};
        System.out.println(findArrayPeek(array));
        int[] array1 = {0,1,2,4,2,1};
        System.out.println(findArrayPeek(array1));
        int[] array2 = {0,1,2,4,5,1};
        System.out.println(findArrayPeek(array2));
        int[] array3 = {0,1,2,4,5,6};
        System.out.println(findArrayPeek(array3));
        int[] array4 = {6,5,4,3,2,1};
        System.out.println(findArrayPeek(array4));
    }

首先是标准的二分查找,但循环条件中必须要上索引的保护。

    public int findArrayPeek1(int[] array) {
        int low = 0, high = array.length-1;
        
        while (low < high-1) {
            int middle = (low + high)/2;
            if (array[middle-1] < array[middle] && array[middle] > array[middle+1]) {
                return middle;
            } else if (array[middle-1] < array[middle] && array[middle] < array[middle+1]) {
                low = middle;
            } else {
                high = middle;
            }
        }

        return -1;
    }

    @Test
    public void findArrayPeek1Test() {
        int[] array = {1,2,3,4,5,3,1};
        System.out.println(findArrayPeek1(array));
        int[] array1 = {0,1,2,4,2,1};
        System.out.println(findArrayPeek1(array1));
        int[] array2 = {0,1,2,4,5,1};
        System.out.println(findArrayPeek1(array2));
        int[] array3 = {0,1,2,4,5,6};
        System.out.println(findArrayPeek1(array3));
        int[] array4 = {6,5,4,3,2,1};
        System.out.println(findArrayPeek1(array4));
    }

然后是另外一种写法如上,它的处理过程从low = middle+1;变成了low = middle;,这里循环条件也必须得变,但这里不用加索引保护。

这两个算法,变化的只有对 low high 的处理,和循环条件。但感觉原理有点不一样,但我又说不清楚。。。(这种说不清楚的感觉,应该是我没有彻底弄懂~)

或者说,对 low high 的处理怎么影响这个过程的,相应的循环条件应该怎么写?

1126 次点击
所在节点    程序员
5 条回复
hitmanx
2020-11-01 14:27:31 +08:00
"要么这个数组是严格递增和严格递减的两个有序数组构成的(这种情况返回 peek 的索引),要么这个数组就只是一个严格递增或严格递减的数组(返回-1 )。"

没看明白,你增加的这两种情况头尾判断一下(O(1))就能知道吧,比如
For A[n], n >= 3,
if A[1] > A[0]:
....if A[n-2] > A[n-1]: MoutainArray
....else: 单调递增
else: 单调递减
amiwrong123
2020-11-01 14:36:30 +08:00
@hitmanx
那啥,你得返回 peek 的索引呀,你这样只是判断这是哪种数组。。在这道 LeetCode 题里面,也是先需要获得 peek 的索引的。
hitmanx
2020-11-01 15:16:22 +08:00
@amiwrong123 我的意思是按照我的理解,你增加的额外几种情况,其实一个 O(1)的判断就完成了,剩下的又回到了原来的题目,所以并没有给题目带来任何实际的变化,比如:

if (A[1] < A[0]):
.... return -1 # monotone decreasing

if (A[n-2] < A[n-1]):
.... return -1 # monotone increasing

// same old code that finds the index within a mountain array
amiwrong123
2020-11-01 15:22:55 +08:00
@hitmanx
好吧,懂你意思啦。确实是这样的,相当于你提前判断特殊情况,然后就不用执行二分查询代码了。不过我重点还是在于这个二分查找的写法
binux
2020-11-01 15:27:06 +08:00
原理没有不一样啊,就是边界处理细节不同罢了。
二分本来就是有“自适应性”的,只要趋势和终结条件是对的,边界多一个少一个没有关系。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/720693

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX