As andy pointed out in the comments: The queries are quite different in nature, so the "best" solution will probably depend on which query type is executed most frequently.
However, the task
- Find the sum of the values between indexes i and j.
can efficiently be solved by performing a scan/prefix sum computation of the array. Imagine an array of int values
index: 0 1 2 3 4 5 6
array: [ 3, 8, 10, -5, 2, 12, 7 ]
Then you compute the Prefix Sum:
index: 0 1 2 3 4 5 6, 7
prefix: [ 0, 3, 11, 21, 16, 18, 30, 37 ]
Note that this can be computed particularly efficient in parallel. In fact, this is one important building block of many parallel algorithms, as described in the thesis "Vector Models for Data-Parallel Computing" by Guy E. Blelloch (thesis as PDF File).
Additionally, it can be computed in two ways: Either starting with the value from array[0], or starting with 0. This will, of course, affect how the resulting prefix array has to be accessed. Here, I started with 0, and made the resulting array one element longer than the input array. This may also be implemented differently, but in this case, it makes it easier to obey the array limits (although one would still have to clarify in which cases indices should be considered as inclusive or exclusive).
However, given this prefix sum array, one can compute the sum of elements between indices i and j in constant time, by simply subtracting the corresponding values of the prefix sum array:
sum(n=i..j)(array[n]) = (prefix[j+1] - prefix[i])
For example:
sum(n=2..5)(array[n]) = 10 + (-5) + 2 + 12 = 19
prefix[5+1] - prefix[2] = 30 - 11 = 19
For task 2,
- Update value at index i to a new given value.
this would mean that the prefix sums would have to be updated. This could be done brute-force, in linear time, by just adding the difference of the old value and the new value to all prefix sums that appear after the modified element (but for this, also see the notes in the last section of this answer)
The tasks 3 and 4
- Find the maximum of the values between indexes i and j.
- Check whether the subarray between indexes i and j, both inclusive, is in ascending or descending order.
I could imagine that the maximum value could simply be tracked while building the prefix sums, as well as checking whether the values are only ascending or descending. However, when values are updated, this information would have to be re-computed.
In any case, there are some data structures that deal with prefix sums in particular. I think that a Fenwick tree might allow to implement some of the O(n) operations mentioned above in O(logn), but I have not yet looked at this in detail.