二分答案总结

二分答案是binary search里面比较难的题型,也就是,直接求一个最值很难,但是我们可以很简单的在解空间做判定性问题,比如能不能,行不行,大了还是小了,return true or false,从而缩小解空间,记住,这种方法有个前提条件就是有单调递增或者递减的性质,才能用。这也是binary search使用的条件;

Wood Cut

有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k。当然,我们希望得到的小段越长越好,你需要计算能够得到的小段木头的最大长度。

Example

样例 1

输入:
L = [232, 124, 456]
k = 7
输出: 114
Explanation: 我们可以把它分成114cm的7段,而115cm不可以

思路:直接求,比较难求,但是我们可以二分解空间;

K 800  400  ..... 8 7 6

L 1   2 ......      x-1 x x+1

也就是如果L持续增大,那么能够切出来的整块在减少,那么就有个递增和递减的关系,L越大,K越小;反之亦然;

那么就可以用二分法;把一个求解最值的问题,变成一个判定性问题;

public class Solution {
    /**
     * @param L: Given n pieces of wood with length L[i]
     * @param k: An integer
     * @return: The maximum length of the small pieces
     */
    public int woodCut(int[] L, int k) {
        if(L == null || L.length == 0 || k <= 0) {
            return 0;
        }
        int maxlen = 0;
        for(int i = 0; i < L.length; i++) {
            maxlen = Math.max(maxlen, L[i]);
        }
        int start = 0; int end = maxlen;
        
        while(start + 1 < end) {
            int mid = start + (end - start) / 2;
            if(canCut(L, mid) >= k){
                start = mid;
            } else {
                end = mid;
            }
        }
        
        if(canCut(L, end) < k) {
            return start;
        }
        return end;
    }
    
    private int canCut(int[] L, int s) {
        int count = 0;
        for(int i = 0; i < L.length; i++) {
            count += L[i]/s;
        }
        return count;
    }
}

Copy Books

给定 n 本书, 第 i 本书的页数为 pages[i]. 现在有 k 个人来复印这些书籍, 而每个人只能复印编号连续的一段的书, 比如一个人可以复印 pages[0], pages[1], pages[2], 但是不可以只复印 pages[0], pages[2], pages[3] 而不复印 pages[1].

所有人复印的速度是一样的, 复印一页需要花费一分钟, 并且所有人同时开始复印. 怎样分配这 k 个人的任务, 使得这 n 本书能够被尽快复印完?

返回完成复印任务最少需要的分钟数.

Example

样例 1:

输入: pages = [3, 2, 4], k = 2
输出: 5
解释: 第一个人复印前两本书, 耗时 5 分钟. 第二个人复印第三本书, 耗时 4 分钟.

样例 2:

输入: pages = [3, 2, 4], k = 3
输出: 4
解释: 三个人各复印一本书.

Challenge

时间复杂度 O(nk)

思路:

二分答案,直接求不好求,那么我们就问,如果给你无穷多的人,你需要多久? Max(Length of page[i]), 如果给你1个人,你需要多久,sum (Pages[i]) ,那么问题来了,你需要求给你k个人情况下的,你需要多久。很显然,这又是个递增递减的关系;

人越多,需要的时间越少,人越少需要的时间越多;

public class Solution {
    /**
     * @param pages: an array of integers
     * @param k: An integer
     * @return: an integer
     */
    public int copyBooks(int[] pages, int k) {
        if(pages == null || pages.length == 0 || k <= 0) {
            return 0;
        }
        int upper = 0;
        int lower = 0;
        for(int i = 0; i < pages.length; i++) {
            upper += pages[i];
            lower = Math.max(lower, pages[i]);
        }
        
        int start = lower, end = upper;
        while(start + 1 < end) {
            int mid = start + (end - start) / 2;
            if(canCopy(pages, mid) > k) {
                start = mid;
            } else {
                // canCopy(pages, mid) <= k;
                end = mid;
            }
        }
        
        if(canCopy(pages, start) > k) {
            return end;
        }
        return start;
    }
    
    private int canCopy(int[] pages, int timelimit) {
        int count = 0;
        int sum = 0;
        int i = 0;
        while(i < pages.length) {
            // 用sum + pages[i]来判断下一个是否满足条件;
            while(i < pages.length && sum + pages[i] <= timelimit) {
                sum += pages[i];
                i++;
            }
            count++;
            sum = 0;
        }
        return count;
    }
}

