Leetcode: Count of Range Sum


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.

Note:
A naive algorithm of O(n2) is trivial. You MUST do better than that.

Example:
Given nums = [-2, 5, -1], lower = -2, upper = 2,
Return 3.
The three ranges are : [0, 0], [2, 2], [0, 2] and their respective sums are: -2, -1, 2.

參考:https://leetcode.com/discuss/79083/share-my-solution

First of all, let's look at the naive solution. Preprocess to calculate the prefix sums S[i] = S(0, i), then S(i, j) = S[j] - S[i]. With these prefix sums, it is trivial to see that with O(n^2)time we can find all S(i, j) in the range [lower, upper]

Java - Naive Solution(這個做法為了讓所有區間都能表示成一個區間減另一個區間,size額外增加了1,sums[i]定義為前i個元素之和,這樣連只包含第一個元素的區間的和都可以表示為sums[1]-sums[0],這樣寫不用再分類討論,挺好的)

public int countRangeSum(int[] nums, int lower, int upper) { int n = nums.length; long[] sums = new long[n + 1]; for (int i = 0; i < n; ++i) sums[i + 1] = sums[i] + nums[i]; int ans = 0; for (int i = 0; i < n; ++i) for (int j = i + 1; j <= n; ++j) if (sums[j] - sums[i] >= lower && sums[j] - sums[i] <= upper) ans++; return ans; } 

However the naive solution is set to TLE intentionally

Now let's do better than this.

Recall count smaller number after self where we encountered the problem

  • count[i] = count of nums[j] - nums[i] < 0 with j > i

Here, after we did the preprocess, we need to solve the problem

  • count[i] = count of a <= S[j] - S[i] <= b with j > i
  • ans = sum(count[:])

Therefore the two problems are almost the same. We can use the same technique used in that problem to solve this problem. One solution is merge sort based; another one is Balanced BST based. The time complexity are both O(n log n).

The merge sort based solution counts the answer while doing the merge. During the merge stage, we have already sorted the left half [start, mid) and right half [mid, end). We then iterate through the left half with index i. For each i, we need to find two indices k and j in the right half where

  • j is the first index satisfy sums[j] - sums[i] > upper and
  • k is the first index satisfy sums[k] - sums[i] >= lower.

Then the number of sums in [lower, upper] is j-k. We also use another index t to copy the elements satisfy sums[t] < sums[i] to a cache in order to complete the merge sort.

Despite the nested loops, the time complexity of the "merge & count" stage is still linear. Because the indices kjt will only increase but not decrease, each of them will only traversal the right half once at most. The total time complexity of this divide and conquer solution is then O(n log n).

One other concern is that the sums may overflow integer. So we use long instead.

 

方法一:mergesort, O(NlogN) running time 最快但是不喜歡這種寫法,不理解11行

 1 public class Solution {
 2     public int countRangeSum(int[] nums, int lower, int upper) {
 3         int n = nums.length;
 4         long[] sums = new long[n + 1];
 5         for (int i = 0; i < n; ++i)
 6             sums[i + 1] = sums[i] + nums[i];
 7         return countWhileMergeSort(sums, 0, n + 1, lower, upper);
 8     }
 9     
10     private int countWhileMergeSort(long[] sums, int start, int end, int lower, int upper) {
11         if (end - start <= 1) return 0;
12         int mid = (start + end) / 2;
13         int count = countWhileMergeSort(sums, start, mid, lower, upper) 
14                   + countWhileMergeSort(sums, mid, end, lower, upper);
15         int j = mid, k = mid, t = mid, r = 0;
16         long[] cache = new long[end - start];
17         for (int i = start; i < mid; ++i, ++r) {
18             while (k < end && sums[k] - sums[i] < lower) k++;
19             while (j < end && sums[j] - sums[i] <= upper) j++;
20             while (t < end && sums[t] < sums[i]) cache[r++] = sums[t++]; //start merging
21             cache[r] = sums[i];
22             count += j - k;
23         }
24         System.arraycopy(cache, 0, sums, start, r);
25         return count;
26     }
27 }

 默認方法:construct BST (好理解很多) , Time: O(NlogN)

這個做法是建立BST,把prefix sum作為TreeNode.val存進去,為了避免重復的TreeNode.val處理麻煩,設置一個count記錄多少個重復TreeNode.val, 維護leftSize, 記錄比該節點value小的節點個數,rightSize同理

由於RangeSum S(i,j)在[lower,upper]之間的條件是lower<=sums[j+1]-sums[i]<=upper, 所以我們每次insert一個新的PrefixSum sums[k]進這個BST之前,先尋找一下(rangeSize)該BST內已經有多少個PrefixSum(叫它sums[t]吧)滿足lower<=sums[k]-sums[t]<=upper, 即尋找有多少個sums[t]滿足: 

sums[k]-upper<=sums[t]<=sums[k]-lower

BST提供了countSmaller和countLarger的功能,計算比sums[k]-upper小的RangeSum數目和比sums[k]-lower大的數目,再從總數里面減去,就是所求

 1 public class Solution {
 2     private class TreeNode {
 3         long val = 0;
 4         int count = 1;
 5         int leftSize = 0;
 6         int rightSize = 0;
 7         TreeNode left = null;
 8         TreeNode right = null;
 9         public TreeNode(long v) {
10             this.val = v;
11             this.count = 1;
12             this.leftSize = 0;
13             this.rightSize = 0;
14         }
15     }
16 
17     private TreeNode insert(TreeNode root, long val) {
18         if(root == null) {
19             return new TreeNode(val);
20         } else if(root.val == val) {
21             root.count++;
22         } else if(val < root.val) {
23             root.leftSize++;
24             root.left = insert(root.left, val);
25         } else if(val > root.val) {
26             root.rightSize++;
27             root.right = insert(root.right, val);
28         }
29         return root;
30     }
31 
32     private int countSmaller(TreeNode root, long val) {
33         if(root == null) {
34             return 0;
35         } else if(root.val == val) {
36             return root.leftSize;
37         } else if(root.val > val) {
38             return countSmaller(root.left, val);
39         } else {
40             return root.leftSize + root.count + countSmaller(root.right, val);
41         }
42     }
43 
44     private int countLarger(TreeNode root, long val) {
45         if(root == null) {
46             return 0;
47         } else if(root.val == val) {
48             return root.rightSize;
49         } else if(root.val < val) {
50             return countLarger(root.right, val);
51         } else {
52             return countLarger(root.left, val) + root.count + root.rightSize;
53         }
54     }
55 
56     private int rangeSize(TreeNode root, long lower, long upper) {
57         int total = root.count + root.leftSize + root.rightSize;
58         int smaller = countSmaller(root, lower);    // Exclude everything smaller than lower
59         int larger = countLarger(root, upper);      // Exclude everything larger than upper
60         return total - smaller - larger;
61     }
62 
63     public int countRangeSum(int[] nums, int lower, int upper) {
64         if(nums.length == 0) {
65             return 0;
66         }
67         long[] sums = new long[nums.length + 1];
68         for(int i = 0; i < nums.length; i++) {
69             sums[i + 1] = sums[i] + nums[i];
70         }
71         TreeNode root = new TreeNode(sums[0]);
72         int output = 0;
73         for(int i = 1; i < sums.length; i++) {
74             output += rangeSize(root, sums[i] - upper, sums[i] - lower);
75             insert(root, sums[i]);
76         }
77         return output;
78     }
79 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM