Java算法学习3——二分查找和差分数组

一、二分查找

二分查找分为了三种情况,分别为:查找目标值,查找左端边界和查找右端边界,其中查找目标值要用左右都闭区,查找左端和右端边界时,要用左闭右开。下面对三种情况进行分别分析

1.查找目标值
即最后要返回mid下标,即target的下标

①先定义left=0,right=num.length-1;
说明了搜索区间为[left,right]
②循环结束的条件是left=right+1,因为此时写成区间的形式是[right+1,right],算的mid恒为空,所以就可以停止了,所以while(left<=right)
③当num[mid]<target时,right=mid+1
因为,这个是闭区间,并且mid已经搜索过,所以要把mid去掉
同理,当num[mid]>target时,left=mid-1;

代码实现如下:

import java.util.*;

public class Main(){
 public int Search(int []num,int target){
 int left=0,right=num.length-1;
 while(left<=right){
 int mid =left+(right-left)/2;//有效避免left+right导致越界的问题
 if(num[mid]=target) return mid;//返回下标所以不用+1
 else if (num[mid]>target) right=mid-1;
 else if (num[mid]<target) left=mid+1;
 }
 return -1;//没有搜到就返回-1
 }
 }
 

2.查找左端边界
即最后要返回left的下标

①查找左端边界为左闭右开的形式,所以要先定义left=0,right=num.length;
此时的搜索区间为[left,right)
②此时的mid也是(left+right)/2,但最好可以写成是left+(right-left)/2,从而避免溢出的情况
③当num[mid]=target时,固定右区间即可。然后缩短左区间,一直到left=right,即可找到左边界,所以,此时的只要限制固定right即可,即right=mid;
④当num[mid]<target时,缩短左区间,由于左为闭区间,所以要把Left排除,所以新的left=mid+1
④当num[mid]>target时,要扩大右区间,由于右为开区间,所以直接right=mid即可
⑤当num的元素里面没有target值时,就返回-1,否则返回左边界下标left,注意此时搜索左边界时,是直接返回下标left而不用left+1,因为left是闭区间,且结束的条件是left=right,所以若能搜到这num[left]必为target值

代码实现如下:

import java.util.*;

public class Main(){
 public int Search(int []num,int target){
 int left=0,right=num.length;
 if (right<1) return -1;
 while(left<right){
 int mid =left+(right-left)/2;//有效避免left+right导致越界的问题
 if(num[mid]=target)  right=mid;//
 else if (num[mid]>target) right=mid;//缩短右区间
 else if (num[mid]<target) left=mid+1;//缩短左区间
 }
return nums[left] == target ? left : -1;
 }
 }

3.查找右端边界
即最后要返回right的下标

①查找左端边界为左闭右开的形式,所以要先定义left=0,right=num.length;
此时的搜索区间为[left,right)
②此时的mid也是(left+right)/2,但最好可以写成是left+(right-left)/2,从而避免溢出的情况
③当num[mid]=target时,固定左区间即可。然后缩短右区间,一直到left=right,即可找到左边界,所以,此时的只要限制固定right即可,即left=mid+1;//注意是要+1,因为左区间是闭区间,而原来的num[left]我们已经搜索过了
④当num[mid]<target时,缩短左区间,由于左为闭区间,所以要把Left排除,所以新的left=mid+1
④当num[mid]>target时,要扩大右区间,由于右为开区间,所以直接right=mid即可
⑤当num的元素里面没有target值时,就返回-1,否则返回左边界下标left-1,注意此时搜索左边界时,是直接返回下标left-1而不是left,因为在第③步的时候,由于left是闭区间,所以我们进行了left+1的操作,而此时的left=left+1一定不等于target值,在num[left-1]位置的才有可能是target值(因为还有可能target不存在的情况),所以若能搜到,则num[left-1]必为target值

代码实现如下:

import java.util.*;

