数组--二分查找 专题训练

Posted HSR CatcousCherishes

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数组--二分查找 专题训练相关的知识,希望对你有一定的参考价值。

目录

一、数组

1.1 二分查找

704. 二分查找
题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

分析:
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。

二分法第一种写法
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
例如在数组:1,2,3,4,7,9,10中查找元素2,如图所示:


Go语言

// 第一种方法:
func search(nums []int, target int) int 
        left :=0
        right:=len(nums)-1
        for left<=right   //左闭右闭的类型
            mid := left+(right-left)/2

            if nums[mid]==target
                 return mid
             else if nums[mid]<target
                left=mid+1
            else if nums[mid]>target 
                right =mid-1
            
        //执行完一遍数组后还没有发现相等的,则返回-1
         return -1

二分法第二种写法
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。

有如下两点:

while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

在数组:1,2,3,4,7,9,10中查找元素2,如图所示:(注意和方法一的区别)

Go 的第二种方法:

func search(nums []int, target int) int 
        left :=0
        right:=len(nums)
        for left<right   //左闭右开 的类型
            mid := left+(right-left)/2

            if nums[mid]==target
                 return mid
             else if nums[mid]<target
                left=mid+1
            else if nums[mid]>target 
                right =mid
            
        //执行完一遍数组后还没有发现相等的,则返回-1
         return -1

相关题目推荐

35.搜索插入位置(opens new window)
34.在排序数组中查找元素的第一个和最后一个位置(opens new window)
69.x 的平方根
367.有效的完全平方数

1.2 其他语言版本

python 第一种方法:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
       left,right =0,len(nums)-1 #  定义target在左闭右闭的区间里,[left, right]
       while left<=right:
           mid =left + (right-left)//2
           if nums[mid]<target:  # target在左区间,所以[middle+1,right]
                left =mid+1
           elif nums[mid]>target:  # target在左区间,所以[left, middle - 1]
                right=mid-1
           elif nums[mid]==target:
                return mid   # 数组中找到目标值,直接返回下标
       return -1   # 未找到目标值

相关推荐:
35. 搜索插入位置(简单)

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

GO语言第一种方案:

func searchInsert(nums []int, target int) int 
    left:=0
    right :=len(nums)-1
    
    if nums[left]>target //目标值在数组所有元素之前
        return 0
    
    if nums[right]<target//目标值在数组所有元素之后 
    

    for left<=right
        mid :=left +(right-left)/2
        if nums[mid]==target
            // 1. 目标值等于数组中某一个元素  return mid;
              return mid
        else if nums[mid]<target
            left =mid+1
        else if nums[mid]>target 
            right=mid-1
        
    
    //2.目标值在数组所有元素之前
    //3.目标值插入数组中 
    //4.目标值在数组所有元素之后   return right + 1;

   return left //目标值插入数组中 


二分暑假专题 训练记录 2017-7-29

POJ3258-River Hopscotch

题意:

  给你区间【0,L】给你n个石头,然后去除m个石头  最大化 石头间最小的距离

思路:

  首先0和L 这两个石头是不可以动的   然后用 s 数组记录 整个区间的石头

  然后排序  此时石头的排序就是有序的了  然后二分套模板

  接着check函数才是最关键的好的把

  从0到 n+1-m   总共就有 n+2-m 个石头了 而由于第0个石头不可以动 , 所以从第一个开始动 

  同时判断条件是 s[cur] - s[last] < d 而不是 <= 

 技术分享
#include <iostream>
#include <algorithm>

using namespace std;

int l,n,m;
int s[50010];

bool check(int d)
{
    int last = 0; // 1 - n+2-m
    for(int i=1;i < n+2-m ; i++)
    {
        int cur = last + 1;
        while (cur <= n+1 &&s[cur] - s[last] < d )
            cur++;
        if(cur > n+1) return 0;
        last = cur;
    }
    return true;
}

int main()
{
    cin>> l>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>s[i];
    sort(s,s+n+1);
    s[0] = 0,s[n+1] = l;

    int ans = 0;
    int le = 0,ri = l;
    while (le <= ri)
    {
        int mid = (le + ri)/2;
        if( check (mid) ) ans = mid , le = mid+1;
        else ri = mid-1;
    }
    cout << ans <<endl;
    return 0;
}
A

 