Copy Books II

给定 n 本书, 每本书具有相同的页数. 现在有 k 个人来复印这些书. 其中第 i 个人需要 times[i] 分钟来复印一本书. 每个人可以复印任意数量的书. 怎样分配这 k 个人的任务, 使得这 n 本书能够被尽快复印完?

返回完成复印任务最少需要的分钟数.

Example

样例 1:

输入: n = 4, times = [3, 2, 4]
输出: 4
解释: 第一个人复印 1 本书, 花费 3 分钟. 第二个人复印 2 本书, 花费 4 分钟. 第三个人复印 1 本书, 花费 4 分钟.

思路:还是二分答案的思想,跟I很类似,给定时间T内,能否用K个人抄完N本书,如果K不变,T就越小,那么抄的书也就<N,如果T越大,那么抄的书数目就>N. CopyBooks就是在timelimit的条件下,所有人都拼命同时抄书,看能抄写多少本,然后跟N比较,从而判断时间是多了还是少了。

public class Solution {
    /**
     * @param n: An integer
     * @param times: an array of integers
     * @return: an integer
     */
    public int copyBooksII(int n, int[] times) {
        if(n < 0 || times == null || times.length == 0) {
            return 0;
        }
        
        // 求速度最慢的人,也就是时间花的最多的人;
        int max = times[0]; 
        for(int i = 1; i < times.length; i++) {
            max = Math.max(max, times[i]);
        }
        
        int start = 1; 
        int end = n * max; // 最慢的一个人去抄N本书;
        while(start + 1 < end) {
            int mid = start + (end - start) / 2;
            if(copyBooks(times, mid) >= n) {
                end = mid;
            } else {
                start = mid;
            }
        }
        if(copyBooks(times, start) < n) {
            return end;
        }
        return start;
    }
    
    private int copyBooks(int[] times, int timelimit) {
        int count = 0;
        for(int i = 0; i < times.length; i++) {
            count += timelimit / times[i];
        }
        return count;
    }
}

Sqrt(x)

实现 int sqrt(int x) 函数,计算并返回 x 的平方根。

Example

样例 1:
	输入:  0
	输出: 0


样例 2:
	输入: 3
	输出: 1
	
	样例解释:
	返回对x开根号后向下取整的结果。

样例 3:
	输入: 4
	输出: 2

思路:binary search,注意0的判断和long防止溢出;标准模板写的。

public class Solution {
    /**
     * @param x: An integer
     * @return: The sqrt of x
     */
    public int sqrt(int x) {
        if(x < 0) {
            return -1;
        }
        if(x == 0) {
            return 0;
        }
        
        long start = 1; long end = x; // long防止溢出;
        while(start + 1 < end) {
            long mid = start + (end - start) / 2;
            if(mid * mid > x) {
                end = mid;
            } else {
                start = mid;
            }
        }
        
        if(end * end > x) {
            return (int)start;
        } else {
            return (int)end;
        }
    }
}

Sqrt(x) II

实现 double sqrt(double x) 并且 x >= 0
计算并返回x开根后的值。

Example

例1:

输入: n = 2 
输出: 1.41421356

例2:

输入: n = 3
输出: 1.73205081

思路:还是二分,但是注意特殊处理一下,如果数字属于(0,1), end的值取1.0,因为0.1的sqrt为0.316,这个大于了0.1的范围;

public class Solution {
    /**
     * @param x: a double
     * @return: the square root of x
     */
    public double sqrt(double x) {
        if(x < 0) {
            return -1;
        }
        
        if(x == 0) {
            return 0;
        }
        
        double start = 0; 
        // 数字属于(0,1)返回,上界需要取1,比如0.1 sqrt是0.3,超过了0.1;上界要超过x;
        double end = Math.max(x, 1.0);
        double eps = 1e-10;
        while(start + eps < end) {
            double mid = start + (end - start) / 2;
            
            if(mid*mid > x) {
                end = mid;
            } else {
                start = mid;
            }
        }
        
        if(end*end > x) {
            return start;
        }
        return end;
    }
}

Find the Duplicate Number

给出一个数组 nums 包含 n + 1 个整数,每个整数是从 1 到 n (包括边界),保证至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

Example

样例 1:

输入:
[5,5,4,3,2,1]
输出:
5

样例 2:

输入:
[5,4,4,3,2,1]
输出:
4

Notice

