Wednesday, March 8, 2017

Maximum Number in Mountain Sequence

Version#1(naiive)
我用的传统的二分查找方法,在mid点处分别求它的prevSlope和postSlope,这个过程中用一个slope函数来处理一些边界情况,防止mid 等于start 或 end 的时候会越界。
譬如如果去掉边界处理,在如下testcase中就会报错(虽然我觉得这个testcase并不严格符合题目的描述)。

public class Solution {
    /**
     * @param nums a mountain sequence which increase firstly and then decrease
     * @return then mountain top
     */
    public int mountainSequence(int[] nums) {
        // Write your code here
        if (nums == null || nums.length == 0) {
            return Integer.MAX_VALUE;
        }
        int start = 0;
        int end = nums.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (slope(mid, nums) > 0 && slope(mid + 1, nums) < 0) {
                return nums[mid];
            } else if (slope(mid, nums) > 0) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (slope(start, nums) > 0 && slope(start + 1, nums) < 0) {
            return nums[start];
        } else {
            return nums[end];
        }
    }
    private int slope(int node, int[] nums) {
        if (node == 0) {
            return 1;
        }
        if (node == nums.length) {
            return -1;
        }
        return nums[node] - nums[node - 1];
    }
}

Version#2
看了答案以后发现这道题实际上应该用三分法查找。关于三分法在网上找到了一篇讲解讲得很清楚。
三分查找适合用来查找最值。
三分查找
于是修改答案:
public class Solution {
    /**
     * @param nums a mountain sequence which increase firstly and then decrease
     * @return then mountain top
     */
    public int mountainSequence(int[] nums) {
        // Write your code here
        if (nums == null || nums.length == 0) {
            return Integer.MAX_VALUE;
        }
   
        int start = 0;
        int end = nums.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            int midmid = mid + (end - mid) / 2;
            if (nums[mid] > nums[midmid]) {
                end = midmid - 1;
            } else if (nums[mid] < nums[midmid]) {
                start = mid + 1;
            } else {
                start = mid;
                end = midmid;
            }
        }
        if (nums[start] > nums[end]) {
            return nums[start];
        } else {
            return nums[end];
        }
    }
}

助教您好:
(刚刚看到ladder里面新加了一些题目。。。结果后面的题被锁住做不了了, 好悲伤!)
我想请问这个http://www.jiuzhang.com/solutions/maximum-number-in-mountain-sequence/答案使用的三分法里面,
int m1 = left + (right - left) / 2;
int m2 = right - (right - m1) / 2;
我把这里m2展开以后发现是 right / 2 + (right + left) / 4, 不太理解它的意义是什么?
我自己的答案写的是:
int m2 = m1 + (right - m1) / 2, 代表着在中点和右端点之间再取一次中点,但是这样就在[1,2,4,8,6,3] 的testcase里面报错了。不知道是什么问题?
谢谢!
P.S.今天把我自己的答案改成int m2 = m1 + (right - m1) / 2 + 1 之后发现运行正确了。然而还是不理解这么取的意义是什么。。。
P.P.S 我发现这个问题发生在start 和end 中间只间隔一个数的情况下,譬如:
index 3 4 5
value 8 6 3
此时left = #3, right = #5, 所以m1 == m2 == #4, 就会造成 6是最大值的错误答案。而老师的标答中通过 right - (right - m1) / 2 这个公式,使得start 和 end中间只相隔一个数的情况下,m1 和 m2 也不会相等。 不知道我理解的对不对?
但是我感觉这个边界情况如果第一次做的话很难想得周全。。。不知道可不可以借鉴老师在二分法中的模板,把while 循环的条件改成 left + 2 < right就退出, 然后再进行判断,这样的做法是不是更加general一点呢?
另外很想问题下老师求解m2的公式如果是第一次做的话是怎么想出来的,思路是什么? 谢谢!

Version#3 我自己想的更改退出条件的做法
这个自己想出来的做法虽然看着有点丑陋但是思路非常直接,而且没有很多edge case需要考虑,感觉比较适合我。
写的过程中唯一一个bug是没有考虑到只有一个输入值的情况,这里在最前边加以讨论了。同理也可以放在最后当做 left == right的一个情况讨论。
public class Solution {
    /**
     * @param nums a mountain sequence which increase firstly and then decrease
     * @return then mountain top
     */
    public int mountainSequence(int[] nums) {
        // Write your code here
        if (nums == null || nums.length == 0) {
            return Integer.MAX_VALUE;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        int left = 0;
        int right = nums.length - 1;
        while (left + 2 < right) {
            int mid = left + (right - left) / 2;
            int midmid = mid + (right - mid) / 2;
            if (nums[mid] < nums[midmid]) {
                left = mid + 1;
            } else if (nums[mid] > nums[midmid]) {
                right = midmid - 1;
            } else {
                left = mid + 1;
                right = midmid - 1;
            }
        }
        int tempMax = Math.max(nums[left], nums[right]);
        if (right == left + 1) {
            return tempMax;
        } else {
            return Math.max(tempMax, nums[left + 1]);
        }
    }
}

No comments:

Post a Comment