public class Main(){
 public int Search(int []num,int target){
 int left=0,right=num.length;
 if (right<1) return -1;
 while(left<right){
 int mid =left+(right-left)/2;//有效避免left+right导致越界的问题
 if(num[mid]=target)  left=mid+1;//左闭右开,说明左边已经搜过了
 else if (num[mid]>target) right=mid;//缩短右区间,右边没搜过,左闭右开
 else if (num[mid]<target) left=mid+1;//缩短左区间,左闭右开说明左边已经搜过了
 }
return nums[left-1] == target ? left-1: -1;
 }
 }

更详细的介绍网址链接如下:
https://www.cnblogs.com/athony/p/13206690.html
更多算法介绍的网址如下:
https://github.com/labuladong/fucking-algorithm

二、差分数组

1.、问题背景
如果给你一个包含5000万个元素的数组,然后会有频繁区间修改操作,那什么是频繁的区间修改操作呢?比如让第1个数到第1000万个数每个数都加上1,而且这种操作时频繁的。

此时你应该怎么做?很容易想到的是,从第1个数开始遍历,一直遍历到第1000万个数,然后每个数都加上1,如果这种操作很频繁的话,那这种暴力的方法在一些实时的系统中可能就拉跨了。

因此,今天的主角就出现了——差分数组。

2.算法解释

https://blog.csdn.net/qq_31601743/article/details/105352885

3.算法性质
假设原型数列存在数组d[]中,建立一个d对应的差分数组f[]。那么差分数组有如下性质。

(1)当需要对数组某一区间[l,r]内进行加减操作时,不需要操作原数组,直接对差分数组的下标为l和r+1项进行操作即可。比如对d数组中的[3,6]区间进行+1操作,则只需要在f[]数组的f[3]+1以及f[7]-1即可。

(2)可以通过差分数组,计算原数组的各项值。因为f[i]=d[i]-d[i-1],所以d[i]=f[i]+d[i-1]。由于第一项f[0]=d[0]已知,所以可以从前往后通过f[i]数组推出d[i]的各项值

for(int i=1;i<=N;i++){
d[i]=d[i-1]+f[i];
}

(3)当通过差分数组计算出了修改区间后原数组的各项值后,便可计算出原数组的前缀和。sum[i]=s[i-1]+d[i]。从前往后开始推,s[0]=d[0]均已知。求出了原数组的前缀和就可以求出某一区间[l,r]的和sum=sum[r]-sum[l-1];
注意,前缀和是包括它自己的,即区间端点是包含的,所以才要sum[l-1];

for(int i=1;i<=n;++i)  d[i]=(d[i-1]+f[i]);
for(int i=1;i<=n;i++) sum[i]=(sum[i-1]+d[i]);

3.算法应用

(1)快速处理区间加减操作

假如现在对数列中区间[L,R]上的数加上x,我们通过性质(1)知道,第一个受影响的差分数组中的元素为f[L],即令f[L]+=x,那么后面数列元素在计算过程中都会加上x;最后一个受影响的差分数组中的元素为f[R],所以令f[R+1]-=x,即可保证不会影响到R以后数列元素的计算。这样我们不必对区间内每一个数进行处理,只需处理两个差分后的数即可;

(2)询问区间和问题

由性质(2)我们可以计算出数列各项的前缀和数组sum各项的值;那么显然,区间[L,R]的和即为ans=sum[R]-sum[L-1];

4.相关题目

(1)[HDU1556]Color the ball

-N个气球排成一排,从左到右依次编号为1,2,3…N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?
-Input:每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。当N = 0,输入结束。
-Output:每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数

Solution
1.记录各次操作,对差分数组进行对应修改,改变量为1(用途1);
2.使用性质(1)计算各项的值即可;

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std; 
int d[100010],a[100010],l,r;
int main(){
	int n;
	while(scanf("%d",&n),n)
	{
		memset(d,0,sizeof(d));
		memset(a,0,sizeof(a));
		for(int i=1;i<=n;++i){
			scanf("%d%d",&l,&r);
			d[l]+=1;
			d[r+1]-=1;
		}
		for(int i=1;i<=n;++i) a[i]=a[i-1]+d[i];
		for(int i=1;i<n;++i) printf("%d ",a[i]);
		printf("%d\n",a[n]);
	}
	return 0;
}