POJ 3104 Drying

题意:

  有n件衣服,每件衣服的含水量为ai单位,每分钟他们能自然脱水1单位,有一个脱水机,每次只能对一件衣服脱水,脱水量为k单位(脱水时不自然风干),问所有衣服全部风干的最小时间是多少?

 

思路:

  首先能够想到的是可以二分查找全部自然风干的最少时间mid。但这一题不同的是在判断函数中用蛮力法判断mid是否满足条件是会出错。需要特殊处理。

设某次二分出的一个值是mid
1、对于一件ai值小于等于mid的衣服,直接晾干即可;
2、对于一件ai值大于mid值的衣服,最少的用时是用机器一段时间,晾干一段时间,设这两段时间分别是x1和x2,那么有mid=x1+x2,ai<=k*x1+x2,解得x1>=(ai-mid)/(k-1) ,所以对(ai-mid)/(k-1)向上取整就是该件衣服的最少用时。

技术分享
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;
const int mod = 1e9 + 7;
const int maxn = 100000 + 5;
typedef long long LL;
int n,k,s[maxn];

bool check (LL d)//表示在d时间内能做玩
{
    LL sum = 0;
    for(LL i=1;i <= n;i++)
    {
        if(s[i] > d)
        {
            sum +=(s[i] - d + k-2 )/(k-1);//向上取整   +k-2 是为了向上取整
            if(sum > d) return 0;
        }
    }
    return true;
}
int main()
{
    while (~scanf("%d",&n))
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&s[i]);
        scanf("%d",&k);
        sort(s+1,s+n+1);
        if(k == 1)//特判
        {
            cout<< s[n] <<endl;
            continue;
        }
        LL ans = 0;
        LL le = 0,ri = s[n];
        while (le <= ri)
        {
            LL mid = (le+ri)/2;
            if( check(mid) ) ans = mid,ri = mid-1;
            else le = mid +1;
            //cout<< mid <<endl;
        }
        cout<< ans <<endl;
    }
    return 0;
}
B_注意特判 k为1的情况

 

POJ 3045 Cow Acrobats(贪心)

题意:

  有N头牛玩叠罗汉游戏,每头牛有一个体重Wi和一个力量Si。 这个游戏对每头牛都有一个危险度等于这头牛上面的牛的体重总和减去他的力量值。

  求所有方案中危险值最大的最小。

思路: 

  按照w+s贪心叠,越大的越在下面。如果最优放置时,相邻两头牛属性分别为weightA,powerA,weightB,powerB,第一头牛在第二头上面,sum为第一头牛上面的牛的体重之和,那么

  第一头牛风险:a=sum - powerA;第二头牛风险:b=sum + weightA - powerB;

  交换两头牛位置之后a‘=sum + weightB - powerA, b‘=sum - powerB,

  所以此时  max(a,a‘) 与 max(b,b‘)比较

  由于是最优放置,所以w2-s1>=w1-s2,即w2+s2>=w1+s1,所以和最大的就该老实的在下面呆着= =!

  此题目有一个WA点 就是危险度可能是负数  所以刚开始初始化的 MAX 必须是比  -1e9 还要小的数.....WA了四次

技术分享
//C_注意贪心规则
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string.h>
using namespace std;
const int mod = 1e9 + 7;
const int maxn = 50000 + 5;
const int INF = 0x3f3f3f3f;
typedef long long LL;

int n;
struct p{
    int weight,power;
    bool operator < (const p &x)const
    {
        //这里有问题的啊  应该是按照 weight+power 排序 而不是重量大力气小的排
        return weight + power > x.weight + x.power;

    }
}s[maxn];

LL sum = 0;
int main()
{
    while (~scanf ("%d",&n))
    {
        sum = 0;
        for(int i=1;i <= n;i++)
        {
            scanf("%d %d" , &s[i].weight , &s[i].power);
            sum += s[i].weight;
        }
        sort(s+1 , s+1+n);
        LL ans = -INF;
        for(int i=1; i<=n; i++)
        {
            sum =sum- s[i].weight;
            ans = max(ans,sum-s[i].power);
        }
        printf("%lld\n",ans);

    }
    return 0;
}
C_注意贪心规则

 

