Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

mari tang
mari tang

Posted on

     

Algorithms: Range Sum Query

It's algorithm time again!

This one's a leetcode easy, but there's a lot to be learned from it.

Here's the problem:

RANGE SUM QUERY:

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.

So, if we have an array of, say[1,2,3,4,5], and indices of2 and4, we'd add3 + 4 + 5 to get12.

Pretty simple, right? We can just loop over our array and sum up whatever's between (and including) the indexes we get.

functionNumArr(arr){this.data=arr;}NumArr.prototype.rangeSum=function(i,j){letoutput=0;for(i;i<=j;i++){output+=this.data[i];}returnoutput;}
Enter fullscreen modeExit fullscreen mode

This is not a horrible solution. If we query our array only once or twice, or if we expect to get in a variety of arrays, this works. Computers are very good at addition-it's possibly the fastest operation a CPU can do. In fact, it's so fast that it actually does pass the leetcode tests.

However, there are two stipulations provided, which give us space to improve and optimize our solution.

  1. You may assume that the array does not change.
  2. There are many calls to sumRange function.

So, let's think about how this works. If we're doing a sufficient number of sums, some of them will probably hit the same range, right? We can cache our solution and look it up instead of re-calcluating it. Let's put a cache on the constructor.

Caching

What shape should the cache take?
If we think about it for a minute, a two dimensional array seems to make the most sense- we're adding a range fromi toj, so we can dump our cached results atthis.cache[i][j]

functionNumArray(arr){this.data=arr;this.cache=arr.map(()=>[]);//fill cache with one empty array per item in arr}NumArray.prototype.sumRange=function(i,j){if(!this.cache[i][j]){letoutput=0;for(letk=i;k<=j;k++){output+=this.data[k];}this.cache[i][j]=output;}returnthis.cache[i][j];}
Enter fullscreen modeExit fullscreen mode

This works, but the extra task of storing stuff in our cache makes the initial query to a range much slower. Each successive time we query is gonna be plenty fast, but it also counts on us landing on our exact range again.

Is there an even better solution?

Short answer: yes. very yes.

Getting there was a bit of a pain. Initially, I'd glanced at the leetcode solution and saw something about precomputing the results. I took this to mean that we should pre-calculate and cache the entire thing- and why not?

If we're computing any range sum, we're doing repeated work. i.e. if we sum the values from index0 to index5, we've calculatedarr[0]+arr[1],arr[0]+arr[1]+arr[2], etc etc. This means that we can simply cache some of those intermediary values as we go.

I could intuit that I could at least get the first set of sums like this:

functionNumArray(arr){this.data=arr;this.cache=[]arr.reduce((acc,val)=>{acc+=val;cache.push(val)returnacc;},0)}
Enter fullscreen modeExit fullscreen mode

When this finishes computing, our cache will be an array with all of the sums from0 ton.[(sum of index 0), (sum of index 0 to index 1), (sum of index 0 to index 2), ...., (sum of index 0 to index n)]

That's a nice little bit of computation that makes our lives easier, but how would we think about getting all of the sums ofindex 1 to index n, thenindex 2 to index n, all the way up toindex n-1 to index n?

I tried to figure out if there was an easy way to compute all possible sums, but kept gettingO(n^2) solutions that would time out on leetcode.

So I tried to figure out what sort of patterns I could see in a test case, modelling it by hand with a very simple array of[0,1,2,3,4]

There are a few interesting things going on. We can see that each successive row is basically made by taking the previous row and subtracting whatever integer we're skipping.

The first row is made by summing all numbers.
The second row can be made by taking the first row and subtracting the first number
The third row can be made by taking the second row and subtracting the second number
The fourth row can be made by taking the third row and subtracting the third number
...and so on.

It took a bit for this to sink in, but the secret here depends on rearranging that previous insight:

In other words, we can find any range fromi toj by taking the sum of numbers from index0 toj, and subtracting the sum of numbers from index0 toi.

With this being the case, all of the data we need is created when we make our initial pass. We're guaranteed to have the appropriate sum for index0 toi, and likewise, for index0 toj. We don't even have to cache every possible answer to have anO(1) operation.

Here's what my final result looks like:

constNumArray=function(nums){this.cache=[0];// done to avoid an "if" check for the first numberfor(leti=0;i<nums.length;i++){this.cache.push(this.cache[i]+nums[i]);}}NumArray.prototype.sumRange=function(i,j){returnthis.cache[j+1]-this.cache[i];}
Enter fullscreen modeExit fullscreen mode

This saves immensely on time complexity- Our initial pass through the array isO(n), which is the same time complexity as calculating a single range sum in the first place (i.e. if you want to sum from0 toarr.length-1). Afterwards, getting any successive answers is anO(1) operation!

The only real tradeoff is that the space complexity of this solution is alsoO(n), but it's well worth it.

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
theodesp profile image
Theofanis Despoudis
Senior Software Engineer @wpengine, Experienced mentor @codeimentor, Technical Writer @fixate.io, Book author
  • Location
    Ireland
  • Work
    Senior Software Engineer at WP Engine
  • Joined
• Edited on• Edited

For me, I would make it a little bit different. Notice that you don't need to use the cache as an array but just as a map. You just store the aggregated sum over all indices and when we ask for the sumRange we subtract the end from the first-1:

constNumArray=function(nums){this.cache=newMap();for(leti=0;i<nums.length;i++){if(i===0){// First elementthis.cache.set(i,nums[i])}else{this.cache.set(i,this.cache.get(i-1)+nums[i])// Every other element}}}NumArray.prototype.sumRange=function(i,j){returnthis.cache.get(j)-this.cache.get(i-1);// Just subtract the j index from the i-1 which is the sum of all array elements up to i.}newNumArray([1,2,3,4,5]).sumRange(2,4)

sumRange(2,4) -> cache(4) - cache(1) -> 15-3 = 12

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

"Poets do not go mad; but chess-players do. Mathematicians go mad, and cashiers; but creative artists very seldom." -GK Chesterton
  • Work
    Software Engineer at PayPal
  • Joined

More frommari tang

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp