不光是查找值! "二分搜索"

2018-11-14 18:14:15

二分搜索法,是通过不断缩小解的可能存在范围,从而求得问题最优解的方法。在程序设计竞赛中,经常会看到二分搜索法和其他算法相结合的题目。接下来,给大家介绍几种经典的二分搜索法的问题。

一、从有序数组中查找某个值

1、lowerBound

问题描述:

给定长度为n的单调不下降数列a和一个数k,求满足ai >= k条件的最小的i。不存在的情况下输出n。

限制条件:

1 <= n <= 10 ^ 6

0 <= ai < 10 ^ 9

0 <= k <= 10 ^ 9

问题求解:

如果使用朴素的解法按照顺序依次查找的话,也可以求得答案。但是如果利用数列的有序性这一条件,则可以得到更高效的算法,也就是采用二分搜索的方法来进行求解。

这个算法除了在有序数列查找值的问题上很有用处外,在求最优解的问题上也非常有用。

让我们考虑一下“求满足某个条件C(x)的最小的x”这一问题。对于任意满足C(x)的x,如果所有的x‘ >= x也满足C(x‘)的话,那么我们就可以使用二分法来求得最小的x。首先我们将左端点设置为不满足C(x)的值,右端点设置为满足C(x)的值。然后每次取中点,判断中点是否满足并缩小范围,直到范围足够小为止。最后ub就是要求的那个最小值。

最大化的问题也可以使用同样的方法进行求解。

    // (lb, ub]
    private int lowerBound(int[] nums, int target) {
        int lb = -1;
        int ub = nums.length;
        while (ub - lb > 1) {
            int mid = lb + (ub - lb) / 2;
            if (nums[mid] >= target) ub = mid;
            else lb = mid;
        }
        return ub;
    }

2、upperBound

问题描述:

问题求解:

    public int[] searchRange(int[] nums, int target) {
        if (nums == null || nums.length == 0) return new int[]{-1, -1};
        int lb = lowerBound(nums, target);
        int ub = upperBound(nums, target);
        if (lb == nums.length || nums[lb] != target) lb = -1;
        if (ub == 0 || nums[ub - 1] != target) ub = 0;
        return new int[]{lb, ub - 1};
    }

    // (lb, ub]
    private int lowerBound(int[] nums, int target) {
        int lb = -1;
        int ub = nums.length;
        while (ub - lb > 1) {
            int mid = lb + (ub - lb) / 2;
            if (nums[mid] >= target) ub = mid;
            else lb = mid;
        }
        return ub;
    }

    // (lb, ub]
    private int upperBound(int[] nums, int target) {
        int lb = -1;
        int ub = nums.length;
        while (ub - lb > 1) {
            int mid = lb + (ub - lb) / 2;
            if (nums[mid] > target) ub = mid;
            else lb = mid;
        }
        return ub;
    }

二、假定一个解并判断是否可行

Cable master POJ 1064

问题描述:

有N条绳子,它们的长度分别为Li。如果从它们中切割出K条长度相同的绳子的话,这K条绳子每条最长能有多长?答案保留到小数点后2位。

限制条件:

1 <= N <= 10000

1 <= K <= 10000

1 <= Li <= 100000

问题求解:

这个问题可以使用二分搜索非常容易的解决。让我们套用二分搜索的模型试着解决一下这个问题。令:

条件C(x) := 可以得到K条长度为x的绳子

则问题变成了求满足C(x)条件的最大x。在区间初始话的时候,只需要使用充分大的数INF(> MaxL)作为上界即可:

lb = 0

ub = INF

现在问题变成了如何高效的判定C(x)。由于长度为Li的绳子最多可以切出floor(Li / x)段长度为x的绳子,因此

C(x) = (floor(Li / x)的总和是否大于等于K)

它可以在O(n)的时间内判断出来。

本题POJ对精度要求很高,因此有两点需要注意:

1、是需要进行Math.floor(x * 100) / 100,避免四舍五入的问题

2、使用DecimalFormat对输出的精度进行控制

import java.text.DecimalFormat;
import java.util.Scanner;

public class CableMaster {
    int n;
    int k;
    double[] l;

    private boolean C(double x) {
        long res = 0;
        for (double i : l) res += (int) (i / x);
        return res >= k;
    }

    public void cableMaster() {
        // 求最大值[lb, ub)
        double lb = 0;
        double ub = 100001;

        // 重复循环直到解的范围足够小
        for (int i = 0; i < 100; i++) {
            double mid = lb + (ub - lb) / 2;
            if (C(mid)) lb = mid;
            else ub = mid;
        }

        DecimalFormat df = new DecimalFormat("0.00");
        lb = Math.floor(lb * 100) / 100;
        System.out.println(df.format(lb));
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        CableMaster cm = new CableMaster();
        while (sc.hasNext()) {
            cm.n = sc.nextInt();
            cm.k = sc.nextInt();
            cm.l = new double[cm.n];
            for (int i = 0; i < cm.n; i++) {
                cm.l[i] = sc.nextDouble();
            }
            cm.cableMaster();
        }
    }
}

三、最大化最小值

Aggressive Cows POJ 2456

问题描述:

农夫约翰搭建了一间有N间牛舍的小屋。牛舍排在一条直线上,第i号牛舍在xi的位置。但是他的M头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其他牛尽可能远的位置。也就是要最大化最近两头牛之间的距离。

限制条件:

2 <= N <= 100000

2 <= M <= N

0 <= xi <= 10 ^ 9

问题求解:

类似的最大化最小值或者最小化最大值的问题,通常用二分搜索法就可以很好的解决。我们定义:

C(d) := 可以安排牛的位置使得最近的两头牛的距离不小于d

那么问题就变成了求满足C(d)的最大的d。另外最近两头距离不小于d也就是所有的牛的间距都大于等于d。

判定C(d)可以使用贪心法进行判断:

对牛舍位置进行排序;

第一头牛放在x0牛舍;

如果第i头牛放到了第xj,那么第i + 1头牛就要放入最近的满足xk - xj >= d的牛舍。

import java.util.Arrays;
import java.util.Scanner;

public class AggressiveCows {
    int n;
    int m;
    int[] x;

    private boolean C(int d) {
        int prevIdx = 0;
        for (int i = 1; i < m; i++) {
            int curIdx = prevIdx + 1;
            while (curIdx < n && x[curIdx] - x[prevIdx] < d) curIdx++;
            if (curIdx == n) return false;
            prevIdx = curIdx;
        }
        return true;
    }

    public int aggressiveCows() {
        Arrays.sort(x);
        int lb = 0;
        int ub = x[n - 1];
        while (ub - lb > 1) {
            int mid = lb + (ub - lb) / 2;
            if (C(mid)) lb = mid;
            else ub = mid;
        }
        return lb;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        AggressiveCows ac = new AggressiveCows();
        while (sc.hasNext()) {
            ac.n = sc.nextInt();
            ac.m = sc.nextInt();
            ac.x = new int[ac.n];
            for (int i = 0; i < ac.n; i++) {
                ac.x[i] = sc.nextInt();
            }
            System.out.println(ac.aggressiveCows());
        }
    }
}

四、最大化平均值

问题描述:

有n个物品的重量和价值分别是wi和vi。从中选出k个物品使得单位重量的价值最大。

限制条件:

1 <= k <= n <= 10 ^ 4

1 <= wi, vi <= 10 ^ 6

问题求解:

一般最先想到的方法可能是把物品按照单位重量进行排序,从大到小进行选取。但是这个方法在本题中是不可行的。那么应该如何求解呢?

实际上,对于本题,使用二分搜索法可以很好的解决。我们定义

条件C(x) : 可以选择使得单位重量的价值不小于x

因此原问题就变成了求满足C(x)的最大的x。那么应该怎么判断C(x)是否可行呢?假设我们选择了某个物品的集合S,那么他们的单位重量价值为:

sum(vi) / sum(wi)

因此就变成了判断是否存在S满足以下的条件

sum(vi) / sum(wi) >= x

把这个不等式进行变形就可以得到

sum(vi - wi * x) >= 0

因此,就可以进行贪心的选取,对vi - wi * x的值进行排序,贪心的从中选择k个,看其和是否大于0。由于每次都需要排序,所以判断的时间复杂度为O(nlogn)。

五、Follow Up

  • Search in Rotated Sorted Array

问题描述:

问题求解:

因为没有重复,所以可以直观的通过mid和r比较来判断当前的mid是在前半段还是后半段。

    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return -1;
        int l = 0;
        int r = nums.length - 1;
        // [l, r]
        while (r - l + 1 > 0) {
            int mid = l + (r - l) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] > nums[r]) {
                // 这里的判断条件是关键
                if (nums[mid] > target && target >= nums[l]) r = mid - 1;
                else l = mid + 1;
            }
            else {
                if (target > nums[mid] && target <= nums[r]) l = mid + 1;
                else r = mid - 1;
            }
        }
        return -1;
    }
  • Search in Rotated Sorted Array II

问题描述:

问题求解:

带有重复值的问题就是有可能mid和两端的值是相等的,在这种情况下就没有办法进行有效的判断了,所以需要对两端的值进行一下去重操作,然后再使用上述的算法进行二分查找。

    public boolean search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return false;
        int l = 0;
        int r = nums.length - 1;
        while (r - l + 1 > 0) {
            while (l < r && nums[l] == nums[l + 1]) l++;
            while (r > l && nums[r] == nums[r - 1]) r--;
            int mid = l + (r - l) / 2;
            if (nums[mid] == target) return true;
            if (nums[mid] > nums[r]) {
                if (target >= nums[l] && target < nums[mid]) r = mid - 1;
                else l = mid + 1;
            }
            else {
                if (target > nums[mid] && target <= nums[r]) l = mid + 1;
                else r = mid - 1;
            }
        }
        return false;
    }

