刷leetcode碰到生存人数一题,有使用差分数组进行求解的,看到很多题解都是直接贴代码,没有差分数组原理的讲解,看的云里雾里的,遂查了一下差分数组的原理和使用场景,茅塞顿开!!
题目:生存人数
代码:
public int maxAliveYear(int[] birth, int[] death) {
int[] dp = new int[102];
for (int i = 0; i < birth.length; ++i) {
dp[birth[i] - 1900] += 1;
// 这里要注意,当年死的人还算在当年的人数,所以是下一年少一人
dp[death[i] - 1900 + 1] -= 1;
}
int start = 0;
int max = 0;
int maxYear = 0;
for (int i = 0; i < 102; ++i) {
start += dp[i];
if (start > max) {
max = start;
maxYear = i;
}
}
return maxYear + 1900;
}
使用场景
如果给你一个包含5000万个元素的数组,然后会有频繁区间修改操作,比如让第1个数到第1000万个数每个数都加上1,而且这种操作是频繁的。此时应该怎么做?很容易想到的是,从第1个数开始遍历,一直遍历到第1000万个数,然后每个数都加上1,这种方法虽然可行,一旦区间操作频繁系统的压力就很大,因此产生了更优解:差分数组!!
差分数组原理
比如我们现在有一个数组arr,arr={0,2,5,4,9,7,10,0}
那么差分数组是什么呢?其实差分数组本质上也是一个数组,我们暂且定义差分数组为d,差分数组d的大小和原来arr数组大小一样,而且d[i]=arr[i]-arr[i-1],且d[i]=0,它的含义是什么?就是原来数组i位置上的元素和i-1位置上的元素作差,得到的值就是d[i]的值。所以,例子中的arr数组其对应的差分数组值如下图所示。
那么构造了这么个玩意有什么用呢?难道是来浪费宝贵的内存空间的?嗯,确实是来浪费宝贵的内存了,但是却换了时间上的高效。
现在我们有这么一个区间修改操作,即在区间1~4上,所有的数值都加上3。我们不要傻傻地遍历arr数组的[1,4]范围,然后再分别给每个值加上3,我们此时更改差分数组d即可。
显而易见,差分数组d在[2,4]范围内的值都不用改变,只需要改变差分数组位置1和位置5的值即可,即d[1]=d[1]+3,而d[5]=d[5]-3,其余不变,为什么呢?因为差分数组的定义——d[i]=arr[i]-arr[i-1]
现在,我们如何根据差分数组d来推测arr中某一个位置的值呢?
比如,此时,我们想知道arr[1]的值,我们不能直接通过arr[1]得到原来的值,因为在区间修改的操作中我们并没有修改arr的值,因此我们必须从前往后遍历递推,由于d[0]=arr[0]-0(我们定义arr[0]的前一个数为0),那么arr[0]=d[0]=0,又由于d[1]=arr[1]-arr[0]=5,那么arr[1]=5+arr[0]=5。以此类推,由于d[2]=arr[2]-arr[1]=3,所以arr[2]=3+arr[1]=8。
总结
可以看到,如果需要对L~ R范围内所有数都进行相同的操作,我们不需要从L~R遍历arr然后在每个值上进行相同操作,只需要在差分数组d中改变L和R+1的值即可。但是在查询arr数组中某个位置的数时,却要根据差分数组从前往后递推求值。
所以,该方法适用于区间频繁修改,而且这个区间范围是比较大的,离线查询的情况。