zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    数据结构之利用递归解决八皇后问题
    SpringBoot注解大全
    shiro框架中出现的错误There was an unexpected error (type=Unauthorized, status=401).
    shiro权限控制
    shiro的整合“心路历程”
    _T宏的使用
    根据文件的路径 分割文件名 文件后缀
    将选中的图片文件以图片形式 (显示在对话框内)或(直接发送出去)
    对话框内粘贴图片文件会直接发送,改为粘贴图片后直接将图片显示在对话内。
    数据库按日期查询,右侧日期内的数据无法现实问题。
  • 原文地址:https://www.cnblogs.com/EdwardLiu/p/5138198.html
Copyright © 2011-2022 走看看