这个leetcode题目是这样的:
算法
这个数组的特色是会造成一个山峰,而题目要求返回这个山峰的索引。数组
若是按题意来想,很快就想到一个解决办法:把a[i]跟a[i+1]做比较,若是a[i+1]比a[i]小了,那就是到山峰了,返回i便可。这个算法须要遍历数组,一直找到开始变小的状况。性能
对于这个算法,能够这样实现(由于山峰必定不会在最后出现,因此没必要担忧A[i+1]会越界):设计
int peakIndexInMountainArray(int* A, int ASize) { int i; for (i = 0; i < ASize; i ++) { if (A[i] > A[i+1]) { break; } } return i; }
把这个实现提交到leetcode,反馈是这样的:
code
从反馈来看,这并非一个速度很快的算法设计,但这不表明它不可行,实际上,不少状况,咱们宁愿简单一点的实现也不要复杂但性能很佳的设计,由于有时简单就是最重要的。可是,这里的训练就是要追求更好的设计,而不是考虑实际状况。blog
小程以前已经提过:通常天然的想法,都不是高效的算法设计。索引
因此,除了理解题意外,还须要有独立的时间来进行算法设计(而不是简单地按着题意来解答),这对于其它场景也是同样,使用什么套路,有时候是很关键的。leetcode
另外,高效是相对的,检测是否是高效有两个办法,一个办法是运行起来看执行速度,通常须要对比其它解决办法,另外一个办法是分析时间复杂度,这两个都不是小程重点讲解的内容,读者不该该陷入时间复杂度的分析当中,而应该把时间放在经典的算法套路的理解上。io
对于这个题目,若是换一个角度,那实际就是求最大值的索引,依然能够套用小程以前反复提到的“分治”与“重用”的套路。class
假如一开始就决定使用分治与重用的套路,那思考的过程多是这样的:
1. 这里的分治,只须要简单的分,从中间一分为二便可。 2. 而后就是重用,也就是标准做业,就是本身,即返回最大值的索引。
总体的思路是这样的:把数列分为两部分,而后重用本身,求得两部分的最大值的索引,而后对这两个索引对应的值再比较一次,最终返回总体的最大值的索引。
分到何时结束呢?分到只有一个元素时结束,或者,在程序实现时再考虑均可以。在算法设计上,能够不考虑细节。
这个算法的实现是这样的:
int maxindex(int* A, int b, int e) { if (b < e) { int m = (b+e)>>1; int l = maxindex(A, b, m); int r = maxindex(A, m+1, e); int ret = l; if (A[l] < A[r]) { ret = r; } return ret; } return b; } int peakIndexInMountainArray(int* A, int ASize) { return maxindex(A, 0, ASize-1); }
把这个代码提交到leetcode,反馈以下(可见速度大幅提高了):
以上是同时使用“分治”与“重用”,并且“重用”的标准做业是自身,所设计出来的算法。
实际上,还有其它套路能够优雅地解决这个问题,好比“排除”的套路,不断地排除掉不符合答案的区域,最终定位到答案。对于这个“排除”套路,能够用折半查找的办法来实现。
对于“排除”套路,小程另做介绍,这里只给出对应的代码实现并结束本文的主要内容:
public class Solution { public int PeakIndexInMountainArray(int[] A) { int ret = 0; int l = 0, r = A.Length - 1; while (l < r) { int m = (l+r) >> 1; if (A[m] < A[m + 1]) l = m; else if (A[m-1] > A[m]) r = m; else { ret = m; break; } } return ret; } }
简单来讲。分治,就是大事化小,难点在于,怎么分,跟分到什么程度为止。重用,就是反复使用标准做业,可能要先假设有某种能力,再使用它,并真的达到某种能力,须要有空手套白狼的勇气。你本身领悟一下。
总结一下,本文再次介绍“分治”与“重用”套路的使用,分治能使问题规模变小并最终获得解决,重用能简化思考的复杂度并让思路简洁,这两个套路常常出如今算法设计当中。