peak finding 问题

文章目录

    • 出处
    • 分析
    • 1D finding
    • 2D finding
    • 参考

出处

《算法》上面1.4.18,1.4.19两道题目
peak finding 问题_第1张图片
这两道题目就是peak finding 问题,找到局部最大/局部最小元素的问题其实是等价的。这里让我们找局部最小的元素
如果你只写了找到局部最大的元素算法,现在想要找局部最小的元素,那么只需要把a中的元素都乘上-1就行了

另外请注意我们讨论的数组是distinct的,如果有重复数字, O ( l o g N ) O(logN) O(logN)的算法是实现不了的

分析

暴力法很容易写出

但是我看到提示
答:检查数组的中间值 a[N/2] 以及和它相邻的元素 a[N/2-1] 和 a[N/2+1]。如果 a[N/2] 是一个局部最小值则算法终止;否则则在较小的相邻元素的半边中继续查找。
感觉很迷惑,这个在较小的相邻元素里面的半边继续查找的依据是什么?不会漏掉正确答案吗?例如
1 2 3 4 5 6 3 5这种组合,按照上面的依据来说,应该是往左边查找,但是正确答案是右边的3才对

看了一些资料以后发现上述两道题目叙述得其实不完整。。
因为上面题目没有对边界条件进行说明。。导致我以为要找的元素下标必须满足i∈[1,length-1]
完整的题目应该是:对于边界元素,它只需要小于相邻的边界元素,那么这个边界元素就是局部最小的

我们先看一般的情况吧,对于任意一个数列,我们从随机的位置开始寻找局部最小
那么情况可以穷举出来

  1. 当前元素就是局部最小
  2. 当前元素不是局部最小,那么此时,总是会有一边以上的元素小于当前元素。可以自己写一下各种情况

看图,图片里面是找局部最大,不过没有关系,思想是一样的,这里是总有一边以上的元素会大于当前元素
peak finding 问题_第2张图片

那么上面的提示也说得通了,对于1 2 3 4 5 6 3 5这种组合,N=8,A[N/2]=5 第一次判断的元素是5,4<5<6,4比6小,那么会往左边查找,查找过程中它只有两种情况

  1. 碰到一个局部最优的元素
    我们的任务完成了
  2. 碰到第一个元素
    第一个元素一定是局部最小的元素

如果一路上没有碰到局部最优的元素,那么就会走到第一个元素,此时第一个元素必定是局部最小的元素

为什么?递推一下

假设第一个元素不是局部最小的元素,那么a[0]>a[1]
此时a[1]如果不是局部最小的元素,那么a[1]>a[2]

此时如果a[N/2-1]不是局部最小元素,那么a[N/2-1]>a[N/2],矛盾的地方出现了
回到这个例子上,我们之所以往左边找,就是因为a[N/2-1]

这说明我们如果走到了第一个元素,那么第一个元素一定是局部最小的

可能你会怀疑,这只是一边的情况,其实你往右推导也是一样的

进一步可以得出结论,如果a[j]不是局部最小的元素,那么总有一边的相邻元素会小于a[j],由上结论得:
我们一定可以在较小的相邻元素那边找到一个局部最小的元素 ,它或者是边界元素,或者不是
nice啊哈哈

上述结论要求数组是distinct的

1D finding

暴力法很容易写出,O(N)
加了一些边界条件让算法更稳定。伪代码会更简洁

    public static int func(int[] a) {
        if (a.length == 0)
            return -1;
        if (a.length == 1)
            return a[0];

        for (int i = 1; i < a.length - 1; i++) {
            if (a[i] < a[i - 1] && a[i] < a[i + 1])
                return i;
        }
       
        if (a[0] < a[1])
            return 0;
        else if (a[a.length - 1] > a[a.length - 1 - 1])
            return a.length - 1;
        return -1;
    }

现在我们有了上述的理论,可以优化算法了,很容易想到二分的思想

    public static int func3(int[] a) {
        if (a.length == 0)
            return -1;

        if (a.length == 1)
            return 0;

        if (a.length == 2)
            if (a[0] < a[1])
                return 0;
            else return 1;

        int lo = 0, hi = a.length - 1;
        int N = (lo + hi) / 2;
        while (N != 0 && N != a.length - 1) {
            if (a[N] < a[N - 1] && a[N] < a[N + 1])
                return N;
            else if (a[N - 1] < a[N + 1])
                hi = N - 1;
            else
                lo = N + 1;

            N = (lo + hi) / 2;
        }
        return N;
    }

可以把上面改写成递归的形式
时间复杂度现在降为 O ( l o g N ) O(logN) O(logN)了,分析请看下图,每次一迭代都问题规模变成一半
peak finding 问题_第3张图片
当子问题的规模等于 n / 2 k n/2^k n/2k的时候,我们设 n = 2 k n=2^k n=2k,那么就能推出来这是O(lgn)的算法

2D finding

暂时略,还没写到

参考

感谢这位作者维护的repo
https://github.com/reneargento/algorithms-sedgewick-wayne/blob/f31578352b7774e857a664bf8768bca2200e75e0/src/chapter1/section4/Exercise18_LocalMinimum.java

mit的讲义
extension://bfdogplmndidlpjfhoijckpakkdjkkil/pdf/viewer.html?file=http://courses.csail.mit.edu/6.006/spring11/lectures/lec02.pdf
配套课程是
https://www.youtube.com/watch?v=HtSuA80QTyo
还没看,是Google带我来的

SO上的答案
https://stackoverflow.com/questions/12238241/find-local-minima-in-an-array

主定理
https://zh.wikipedia.org/wiki/%E4%B8%BB%E5%AE%9A%E7%90%86

https://blog.csdn.net/woshilsh/article/details/89429130

你可能感兴趣的:(算法)