寻找峰值

在LeetCode中有一道搜索问题:https://leetcode-cn.com/explore/interview/card/top-interview-questions-medium/50/sorting-and-searching/99/
如果所用数据结构是数组,要求在对数时间复杂度内完成,那么往往会用到分而治之算法,经常将数组一分为二,递归求解。问题的关键是怎么分解数组。

public int findPeakElement(int[] nums) {
    if (nums == null || nums.length == 0) {
        return -1;
    }
    if (nums.length == 1) {  // 特殊数组
        return 0;
    }

    return findPeakElement(nums, 0, nums.length - 1);

}

private int findPeakElement(int[] nums, int p, int q) {
    if (p > q) { 
        return -1;
    }
    int m = p + (q - p) / 2;
   // 得到的中点m,可能落在数组的两段,考虑到这种情况,需要分类讨论。
    if (m == 0 ) {
        if (nums[m] > nums[m + 1]) {
            return 0;
        } else {
            return findPeakElement(nums, m + 1, q);
        }
    }
    if (m == nums.length -1) {
        if (nums[m] > nums[m - 1]) {
            return m;
        } else {
            return findPeakElement(nums, p, m - 1);
        }
    }

    if (nums[m] > nums[m + 1] && nums[m] > nums[m - 1]) {
        return m;
    } else if (nums[m] < nums[m + 1]) {
        return findPeakElement(nums, m + 1, q);
    } else {
        return findPeakElement(nums, p, m - 1);
    }
}

假设数组非常大,那么会有什么问题呢?递归的深度很大,导致StackOverFlow,那么怎么办呢?改成递推的写法。

public int findPeakElement(int[] nums) {
    if (nums == null || nums.length == 0) {
        return -1;
    }
   // 不可或缺,如果删掉下面的if语句,
  // 那么 if (nums[m] > pre && nums[m] > next) 必须改成
  // 那么 if (nums[m] >= pre && nums[m] >= next) 
    if (nums.length == 1) {
        return 0;
    }
    int len = nums.length, p = 0, q = nums.length - 1;
    while (p <= q) {
        int m = p + (q - p) / 2;
        int pre = m - 1 >= 0 ? nums[m - 1] : Integer.MIN_VALUE;
        int next = m + 1 < len ? nums[m + 1] : Integer.MIN_VALUE;
        if (nums[m] > pre && nums[m] > next) {
            return m;
        }
        if (nums[m] < next) {
            p = m + 1;
        } else {
            q = m - 1;
        }
    }
    return -1;
}

这种写法需要注意:
1)while的结束条件是p<q还是p<=q?
2)对于数组越界情况,使用了特殊值来代替,这样比上段代码简洁了不少,但这样会带来问题吗?不会。因为m元素是什么值,不可能比min_value更小,只能是大于等于。

再看一种颇为神奇的写法:)

public int findPeakElement(int[] nums) {
    if (nums == null || nums.length == 0) {
        return -1;
    }

    int p = 0, q = nums.length - 1;
    while (p <= q) {
        if (p == q) {
            return p;
        }
        int m = p + (q - p) / 2;
        if (nums[m] < nums[m + 1]) {
            p = m + 1;
        } else {
            q = m;
        }
    }
    return -1;
}     

这段代码和之前的二分搜索不太一样,它并没有判断m节点是不是峰值,而是通过不断缩小范围来逼近峰值,这提供了一种分而治之的新思路。但这乍一看并不好理解,其中原理是什么呢?让我们换个角度来思考:该算法会排除掉一半的元素,保证解一定在另一边的元素里,反复循环,那么每次保留的元素均为之前的一半,肯定可以缩小成1个元素,那么该点必是峰值。
这种算法的运行时间要慢些,因为总是要把范围缩小成1才得出结果。

2019-02-25:
重做这道题目,发现以前写得代码不够优雅。不如临时所写:

public int findPeakElement(int[] arr) {
    if (arr == null || arr.length == 0) {
        return -1;
    }
    int p = 0;
    int q = arr.length - 1;
    while (p <= q) {
        int x = p + (q - p) / 2;
        boolean isGreaterThanRight = x + 1 >= arr.length || arr[x] > arr[x + 1];
        boolean isGreaterThanLeft = x - 1 < 0 || arr[x] > arr[x - 1];
        if (isGreaterThanRight && isGreaterThanLeft) {
            return x;
        }
        if (!isGreaterThanRight) {
            p = x + 1;
        } else {
            q = x - 1;
        }
    }
    return -1;
}

再来一个递归版本

public int findPeakElement(int[] arr, int p, int q) {
    if (arr == null || p > q) {
        return -1;
    }
    int x = p + (q - p) / 2;
    System.out.println(p + "\t" + q  + "\t" + x);
    boolean isGreaterThanRight = x + 1 >= arr.length || arr[x] > arr[x + 1];
    boolean isGreaterThanLeft = x - 1 < 0 || arr[x] > arr[x - 1];
    if (isGreaterThanRight && isGreaterThanLeft) {
        return x;
    }
    if (!isGreaterThanRight) {
        return findPeakElement(arr, x + 1, q);
    } else {
        return findPeakElement(arr, p, x - 1);
    }
}

这两段代码清晰的关键是运用了处理边界的技巧,以及布尔值技巧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容

  • 本文试图从一个简单的小题目出发,引入算法的若干基本概念,重点引入一种方法:分治法,并且给出表述算法效率的记号。本文...
    Bintou老师阅读 2,231评论 0 3
  • 峰值元素是指其值大于左右相邻值的元素。 给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],...
    Houtasu阅读 1,145评论 0 0
  • 162. 寻找峰值 描述 峰值元素是指其值大于左右相邻值的元素。 给定一个输入数组 nums,其中 nums[i]...
    GoMomi阅读 508评论 0 0
  • 峰值元素是指其值大于左右相邻值的元素。给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找...
    vbuer阅读 87评论 0 0
  • 峰值元素是指其值大于左右相邻值的元素。 给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],...
    小白学编程阅读 563评论 0 0