POJ1064 Cable master

题意:

  给你n,k   然后给出 n个a[i] 表示每次test 作对的数量   再给n个b[i] 表示每次test 的题数  让你去掉 k 次测验后  平均成绩的最大值

思路:

  看挑战 (最大化平均值)

  有句MMP 不知当讲不当讲

  最后结果的输出   错误 竟然就导致这道题 WA了6次;

  刚开始int (ans*100)    这个没有四舍五入

  改成 printf("%.0lf\n",ans*100); 然后网上找的数据   结果还是WA   我直接准备去 POJ 上交 然后蜜汁过了

  后来发现 VJ上我用的G++  

 

  最后网上搜索了一波 原来是 %f 和 %lf 的原因

  

double numG++提交C++提交最安全的方法
输入 scanf(“%lf”, &num); scanf(“%lf”, &num); cin >> num;
输出 printf(“%f”, num); printf(“%lf”, num); cout << num;
技术分享
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int mod = 1e9 + 7;
const int maxn = 10000 + 5;
const int INF = 0x3f3f3f3f;

typedef long long LL;
int n,k;
int a[1010],b[1010];
double s[1010];

bool check(double d)
{
    for(int i=0; i<n; i++)
    {
        s[i] = double(1.000*a[i] - 1.000*d*b[i]);
    }
    sort(s,s+n);
    double sum = 0;
    for(int i=0; i<n-k; i++)
    {
        sum += s[n-i-1];
    }
    return sum >= 0;
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    std::ios::sync_with_stdio(false);

    while (cin >> n >> k && n+k)
    {
        for(int i=0; i<n; i++)
            cin>>a[i];
        for(int i=0; i<n; i++)
            cin>>b[i];
        double le = 0,ri = 1;
        double ans = 0;
        for(int i=0; i<100; i++)
        {
            double mid = (le + ri)/2.0;
            if(check(mid))
                ans = mid, le = mid;
            else
                ri = mid;
        }
        printf("%.0lf\n",ans*100);
    }
    return 0;
}
D

 

poj 3685 Matrix

题意:

  Given a N × N matrix A, whose element in the i-th row and j-th column Aij is an number that equals i2 + 100000 × i + j2 - 100000 × j + i × j, you are to find the M-th smallest element in the matrix.   自己能读懂...

思路: 

  看别人的题解  发现 如果固定j  就发现函数是个递增函数  所以主函数里的二分是用来判断第M-th数的大小的

  然后check 函数里面的二分 是用来确定小于 m的个数的

  这个题挺好的  晚上再写一遍 加深对二分的理解

技术分享
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

#define maxn 1e12;
typedef long long LL;
LL k ,n;
LL cal(LL i,LL j)
{
    return i*i + 100000 * i + j*j - 100000 * j + i*j;
}
bool check(LL m)
{
    LL cnt = 0;
    for(int j=1; j<=n; j++)//着相当于固定每一个j 求 <= m的个数
    {
        int le=1,ri = n,ans = 0;
        while (le <= ri)
        {
            int mid = (le + ri)/2;
            if(cal(mid,j) <= m)//如果此时小于等于m 就说明还可以找更大的i 所以左区间缩小
                ans = mid,le = mid+1;
            else
                ri = mid-1;
        }
        cnt += ans;
    }
    return cnt >= k;//如果cnt >= k ,此时就说明这不是第k小  所有让右区间缩小
}
int main ()
{
    std::ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while (T--){
        cin >> n >> k;
        LL le = -100000*n;
        LL ri = n*n+n*n+100000*n+n*n;
        LL ans = 0;
        while (le <= ri)
        {
            LL mid = le+ (ri-le)/2;
            if( check (mid) )
                ans = mid,ri = mid-1;
            else
                le = mid+1;
        }
        cout<< ans <<endl;
    }
    return 0;
}
E 二分套二分

 

以上是关于数组--二分查找 专题训练的主要内容,如果未能解决你的问题,请参考以下文章

数组--二分查找 专题训练

AK leetcode 流浪计划 - 二分查找

算法专题(01)二分查找(03) 简单LeetCode 278

专题总结—二分查找与旋转排序数组

二分查找专题总结 - 基础篇

[二分查找] 在排序数组中查找元素的第一个和最后一个位置