原文地址:https://www.cnblogs.com/TIMHY/p/9959558.html

时间: 2024-11-08 22:59:05

不光是查找值! "二分搜索"的相关文章

不光是查找值!&quot;二分搜索&quot;

从有序数组中查找某个值 问题描述:给定长度为n的单调不下降数列a0,-,an-1和一个数k,求满足ai≥k条件的最小的i.不存在则输出n. 限制条件:1≤n≤1060≤a0≤a1≤-≤an-1<1090≤k≤109 分析:二分搜索.STL以lower_bound函数的形式实现了二分搜索. 代码: 1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5

(一)Python入门-3序列:18字典-核心底层原理-内存分析-查找值对象过程

一:根据键查找“键值对”的底层过程 明白一个键值对是如何存储到数组中的,根据键对象取到值对象,理解起来就 简单了. >>> a.get("name") 'jack' 当我们调用a.get(“name”),就是根据键“name”查找到“键值对”,从而找到值对象“jack”. 第一步,我们仍然要计算“name”对象的散列值: >>> bin(hash("name")) '-0b10101111010011101101011001001

循环有序数组,查找值

一.从一个循环有序数组总查找给定值 1.思路:先通过中间值和最后一个或者第一个元素比较,找出局部有序范围,再通过二分查找局部有序段 private static int sortArrFindOne(int arr[], int low, int high, int target) { int mid = (high - low) / 2 + low; if (arr[mid] == target) return mid; if (arr[mid] < arr[high]) { if (arr[

二叉查找树(3) - 查找值最小的节点

查找最小值的操作是很简单的,只需要从根节点递归的遍历到左子树节点即可.当遍历到节点的左孩子为NULL时,则这个节点就是树的最小值. 上面的树中, 从根节点20开始,递归遍历左子树,直到为NULL.因为节点4的左子树为NULL,则4就是树的最小值. 代码实现查找最小值: Node * minValueNode(Node* node) { Node* current = node; //查找最左侧的叶子 while (current->left != NULL) current = current-

Python3基础 setdefault() 根据键查找值,找不到键会添加

镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.------------------------------------------ code: dict1={'子':'鼠','丑':'牛','寅':'虎','卯':'兔','辰':'龙','巳':'蛇','午':'马','未':'羊','申':'猴','酉':'鸡','戌':'狗','亥':'猪'} #找得到返回 print(dict1.setdefault('子'))

Python3基础 dict setdefault 根据键查找值,找不到键会添加

? python : 3.7.0 OS : Ubuntu 18.04.1 LTS IDE : PyCharm 2018.2.4 conda : 4.5.11 type setting : Markdown ? code """ @Author : 行初心 @Date : 18-9-23 @Blog : www.cnblogs.com/xingchuxin @GitHub : github.com/GratefulHeartCoder """ de

Excel-vlookup(查找值,区域范围,列序号,0)如何固定住列序列号,这样即使区域范围变动也不受影响

突然,发现VLOOKUP的列序列号并不会随着区域范围的改变而自动调节改变,只是傻瓜的一个数,导致V错值.所有,就想实现随表格自动变化的列序号. 方法一:在列序号那里,用函数得出永远想要的那个列在区域范围中的列数:区域范围内的目标列COLUMN值-区域范围首列COLUMN值+1 =VLOOKUP(I2,$B$2:$G$15,COLUMN($G$2)-COLUMN($B$2)+1,0) 方法二: 原文地址:https://www.cnblogs.com/Formulate0303/p/1104544

查找算法——————二分搜索

最常见的判断是存在key,如果存在输出位置,否则输出-1. int BinSearch(int l,int r,int key){ //judge exist int md; while(l <= r){ md= (l+r)/2; if(key < a[md]){ r = md - 1; }else if(key > a[md]){ l = md + 1; }else{ return md; } } return -1; //no exist } 如果要求是大于等于key的最小位置时.只

9、Cocos2dx 3.0游戏开发三查找值小工厂方法模式和对象

重开发人员的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27704153 工厂方法模式 工厂方法是程序设计中一个经典的设计模式.指的是基类中仅仅定义创建对象的接口,将实际的实现推迟到子类中. 在这里.我们将它稍加推广,泛指一切生成并返回一个对象的静态函数. 一个经典的工厂方法如同这样: Sprite* factoryMethod() { Sprite* ret = new Sprite(); //在这里对 r