二分法汇总

标准二分法

标准例题:https://leetcode.com/problems/binary-search/

int binarySearch(vector<int>& v, int target){
        int l = 0, r = v.size()-1;
        // 一般来说,这里的判断包括等号可以用来处理没有找到解的情况
        while(l <= r){ // 考虑只有一个样例的情况 [1], 1 
            int mid = (l+r)/2;
            if(v[mid] == target) return mid;
            if(v[mid] < target) {
                l = mid+1;
            } else {
            	r = mid-1;
			}
        } return -1; // not find
    }

Search Insert Position

Search Insert Position

Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

You may assume no duplicates in the array.

Here are few examples.
[1,3,5,6], 5 → 2
[1,3,5,6], 2 → 1
[1,3,5,6], 7 → 4
[1,3,5,6], 0 → 0

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l = 0, r = nums.size()-1;
        if(target < nums[l]) return 0;
        if(target > nums[r]) return nums.size();
        // at least 2 elements
        while(r - l > 1){ // at least two 3 elements
            int mid = (l+r)/2;
            int midVal = nums[mid];
            if(midVal == target) return mid;
            else if(midVal < target) l = mid;
            else r = mid;
        } if(nums[l] == target) return l;
        return r;
    }
};

2019.3.25 重新做

class Solution {
public:
    int searchInsert(vector<int> nums, int target) {
        int l = 0, h = nums.size()-1;
        while(l <= h){
            int mid = l + (h-l)/2;
            if(nums[mid] == target) return mid;
            if(nums[mid] < target) l = mid+1;  // 保证了 nums[j] < target for j < l
            else h = mid-1;  // 保证了 target < nums[j] for h < j
        }
        // 没有找到target跳出循环时 l == h+1
        /* 此时保证了 nums[h] < target  */
        return l;
    }
};

Find First and Last Position of Element in Sorted Array

Find First and Last Position of Element in Sorted Array
Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value.

Your algorithm’s runtime complexity must be in the order of O(log n).
If the target is not found in the array, return [-1, -1].

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> res(2, -1);
        if(nums.size() == 0) return res;
        // step1: find left most element
        int l = 0, r = nums.size()-1;
        while(l <= r){
            int m = l + ((r-l)>>1);
            if(nums[m] < target) l = m+1;  // nums[j] < target for j<l
            else r = m-1;  // target <= nums[j] for i<j
        }
        if(l == nums.size() ||   // 这里容易忘记
           nums[l] != target) return res; 
        res[0] = l;
        // step2: find right most element        
        l = 0, r = nums.size()-1;
        while(l <= r){
            int m = l + ((r-l)>>1);
            if(nums[m] <= target) l = m+1;  // nums[j] <= target for j<l
            else r = m-1;  // target < nums[j] for i<j
        } res[1] = r;
        return res;
    }
};

总结:lower_bound & upper_bound

Find First and Last Position of Element in Sorted Array, 在找first position of element时的操作,其实与找lower_bound(第一个大于或等于 target值的下标)相同;而在找last position of element时,与upper_bound(第一个大于 target值的下标)有联系,因为在target存在时,last position of element的下一个位置就是upper_bound。
找到lower_bound:

int lower_bound(const vector<int>& arr, int target) const{
        int l = 0, r = arr.size()-1, m;
        while(l <= r){
            m = l + ((r-l)>>1);
            if(arr[m] < target) l = m+1;  // arr[j] < target for j<l
            else r = m-1;  // target <= arr[j] for r<j
        } return l;
    }
    

找到upper_bound:

int upper_bound(const vector<int>& arr, int target){
	int l = 0, r = arr.size()-1, m;
	while(l <= r){
		m = l + ((r-l)>>1);
		if(arr[m] <= target) l = m+1; // arr[j] <= target for j<l
		else r = m-1;  // target<arr[j] for r<j
	} return l;
}

自此,我们应该已经能熟练运用二分法了,关键要时刻明白自己的代码中每次迭代要保证什么条件。这个在《编程珠玑》中被称为循环不变式 (loop invariant)。

另外推荐另一个博客总结:https://www.cnblogs.com/xiaowuga/p/8604750.html
这里总结的方法类似《算法竞赛入门经典》,每次考察的区间是[L, R)。

最大值最小化

这种类型的问题,我第一次是在刘汝佳的《算法竞赛入门经典》中看到的。
这里给一道标准的例题 Monthly Expense
题目意思是说,共n个月,给出每个月的开销。将n个月划分成m个时间段,求m个时间段中开销最大的时间段的最小开销值。
我们把问题转化为对于给定的开销值val,能否划分成m个时间段,然后通过二分法找到最小满足要求的val。这样我们就把最优化问题转化为了判定稳定。

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
using namespace std;


