Given an integer array nums, return the number of range sums that lie in [lower, upper] inclusive.
Range sum S(i, j) is defined as the sum of the elements in nums between indices i and j (i ≤ j), inclusive.

题意: 

在一个数组里统计 区间的 sum(i,j), 然后    lower<=sum(i,j)<=upper. 

分析: 一般求sum(i,j) =  sum(0,j) - sum(0,i-1) , 因此需要先 计算 sum (0,0), sum(0,1) ....sum(0,n-1)

examples: nums = [-2,5,-1], lower = -2, upper = 2,  output: 3

基本算法采用 merge sort  先统计 sum(0,i), 注意为了方便处理 先放个 0 进去  得到 [0, -2, 3, 2] ,即 s(0,0) s(0,1) s(0,2),s(0,3)

为何要先放个0 进去, 因为  任意一 s(i,i) -0 都是 s(i,i) 所以一开始一定要放个0进去,避免 s(i,i) - NULL 
s(0,0) s(0,1) 这样merge 时右边减去左边 就是 s(1,1)
s(0,2) s(0,3) merge 时 可以得到 s(3,3)
然后 [s(0,0), s(0 1)] 和 [s(0 2) , s(0 3)] merge 时 右边减去左边可以得到 s(1,2) s(2,2) , s(1,3) s(2,3)
这样 s(0,0) s(0,1) s(1,1) s(0,2) s(0 3) s(3 3) s(1 2 ) s(2 2) s(1 3 ) s(2 3) 10个s(i,j) 就全部得到了。

换成 具体例子来看: [0  -2 3 2],  merge sort 时 先分裂成 四个子 数组 [0] [-2] [3] [2]

1.  [0] [-2] 在merge 前 计算    -2<= -2 - 0<=2 即  s(1,1) ,因此 count = 1 , merge 后变成 [-2, 0]

2. [3] [2] 在 merge 前 计算  2 -3 = -1 这是 s(0,3) - s(0,2) = s(3,3) ,符合 条件, 因此 count ++ = 2, merge 后变成 [2, 3]

3. 继续统计和merge,经过上2步, left = [-2, 0] right = [2,3] , 继续统计 right 和 left 关系, 注意 right 边 s(0,j) left s(0,i) 中, j>i 这是merge sort 的特性,算法本质就是利用这个特性。  然后 2- -2 =4 不符合, 2-0 =2 符合 , 3--2 = 5 不符合, 3-0 =  3不符合, 此时count ++ =3, 然后merge 后的数组变成 [-2, 0, 2, 3],算法结束。 

4. 这里还有个ticky 的地方就是 left 和 right 都是排序好的数组, 然后统计出 lower <= right j - left i <= upper 时, 不需要二重循环,可以优化。 

举个例子:

left = [1,4,5,8] right = [2,4,9,13] , lower,upper= [3,5]
for(int k=s; k<=mid; k++){
while(i<=e && nums[i]-nums[k] <lower) i++;
while(j<=e && nums[j]-nums[k] <=upper) j++;
count += j-i; // 这里不用j-i+1, 是因为 i和j就算重合, j 都会往下增一位。比如 left =[0] | right=[1] , lower,up =[1 1]
}

依次遍历 left, 然后 在 right 里设 i j 指针, ri -leftx < lower 排除, 那么ri - left(>x) 一定也是< lower 的,因此 left(>x) > leftx
j 指针, 寻找 rj -leftx <=upper 的,并且j 会停在 rj-left >upper 那个位置,然后 j -i 就是leftx 时 满足条件的个数。
问题: 为何left x+1 位置时 i j 还可以从当前位置寻找而不是起始位置呢呢?
因为 leftx+1 >= leftx , 那么ri-leftx <lower ,那么 ri-leftx+1 一定可以 <lower,所以从i开始 就可
同理, rj-left<= upper, 那么 rj -leftx+1 也<= upper.. 所以j+1 可以从j 位置

class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
       long[] sums = new long[nums.length+1];
        
       for(int i=0; i< nums.length; i++){
           sums[i+1] = sums[i]+ nums[i];  //必须把sums[0] = 0 否则  像i=j的情况没法处理,  sums[i] - sums[0] 才可以。
       }
      
      return merge_count(sums,0,sums.length-1,lower, upper);
  }     
    
    private int merge_count(long[] nums, int s, int e, int lower, int upper){
        
        if(s<e){
            int mid = (s+e)/2;
            int count = merge_count(nums,s,mid,lower,upper) + merge_count(nums,mid+1,e,lower,upper);
            
            int i= mid+1;
            int j= mid+1;
            //left = [1,4,5,8] right = [2,4,9,13] , lower,upper= [3,5]
            for(int k=s; k<=mid; k++){
               // System.out.println("numsk: "+nums[k]+" s: "+s+ " mid: "+mid);
                while(i<=e && nums[i]-nums[k] <lower) i++;
                  
                j=i;
                while(j<=e && nums[j]-nums[k] <=upper) j++;
                //System.out.println(i+"  "+j);
                count += j-i;  // 这里不用j-i+1, 是因为 i和j就算重合, j 都会往下增一位。比如 left =[0] | right=[1] , lower,up =[1 1]
            }
            merge(nums,s,mid,e);  
            return count;
        }
        
        return 0;
    }
      
    private void merge(long[] nums, int start, int mid, int end){
        long[] tmp = new long[end-start+1];  //merge sort 需要的额外空间在此
        
        int i= start;
        int j= mid+1;    
        int k=0;
        //for(; i<mid && j<end; tmp[k++]=(arr[i]<arr[j]?arr[i++]:arr[j++]));
        while(i<=mid && j<=end){      
            if(nums[i] < nums[j]){
                tmp[k++] = nums[i];
                i++;
            }
            else{
                tmp[k++] = nums[j];
                j++;
            }
        }
        
        //处理merge 完剩余部分
        while(i<=mid){tmp[k++] = nums[i++];}
        while(j<=end) {tmp[k++] = nums[j++];}
        
        // 把tmp copy 进nums
        int s = start;
        for(long num: tmp){
            nums[s++] = num;
        }
        
    }    
  
}

  

 方法二: 这题可以用BST 解决,但普通的BST 会退化成 数组,造成TLE。 不过BST 构造Node 的思想非常值得学习。 待补充。

 

posted on 2018-10-22 05:03  KeepAC  阅读(97)  评论(0编辑  收藏  举报