(2) [NKOJ3754]数列游戏

-给定一个长度为N的序列,首先进行A次操作,每次操作在Li和Ri这个区间加上一个数Ci。
然后有B次询问,每次询问Li到Ri的区间和。
初始序列都为0。
-输入格式:
第一行三个整数N A B。(1<=N<=1000000,1<=A<=N,A<=B<=N)
接下来A行,每行三个数Li Ri Ci。(1<=Li<=N,Li<=Ri<=N,|Ci|<=100000000000000)。
接下来B行,每行两个数 Li Ri。范围同上。
-输出格式:
对于每次询问,输出一行一个整数。
因为最后的结果可能很大,请对结果mod 1000000007。

Solution
1.应用(1)处理区间加;
2.用性质(1)求出修改后数列,再求出相应数列和(应用2)或直接用性质(2)求解;
3.注意随时取模

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>
const long long mod=1000000007; 
using namespace std;
long long d[100010],f[100010],sum[100010];
int main(){
	int n,a,b;
	scanf("%d%d%d",&n,&a,&b);
	memset(d,0,sizeof(d));
	memset(f,0,sizeof(f));
	memset(sum,0,sizeof(sum));
	for(int i=1;i<=a;++i){
		long long l,r,c;
		scanf("%ld%ld%ld",&l,&r,&c);
		f[l]=(f[l]+c)%mod;
		f[r+1]=(f[r+1]-c)%mod;
	}
	for(int i=1;i<=n;++i)  d[i]=(d[i-1]+f[i])%mod;
	for(int i=1;i<=n;i++) sum[i]=(sum[i-1]+d[i])%mod;
	for(int i=1;i<=b;++i){
		int l,r;
		scanf("%d%d",&l,&r);
        printf("%ld\n",(sum[r]-sum[l-1])%mod);
		//printf("%ld\n",temp>=0?temp:temp+mod);//防止结果为负; 
	}
	return 0;
} 

(3)[NOIP2012提高&洛谷P1083]借教室

题解随笔:http://www.cnblogs.com/COLIN-LIGHTNING/p/8467795.html

(4)[洛谷P3948]数据结构

题解随笔:http://www.cnblogs.com/COLIN-LIGHTNING/p/8482463.html

----------------------------------3.22补充------------------------------------


/*
 * 本节描述了哈希表的使用方法、优先队列、并查集以及for迭代器的循环遍历
 * 
 *  */
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;

public class HashmapNQueueNUnionset {
	public static void main(String[] args) {
		HashMap<Integer,String> hashmap=new HashMap<>();
		hashmap.put(0, "1");
		boolean contains=hashmap.containsValue(1);
		hashmap.put(1, "a");
		hashmap.remove(1);
		
		Queue<Integer> priorityq=new PriorityQueue<>(11);//升序
		Queue<Integer> priorityqz=new PriorityQueue<>(11,newcom);//降序
		priorityq.add(1);
		priorityq.poll();
		
		
		
	}
	static 
	Comparator<Integer> newcom =new Comparator<Integer>() {
		public int compare(Integer s1,Integer s2) {
			if(s1>s2) {
				return -1;
			}else if(s1<s2) {
				return 1;
			}else {
				return 0;
			}
		}
	};
	//下面描述的为并查集,并查集主要包括三个部分:init、findFather and Union
	boolean isRoot[]=new boolean [5];
	int father[]=new int[10];
	public void init(int n) {
		for(int i=0;i<=n;i++) {
			father[i]=i;
			isRoot[i]=false;
		}
	}
	public int findfather(int x) {
		while(father[x]!=x) {
			x=father[x];
		}
		int a=x;
		while(a!=father[a]) {
			int z=a;
			a=father[a];//先走到下一个节点
			father[z]=x;//再修改节点
		}
		return x;
	}
	public void union(int a,int b) {
		int fathera=findfather(a);
		int fatherb=findfather(b);
		if(fathera!=fatherb) {
			father[fathera]=fatherb;
		}
	}
	int []arraynumber=new int[10];
	//二分查找:1.目标值 2.左边界 3.右边界
	public int binarytarget(int n){
		int left=0,right=arraynumber.length-1;
		while(left<=right) {
			int mid=left+(right-left)/2;
			if(arraynumber[mid]>n) {
				right=mid-1;
			}else if(arraynumber[mid]<n){
				left=mid+1;
			}else {
				return mid;//找到了返回位置
			}
		}
		return -1;//没找到返回-1
	}
	public int binarylefttarget(int n) {
		int left=0,right=arraynumber.length;
		if (right<1) return -1;
		while(left<right) {
			int mid=left+(right-left)/2;
			if(arraynumber[mid]==n) {
				right=mid;
			}else if(arraynumber[mid]>n) {
				right=mid;
			}else if(arraynumber[mid]<n) {
				left=mid+1;//由于是左闭右开,right一直取不到边界值,而left则可以。
			}
		}
		return arraynumber[left]==n?left:-1;
	}
	
	public int binaryrighttarget(int n) {
		int left=0,right=arraynumber.length;
		if(right<1) return -1;
		while(left<right) {
			int mid=left+(right-left)/2;
			if(arraynumber[mid]==n) mid=left+1;
			else if(arraynumber[mid]>n) right=mid;
			else if(arraynumber[mid]<n) left=mid+1;
		}
		return arraynumber[left-1]==n?left-1:-1;
	}
	//差分数组与前缀和
	/*
	 * 差分数组:
	 *   1. 一维差分:b[l]+=c,b[r+1]-=c
	 *   2. 二维差分:b[l][r]+=c,b[l-1][r]-=c,b[l][r-1]-=c,b[l+1][r+1]+=c
	 *前缀和:
	 *   1.一维前缀和:sum[i+1]=sum[i]+a[i+1];
	 *   2.二维前缀和:sum[[x+1][y+1]=sum[x-1][y]+sum[x][y-1]-sum[x-1][y-1]+a[x+1][y+1]; 
	 */
	//差分数组,从1开始存数据
	int []arraydiff;
	public void insert(int l,int r,int c) {
		arraydiff[l]+=c;
		arraydiff[r+1]-=c;
	}
	public void startdiff(int size,int samplenumber) {
		Scanner cin=new Scanner(System.in);
		for(int i=1;i<size;i++) {
			int c=cin.nextInt();
			insert(i,i,c);//初始化就相当于自己的区间里面插入c,会导致i+1的数字就是-c,即最后会抵消
		}
		for(int i=0;i<samplenumber;i++) {
			int sampleleft=cin.nextInt();int sampleright=cin.nextInt();
			int samplec=cin.nextInt();
			insert(sampleleft,sampleright,samplec);
		}
		//输出全部数组
		int []dis = new int [arraydiff.length];
		for(int i=1;i<arraydiff.length;i++) {
			dis[i]=dis[i-1]+arraydiff[i];
		}
	}
	//list三种遍历方式
	public void forcheck1() {
		LinkedList<Integer> newlist=new LinkedList<>();
		Iterator<Integer> list_itera=newlist.iterator();
		while(list_itera.hasNext()) {
			int i=list_itera.next();
		}
	}
	public void forcheck2() {
		LinkedList<Integer> newlist=new LinkedList();
		//Iterator<Integer> iterator=newlist.iterator();
		for(Integer i:newlist) {
			int w=i;
		}
	}
	public void forcheck3() {
		LinkedList<Integer> newlist=new LinkedList();
		for(int i=0;i<newlist.size();i++) {
			int w= newlist.get(i);
		}
	}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值