学习笔记——二分算法

二分算法

查找流程

在这里插入图片描述

示例

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

代码演示

#include <iostream>
using namespace std;

void output(int *arr, int n, int ind = -1) {
   int len = 0;
    for (int i = 0; i < n; i++) {
        len += printf("%4d", i);
    }
    printf("\n");
    for (int i = 0; i < len; i++) printf("-");
    printf("\n");
    for (int i = 0; i < n; i++) {
        if (i == ind) printf("\033[1;32m");
        printf("%4d", arr[i]);
        if (i == ind) printf("\033[0m");
    }
    printf("\n");
    return ;
}

int binary_search(int *arr, int n, int x) {
    int head = 0, tail = n - 1, mid;
    while (head <= tail) {
        mid = (head + tail) / 2;
        printf("[%d, %d], mid = %d, arr[%d] = %d\n", 
            head, tail, mid,
            mid, arr[mid]
        );
        if (arr[mid] == x) return mid;
        if (arr[mid] < x) head = mid + 1;
        else tail = mid - 1;
    }
    return -1;
}

void test_binary_search(int n) {
    int *arr = (int *)malloc(sizeof(int) * n);
    arr[0] = rand() % 10;
    for (int i = 1; i < n; i++) arr[i] = arr[i - 1] + rand() % 10;
    output(arr, n);
    int x;
    while (~scanf("%d", &x)) {
        if (x == -1) break;
        int ind = binary_search(arr, n, x);
        output(arr, n, ind);
    }
    free(arr);
    return ;
}

int main() {
    #define MAX_N 10
    test_binary_search(MAX_N);
    return 0;
}

泛型情况

在这里插入图片描述
分别为前0后1和前1后0两种情况。第一种情况要求我们找到第一个1的位置,第二种情况要求我们找到最后一个1的位置。

情况1的处理策略:
当arr[mid]=0时,head=mid+1;
当arr[mid]=时,tail=mid;
当head=tail时,返回head(tail)的值。
(mid=(head+tail)/2)

情况2的处理策略:
当arr[mid]=0时,tail=mid-1;
当arr[mid]=1时,head=mid;
当head=tail时,返回head(tail)的值。
(mid=(head+tail+1)/2) 情况2的特殊处理!!!

例题

1.搜索插入位置

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

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

示例 1:

输入:nums = [1,3,5,6], target = 5
输出: 2
示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

思路:本题实际上是泛型情况中所提到的前0后1的情况(也可以是前1后0),即前面的若干元素小于x,后面的若干元素大于等于x,我们要找到第一个大于等于x的位置。

代码实现:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int head=0,tail=nums.size(),mid;
        //注意tail设置成了n,因为要覆盖所以合法的插入位置
        while(head<tail)
        {
            mid=(head+tail)/2;
            if(nums[mid]<target)head=mid+1;
            else tail=mid;
        }
        return head;
    }
};

2.无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

思路:当本问题抽象成1111100000模型,将能找到的子串长度进行二分,在其中找最大的。

代码实现

class Solution {
public:
    int check(string s,int l)
    {
        int vis[256]={0};
        int k=0;
        for(int i=0;i<s.size();i++)
        {
            vis[s[i]]++;
            if(vis[s[i]]==1)k++;
            if(i>=l)
            {
                vis[s[i-l]]--;
                if(vis[s[i-l]]==0)k--;
            }
            if(l==k)return 1;
        }
        return 0;
    }
    int lengthOfLongestSubstring(string s) {
        int head=0,tail=s.size(),mid;
        while(head<tail)
        {
            mid=(head+tail+1)/2;
            if(check(s,mid))head=mid;
            else tail=mid-1;
        }
        return head;
    }
};

3.寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

思路:如果将本问题泛化为找第K的元素的值的话,那么可以这么处理:每次在两个数组中分别找k/2个数,比较最后一位的大小,如果nums1[k/2]的值小于nums2[k/2]的话,那么第k个元素一定不在nums1[k/2]及之前,此时就在剩余的位置里面找第k/2大的数,如此递归。

代码实现

class Solution {
public:
    int findK(vector<int>nums1,int ind1,vector<int>nums2,int ind2,int k)
    {
        if(nums1.size()==ind1)return nums2[ind2+k-1];
        if(nums2.size()==ind2)return nums1[ind1+k-1];
        if(k==1)return min(nums1[ind1],nums2[ind2]);
        int n=nums1.size(),m=nums2.size();
        int cnt1=min(k/2,n-ind1);
        int cnt2=min(m-ind2,k-cnt1);
        cnt1=k-cnt2;
        if(nums1[cnt1+ind1-1]<nums2[cnt2+ind2-1])
        {
            return findK(nums1,ind1+cnt1,nums2,ind2,k-cnt1);
        }
        else return findK(nums1,ind1,nums2,ind2+cnt2,k-cnt2);
    }
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int n=nums1.size(),m=nums2.size();
        if((n+m)%2==1)return findK(nums1,0,nums2,0,(n+m)/2+1);
        else
        {
            double a=findK(nums1,0,nums2,0,(n+m)/2);
            double b=findK(nums1,0,nums2,0,(n+m)/2+1);
            return (a+b)/2;
        }
    }
};

4.最大平均值

题目描述
​ 给定一个有 N 个元素的非负序列,求长度大于等于 M 的连续子序列的最大平均值。

输入
​ 第一行输入两个数 N,M。(1≤N,M≤100000)
​ 接下来 N 行,每行输入一个数表示非负序列。

输出
​ 输出一个整数表示最大平均值乘 1000 的结果。

样例输入

10 6
6
4
2
10
3
8
5
9
4
1

样例输出

6500

思路

1.将问题进行等价转换

1.是否存在一段长度>=M的序列,平均值>=A(求平均值自然可以想到求和值,使用前缀和数组来维护)---->
在这里插入图片描述

2.是否存在一段长度>=M的序列,和值>=A×L(由于L是变量,因此每个长度下A×L的值都不同,继续对问题进行转化)---->

在这里插入图片描述

3.是否存在一段长度>=M的序列,和值>=0(原始序列的每一项都减去A)

2.使用1111100000型的二分模型

在0到max_val区间内进行二分,找到最后一个1所在的位置。

代码实现

#include<iostream>
using namespace std;
#define MAXSIZE 100000
long long arr[MAXSIZE+5],sum[MAXSIZE+5];
int N,M;
bool check(int item)
//由于要调用多次check,注意处理的过程中不要改变arr的值 
{
	for(int i=1;i<=N;i++)
	sum[i]=arr[i]-item;
	sum[0]=0;
	for(int i=1;i<=N;i++)
	sum[i]+=sum[i-1];
	long long ret=0;
	for(int i=M;i<=N;i++)
	{
		ret=min(ret,sum[i-M]);
		if(sum[i]-ret>=0)return 1;
	}
	return 0;
}
int main()
{
	cin>>N>>M;
	arr[0]=0;
	long long max_val=0;
	for(int i=1;i<=N;i++)
	{
		scanf("%d",&arr[i]);
		arr[i]*=1000;
		max_val=max(max_val,arr[i]);//找到最大值 
	}
	int head=0,tail=max_val,mid;
	while(head<tail)//1111100000 
	{
		mid=(head+tail+1)/2;
		if(check(mid))head=mid;
		else tail=mid-1;
	}
	cout<<head;
	return 0;
 } 

5.奶牛围栏

题目描述
​ 约翰打算建一个围栏来圈养他的奶牛。作为最挑剔的兽类,奶牛们要求这个围栏必须是正方形的,而且围栏里至少要有 C (1≤C≤500) 个草场,来供应她们的午餐。

​ 约翰的土地上共有 N (C≤N≤500) 个草场,每个草场在一块1x1的方格内,而且这个方格的 坐标不会超过 10000。有时候,会有多个草场在同一个方格内,那他们的坐标就会相同。

​ 现求围栏的最小边长为多少。

输入
​ 第一行输入两个数 C,N。

​ 接下来 N 行每行两个数,表示每个草场的坐标 Xi,Yi。

输出
​ 输出围栏的最小边长。

样例输入

3 4
1 2
2 1
4 1
5 2

样例输出

4

思路

本题为0000011111二分模型,找到第一个1,即符合要求的最小边长。
有了最外层的二分框架,如何去实现check函数?本题使用二维扫描线法,即先找到x符合要求的一组数,再判断这一组数中是否有足够数量的元素在y上也符合要求。
在这里插入图片描述

在这里插入图片描述
代码实现

#include<iostream>
#include<algorithm>
using namespace std;
#define MAXSIZE 500
int C,N;
struct Data{
	int x,y;
}arr[MAXSIZE+5];
int temp[MAXSIZE+5];
bool cmp(const Data&a,const Data&b)
{
	return a.x<b.x;
}
bool check_y(int a,int b,int l)
{
	int cnt=0;
	for(int i=a;i<=b;i++)
	temp[++cnt]=arr[i].y;
	sort(temp+1,temp+1+cnt);
	for(int i=C;i<=cnt;i++)
	{
		if(temp[i]-temp[i-C+1]<l)return 1;
	}
	return 0;
}
bool check(int l)
{
	for(int i=1,j=1;j<=N;j++)
	{
		while(arr[j].x-arr[i].x>=l)i++;
		if(j-i+1<C)continue;
		if(check_y(i,j,l))return 1;
	}
	return 0;
}
int main()
{
	cin>>C>>N;
	for(int i=1;i<=N;i++)
	cin>>arr[i].x>>arr[i].y;
	sort(arr+1,arr+1+N,cmp);
	int head=1,tail=10000,mid;
	while(head<tail)
	{
		mid=(head+tail)/2;
		if(check(mid))tail=mid;
		else head=mid+1;
	}
	cout<<head;
	return 0;
 } 
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
cda备考学习学习笔记——基础知识篇(二)主要涉及了计算机科学与技术领域的基本概念和知识。 首先,它介绍了计算机网络的基础知识。网络是将多台计算机通过通信链路连接起来,使它们能够相互通信和共享资源的系统。笔记中详细介绍了网络的组成、拓扑结构和通信协议等重要内容。 其次,笔记还解释了计算机系统的基本组成。计算机系统由硬件和软件两部分组成,其中硬件包括中央处理器、存储器、输入输出设备等,而软件则分为系统软件和应用软件。笔记详细介绍了各种硬件和软件的功能和作用。 此外,笔记还对数据库管理系统进行了介绍。数据库管理系统是一种用于管理和组织数据的软件系统,它能够实现数据的存储、检索和更新等操作。笔记中详细介绍了数据库的概念、结构和操作等内容。 最后,笔记还包括了算法和数据结构的基础知识。算法是解决问题的一系列步骤和规则,而数据结构则是组织和存储数据的方式。笔记中介绍了常用的算法和数据结构,如排序算法、树和图等。 总之,通过学习CDA备考学习笔记中的基础知识篇(二),我们能够更好地理解计算机网络、计算机系统、数据库管理系统以及算法和数据结构等相关概念和知识。这些基础知识对于我们深入研究计算机科学与技术领域是非常重要的,也为我们日后的学习和工作奠定了坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值