1.不能修改数组(假设数组只能读)
2.只能用额外的O(1)的空间
3.时间复杂度小于O(n^2)
4.数组中只有一个重复的数,但可能重复超过一次

思路:发现规律,P如果是重复的数,

如果<=P的个数>P,那么重复的数就是end = P,重复的数在左边;

如果<=P的个数,小于P,那么start = P,  重复的数在右边

public class Solution {
    /**
     * @param nums: an array containing n + 1 integers which is between 1 and n
     * @return: the duplicate one
     */
    public int findDuplicate(int[] nums) {
        if(nums == null || nums.length == 0) {
            return 0;
        }
        
        int min = 0; int max = 0;
        for(int i = 0; i < nums.length; i++) {
            min = Math.min(min, nums[i]);
            max = Math.max(max, nums[i]);
        }
        
        int start = min; int end = max;
        while(start + 1 < end) {
            int mid = start + (end - start) / 2;
            if(count(nums, mid) > mid) { // 注意是重复的数count是大于自己;
                end = mid;
            } else {
                // count(nums, mid) < mid;
                start = mid;
            }
        }
        
        if(count(nums, start) > start) { // 注意是重复的数count是大于自己;
            return start;
        }
        return end;
    }
    
    private int count(int[] A, int num) {
        int count = 0;
        for(int i = 0; i < A.length; i++) {
            if(A[i] <= num) {
                count++;
            }
        }
        return count;
    }
}

Maximum Average Subarray II

给出一个整数数组,有正有负。找到这样一个子数组,他的长度大于等于 k,且平均值最大。

Example

例1:

输入:
[1,12,-5,-6,50,3]
3
输出:
15.667
解释:
 (-6 + 50 + 3) / 3 = 15.667

例2:

输入:
[5]
1
输出:
5.000

Notice

保证数组的大小 >= k

思路:直接求,不要计算,那么我们发现题目要求最大值,并且average的长度> k,那么我们先不管长度,我们先问,subarray(i,j)能够求的最大值会是多少;

(ai + .. + aj) / (j-i+1) >= avg,这个是否成立,那么转换为(ai - avg) + .. ( aj - avg) >=0 

那么问题转换为B[i] 组数里面,有没有B[j] - B[i] , j - i + 1 >= k 也就是 i <= j - k + 1; 使得B[j] - B[i] >=0

那么就是一个解空间的搜索问题了,上面的B数组可以写成了一个canAverage的函数,那么就是求能否有avg,使得k

因为解空间肯定是 从小到大的avg,刚开始是可以,后面不行;

有则start = mid 否则 end = mid; 

public class Solution {
    /**
     * @param nums: an array with positive and negative numbers
     * @param k: an integer
     * @return: the maximum average
     */
    public double maxAverage(int[] nums, int k) {
        if(nums == null || nums.length == 0 || k <= 0) {
            return 0.0;
        }
        
        double min = Integer.MAX_VALUE;
        double max = Integer.MIN_VALUE;
        
        for(int i = 0; i < nums.length; i++) {
            min = Math.min(min, nums[i]);
            max = Math.max(max, nums[i]);
        }
        
        // 注意题目有double,output有精度要求,那么这里binary search就有精度要求;
        double start = min; double end = max;
        while(start + 1e-5 < end) {
            double mid = start + (end - start) / 2;
            if(canFind(nums, mid, k)){
                start = mid;
            } else {
                end = mid;
            }
        }
        
        if(canFind(nums, end, k)) {
            return end;
        }
        return start;
    }
    
    private boolean canFind(int[] nums, double T, int k) {
        double rightsum = 0.0; 
        double leftsum = 0.0; 
        double minleftsum = 0.0;
        
        // 先算[0,k-2];
        for(int j = 0; j < k -1; j++) {
            rightsum += nums[j] - T;
        }
        
        // 从第k-1开始算;也就是满足第k个了;
        for(int j = k - 1; j < nums.length; j++) {
            rightsum += nums[j] - T;
            if(rightsum - minleftsum >= 0) {
                return true;
            }
            // j - i + 1 >= k --> i <= j - k  + 1; 
            // index一定是推倒出来的,不是凭空想象的;
            leftsum += nums[j - k + 1] - T;
            minleftsum = Math.min(minleftsum, leftsum);
        }
        return false;
    }
}
发布了562 篇原创文章 · 获赞 13 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/u013325815/article/details/104013016
今日推荐