[leetcode]——数位运算及二叉索引树

前言

二叉索引数。

二进制中负数

一个8位的二进制

  • 无符号:表正数,范围是:0-255
  • 有符号:
    • 表正数,范围是:0-127
    • 表负数,范围是:-1(10000001) - -128(1000000)(没有-0,-0就是0)

但在一般计算中,我们不使用上述负数的原码,而使用它的补码(除符号位取反+1),这是为何?


以 10 + (-8)为例,如果直接原码计算,结果出错:
在这里插入图片描述
如何解决呢?(引入模的思想):
考虑一个时钟,指向了10点,需要调回2点,可以表示成(1). 10 - 8 = 2,即往回调8,如果考虑模,我们也可以往前调 12(表范围) - 8 = 4,即(2). 10 + 4 = 14 --> 14 % 12 = 2

把这种思想引入到二进制中,10 - 8 可以写成 10 + 2^8(表范围)- 8,而2^8-8 = (2^8 - 1) - 8 + 1,其中,(2^8 - 1) - 8计算如下:
在这里插入图片描述
然后再+1。

也就是说,遇到x-y时,先把y取反,再+1,得到z,然后就等价于计算x+z了。(z是y的补码)

把加法拓展到任何运算,只要我们用补码来表示二进制,就可以忽略符号位了,下面是与运算的例子。

x&-x

x&-x保留二进制x最低位的1,其余位置都为0.

假设A是01串,A’是A取反,B是全0串,C是全1串。下面考虑x的奇偶的不同表示:

  • 偶数:表示成(A)1(B)
  • 奇数:表示成(A)1

则-x表示为(补码):

  • 偶数:(A’)0© + 1 = (A’)1(B)
  • 奇数:(A’)0 + 1 = (A’)1

所以x&-x为:

  • 偶数:(B)1(B)
  • 奇数:(B)1

即不管奇偶,该位运算都是保留最低位的1,其余位置统统置零。

x&(x-1)

x&(x-1)消除二进制x最低位的1,其余位置不变.

x-1表示为:

  • 偶数:(A)0©
  • 奇数:(A)0

所以x&(x-1)为:

  • 偶数:(A)0(B)
  • 奇数:(A)0

即不管奇偶,该位运算都是消去最低位的1,其余位置保持不变。

二叉索引树

以一个长度为9的数组A为例,一般要求前缀和i(表示到下标i为止(包含i)前面所有元素的和),只需要遍历这个数组,创建一个sum数组B。

//为方便讲解,sum[i]实际=A[0]+A[1]+...A[i-1]
int[] A = new int[9]; // 不管它里面具体是什么元素
int[] sum = new int[A.length + 1];
sum[1] = A[0];
for(int i = 2; i <= A.length; i ++){
	sum[i] = sum[i-1] + A[i-1];
}

如果要方位[0,i]中间的前缀和,则

int ans = sum[i] + A[i];

如果需要访问[i,j]中间的和,则

int ans = sum[j] - sum[i] + A[j]

建立sum数组的时间为O(n),查找的时间复杂度为O(1),而如果A中的某一个元素被修改了,则需要重新构建这个sum,所以修改的时间复杂度为O(n)。

下面所讲的二叉索引树的建立时间为O(nlogn),查找时间为O(logn),修改的时间为O(logn)。

和sum数组相同,二叉索引树也是存在一个数组中,记作数组C。不同的是,C[i]记录的是[i-lowbit(i)+1-1,i-1]间的和。
这里的lowbit指的是对于一个二进制i,其最低位的1的构成的二进制数,也就是上述x&-x。举个例子:8的二进制为1000,则lowbit(8) = 8;4的二进制为100,则lowbit(4) = 4;3的二进制为11,则lowbit(3) = 1。

代码:
首先定义一个lowbit函数:

public int lowbit(int x){
	return x & - x
}

然后根据输入的数组A,构建数组C:

public int[] build(int[] A){
	int[] C = new int[A.length + 1];
	for(int i = 1; i < C.length; i++){
		for(int j = i - lowbit(i) + 1, j <=i; j ++){
		C[i] += A[j - 1]; // 注意这里的j-1,也就代表了C[i]所表示的区间为[i-lowbit(i)+1-1,i-1]
		}
	}
	return C;
}

接着定义前缀和的计算函数:

public int get_num(int index){
	index = index + 1;
	int ans = 0;
	while(index > 0){
		ans += C[index];
		index -= lowbit(index);
	}
}

i…j区间的计算函数:

public int i_j_sum(int i, int j){
	int sum_i = get_sum(i);
	int sum_j = get_sum(j);
	return sum_j - sum_i + A[i];
}

A中第k个元素改变之后,对C的更新函数:

public int[] update(int index, int new_num){
	int old = A[index];
	index = index + 1;
	while(index < C.length){
		C[index] += new_num - old;
		index += lowbit(index); 
	}
}

参考

树状数组

关于为什么负数要整数取反+1表示,而不指定首位为符号位,其它位为数值位。

如何理解x&(-x)和x&(x-1)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值