int n, m;
int arr[100000];
inline bool check(int val, int m){
    int cur = 0;
    for(int i = 0; i < n; ++i){
        if(arr[i] > val) return false;
        cur += arr[i];
        if(cur > val){
            --m;
            cur = arr[i];
        }
    } return m>0;  // 因为我们用第一个区间时并没有--m,所以判定条件是m>0
}

int main(){
    while(cin >> n >> m){
        int max_exp = INT_MIN, min_exp = INT_MAX;
        for(int i = 0; i < n; ++i){
            cin >> arr[i];
            max_exp = max(max_exp, arr[i]);
        }
        int l = max_exp, r = max_exp*n, mid;
        while(l <= r){
            mid = (l+r)>>1;
            if(check(mid, m)) r = mid-1;  // for any j, s.t. r<j, j is feasible
            else l = mid+1;  // for any j < l, j is not feasible
        } cout << l << endl;
    }
    return 0;
}

为什么在check函数中可以贪心的方法,尽量往右划分呢?
我有这么个朴素的想法,可能是对的:
对于元素j,如果能放进i组,但是我们放进了i+1组。
假设j放进i+1组能够得到最优解A,那我们把j放回i组能得到一个不会差过最优解A的解A*

最小值最大化

有了最大值最小化,同理也有最小值最大化。
这里给一道标准的例题 Aggressive cows
题意: 输入N间屋子的坐标, 求最大的c头牛相邻之间的距离

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
using namespace std;

int n, c;
int arr[100000];
bool check(int val){
    int dis = 0, cnt = 1;
    for(int i = 1; i < n; ++i){
        dis += (arr[i] - arr[i-1]);
        if(dis >= val){
            dis = 0;
            ++cnt;
        }
    } return cnt >= c;
}
int main(){
    cin >> n >> c;
    for(int i = 0; i < n; ++i){
        cin >> arr[i];
    } sort(arr, arr+n);
    int min_dis = INT_MAX;
    for(int i = 1; i < n; ++i){
        min_dis = min(min_dis, arr[i]-arr[i-1]);
    } int max_dis = arr[n-1] - arr[0];
    int l = min_dis, r = max_dis, m;
    while(l <= r){
        m = l + ((r-l)>>1);
        if(check(m)) l = m+1;
        else r = m-1;
    } cout << r << endl;
    return 0;
}

Search in Rotated Sorted Array

Search in Rotated Sorted Array
Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Your algorithm’s runtime complexity must be in the order of O(log n).

Example 1:

Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

Example 2:

Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1
class Solution {
    // step1. 旋转有序数组的最小值.  step2. 二分查找
public:
    int search(vector<int>& nums, int target) {
        if(nums.size() == 0) return -1;
        int l = 0, r = nums.size()-1;
        bool no_rotate = false; 
        if(nums[l] < nums[r]){
            no_rotate = true;  // no rotate occured
        }
        else{
            while(l+1 < r){
                int mid = l + ((r-l)>>1);
                if(nums[l] < nums[mid]){
                    l = mid;
                } else{
                    r = mid;
                }
            }  // assert(l+1 == r)
        } // nums[r] is the least element
        
        // step2: binary search
        if(no_rotate) {
            // continue;
        }
        else if(target <= nums[nums.size()-1]){
            l = r;
            r = nums.size()-1;
        } else{
            r = l;
            l = 0;
        }
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(nums[mid] == target) return mid;
            if(nums[mid] < target) {
                l = mid+1;
            } else{
                r = mid-1;
            }
        } return -1;
    }
};

Sqrt(x)

Sqrt(x)
Implement int sqrt(int x).

Compute and return the square root of x.

class Solution {
public:
    int mySqrt(int x) {
        if(x <= 1) return x;  // when x==1, algo below will return 0
        int low = 0, high = x;
        while(high - low > 1){ // low+1 == high 时进入会变成死循环
            int mid = ((long long)high+low)/2;
            long long square = (long long)mid*mid;
            if(square == x) return mid;
            if(square < x){   // assert low*low < x
				low = mid;
            } else high = mid;
        }  // assert low+1 == high
        return low;
    }
};

旋转数组的最小数字

《剑指offer》上的题 2019.3.8

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <queue>
using namespace std;
/*
	题目:
		把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
		输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。
		例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
		NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

	因为明确了有序数组一定发生了旋转,所以我们二分的目标就是找到相邻的两个数,而且这两个数是降序的。
    要找相邻的两个数,所以二分的终止条件就得是 left+1 < right

	下面的解法有误,请见 https://www.nowcoder.com/questionTerminal/9f3231a991af4f55b95579b44b7a01ba?toCommentId=1259695
	    int minNumberInRotateArray(vector<int> v) {
			if(v.empty()) return 0;
			int l = 0, r = v.size()-1;
			while(l+1 < r){
				int mid = (l+r)/2;
				if(v[l] > v[mid]){
					r = mid;
				} else{
					l = mid;
				}
			} return v[r];
		}
	需要考虑的特殊情况:
	① 数组旋转部分的长度 len s.t. (len%v.size()) == 0
	② 如果原序列为011111,旋转后序列为101111,这种情况就不能通过了。
	所以当v[l]=v[mid]时候情况比较特殊,只能修改下标加一(判断下一个元素),而不能把下标改成mid。
		//* 经典测试样例 /
         1
         2 1
         5 6 1
         6 1 2
         4 5 6 1 2 3
         1 2   // is OK?
		 1, 0, 1, 1, 1, 1  // 上面的错误代码不能通过该样例


*/
class Solution {
public:
	int minNumberInRotateArray(vector<int> v) {
        if(v.empty()) return 0;  // 给出的所有元素都大于0,若数组大小为0,请返回0。
        int l = 0, r = v.size()-1;
        if(v[l] < v[r]) return v[l]; // 补丁,旋转过的数组与原来的数组一样
        while(l+1 < r){
            int mid = (l+r)/2;
            if(v[l] == v[mid] && v[mid] == v[r]){
                // 顺序遍历找最小值
                int res = v[l];
                for(int i = l+1; i<=r; ++i)
                    if(v[i]<res)
                        res = v[i];
                return res;
            }
            if(v[l] > v[mid]){
                r = mid;
            } else{ // if(v[l] <= v[mid]){ // 这里的分析很重要。v[l] < v[mid]时,容易分析。当v[l] == v[mid]时,因为v[mid]与v[r]不相等,因此一定满足v[mid]>v[r]
                l = mid;
            } 
            
        } return v[r];
    }
};
int main(){
	int a[] = {1, 0, 1, 1, 1, 1};
	int res = Solution().minNumberInRotateArray(vector<int>(a, a+6));
	cout << res << endl;
    return 0;
}

统计一个数字在排序数组中出现的次数

《剑指offer》上的题,也就是找数字出现在排序数组中的第一个和最后一个位置。

class Solution {
public:
    int GetNumberOfK(vector<int> data, int k) {
        if(data.size() == 0) return 0;
        // assert ascend order
        int l = left_pos(data, k);
        if(l == data.size()) return 0;
        return right_pos(data, k) - l + 1;
    }
private:
    int left_pos(const vector<int>& arr, int target) const{
        int l = 0, r = arr.size()-1, m;
        while(l <= r){
            m = l + ((r-l)>>1);
            if(arr[m] < target) l = m+1;  // arr[j] < target for j<l
            else r = m-1;  // target <= arr[j] for r<j
        } return l;
    }
    int right_pos(const vector<int>& arr, int target) const{
        int l = 0, r = arr.size()-1, m;
        while(l <= r){
            m = l + ((r-l)>>1);
            if(arr[m] <= target) l = m+1; // arr[j] <= target for j<l
            else r = m-1;
        } return r;
    }
};

First Bad Version

First Bad Version
You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.

Suppose you have n versions [1, 2, …, n] and you want to find out the first bad one, which causes all the following ones to be bad.

You are given an API bool isBadVersion(version) which will return whether version is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.

// Forward declaration of isBadVersion API.
bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        int l = 1, r = n;
        while(r - l > 1) { // at least 3
            int mid = ((long long)l+r)/2; // 防止int溢出
            if( isBadVersion(mid) ){
                r = mid;
            } else {
                l = mid;
            }
        } if(isBadVersion(l)){
            return l;
        } return r;
    }
};

此题考察了二分法,这里修改了一点就是low和high在调整时直接等于mid, 这样做可以保证low一定是好零件,high一定是坏零件。又因为不会调整为mid-1和mid+1,所以两个相邻的数可以使while陷入死循环。 还有需要注意的即使l+r会使int溢出。

Find Peak Element

https://leetcode.com/problems/find-peak-element/

A peak element is an element that is strictly greater than its neighbors.

Given a 0-indexed integer array nums, find a peak element, and return its index. If the array contains multiple peaks, return the index to any of the peaks.

You may imagine that nums[-1] = nums[n] = -∞. In other words, an element is always considered to be strictly greater than a neighbor that is outside the array.

You must write an algorithm that runs in O(log n) time.

Example 1:

Input: nums = [1,2,3,1]
Output: 2
Explanation: 3 is a peak element and your function should return the index number 2.
Example 2:

Input: nums = [1,2,1,3,5,6,4]
Output: 5
Explanation: Your function can return either index number 1 where the peak element is 2, or index number 5 where the peak element is 6.

/**
 * @param arr int整型一维数组 
 * @param n int nums数组长度
 * @return int整型
 */
int findPeakElement(int* arr, int n) {
    int l = 0;
    int r = n-1;
    int m;
    while(l < r){
        m = l+ ((r-l)>>1);
        if(arr[m] < arr[m+1]){
            l = m+1;
        }
        else{  // arr[m] > arr[m+1]
            r = m;
        }
    }
    // assert (l == r)
    return l;
}

很容易做不对。要找到循环不变式,还要防止死循环。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值