Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Table of contents
1.
Introduction
2.
Problem Statement
2.1.
Let us look at some of the examples to make the problem statement clear
3.
Approach
3.1.
Brute Force Approach
3.2.
Efficient Approach
3.3.
Brief Working of the Segment Tree
4.
Pseudo Code
5.
Python3 Implementation
5.1.
Time Complexity
5.2.
Space Complexity
6.
FAQs
7.
Key Takeaways
Last Updated: Mar 27, 2024

Maximize value of a pair from two given arrays based on given conditions

Author HET FADIA
1 upvote

Introduction

This article aims to familiarise you with finding out the maximum value of a pair from two given arrays when a condition is given. 

To brush up on your knowledge of Segment Tree and Range Query,  you can read the article Segment Tree and Range Query on Coding Ninjas Studio.

Let’s see the problem statement in the next section.

Problem Statement

There is an integer K and there are two arrays int A[] and int B[] consisting of N integers. We have to find the maximum value of B[i] + B[j] + abs(A[i]-A[j]) for two distinct indices i and j such that abs(A[i]-A[j])<=k

Here  the constraints are : 

1<=N<=105 

2-31<=A[i]<=231-1 and 2-31<=B[i]<=231-1 for all 0<=i<N.

2-31<=K<=231-1

Let us look at some of the examples to make the problem statement clear

Input 1

A=[5, 6, 9, 10]
B=[3, 0, 10, -10]
K=1

Output 1:

4

Explanation:

As k=1, we can select (i,j) = (0,1) and (i,j) = (2,3). Here we are using 0 based indexing.

So for i=0 and j=1, B[i]+B[j] + abs(A[i]-A[j]) = 3+0 + abs(6-5) = 4.

Similarly i=2 and j=3, B[i]+B[j] + abs(A[i]-A[j]) = 10 + -10 + 1 = 1

Input 2

A=[2,2,2,2]
B=[4,5,19,18]
K=1

Output 2:

37

Explanation:

As k=1, we can select all (i,j) i!=j as the difference between any two elements of the array is less than k.

Also (i,j) = (2,3) , B[i]+B[j] + abs(A[i]-A[j]) = 19 + 18 + 0 = 37.

Input 3

A=[1,21,20,30]
B=[4,5,19,18]
K=5

Output 3:

25

Explanation:

As k=5, we can only select (i,j) = (1,2).

For (i,j) = (1,2), B[i]+B[j] + abs(A[i]-A[j]) = 5 + 19 + 1 = 25.

Input 4

A=[1,10,20,30]
B=[4,5,19,18]
K=5

Output 4:

-1

Explanation:

The difference between any A[i] and A[j] where i is not equal to j, is greater than k. 

So no i,j satisfies abs(A[i]-A[j]) <=k.

Input 5

A=[1,10,20,30]
B=[4,5,3,7]
K=11

Output 5:

20

Explanation:

At (i,j) = (2,3) B[i]+B[j] + abs(A[i]-A[j]) = 3 + 7 + abs(30-20) = 20

Also read, Euclid GCD Algorithm

Approach

Brute Force Approach

Let us talk about the brute force approach. In the brute force approach we check for every (i,j) pair and find the pair having maximum value B[i]+B[j] + abs(A[i]-A[j]). 

As we check for every (i,j) pair, the time complexity will be O(NxN).

The extra space complexity will be O(1) as we would only have one variable to store the answer. However, the input array contains N elements, so the space complexity is O(N).

Efficient Approach

Now, let us see an efficient approach.

We can see that B[i]+B[j] + abs(A[i]-A[j]) and abs(A[i]-A[j])<=k can be one of the two cases

  1. B[i]+B[j] + A[i]-A[j] = (B[i] + A[i]) + (B[j]-A[j]) and 0<=A[i]-A[j]<=k
  2. B[i]+B[j]-  A[i]+A[j] = (B[i] - A[i]) + (B[j]+A[j]) and 0<=A[j]-A[i]<=k

Let us consider the second case where we have to find (i,j) such that (B[i] - A[i]) + (B[j]+A[j]) is maximum and 0<=A[j]-A[i]<=k

So our idea is to iterate through A and find a j such that 0<=A[j]-A[i]<=k and (B[i] - A[i]) + (B[j]+A[j]) is maximum for that iteration. 

So now the problem is that for index i, there may be multiple number of j’s such that 0<=A[j]-A[i]<=k. So finding the j such that (B[i] - A[i]) + (B[j]+A[j]) is maximum will not be efficient if we check for each j.

Now for same i, the value of (B[i] - A[i]) + (B[j]+A[j]) only depends on the (B[j]+A[j]).

As 0<=A[j]-A[i]<=k, we can see that sorting the arrays based on A will help us because as A[i] increases, A[j] also increases.

Now we sort both the arrays based on A. Now, as i increases, the A[i] increases, so the j increases. Thus we get all the required elements in front of it.

Consider :

A=[1,100,10,91,20,11]

B=[4,5,19,18,18,2]

K=10

Now for i=0(0 based indexing), A[i]=A[0]=1 and so j=2 and j=5 satisfies abs(A[i]-A[j])<=k.

For i=1, j=3 satisfies.

For i=2, A[i]=10 and so j=0, j=4 and j=5 satisfies.

But now, we sort the array based on of A.

A= [1, 10, 11, 20, 91, 100]

B= [4, 19, 2, 18, 18, 5]

Now for i=0, j=1,2 satisfies the condition 0<=A[j]-A[i]<=k

For i=1, j= 2 and 3 satisfies the condition  0<=A[j]-A[i]<=k

So we can see that by sorting on A, the indices j which satisfies the condition 0<=A[j]-A[i]<=k are continuous and they are greater than the current index. Thus the indices satisfying the condition are (i+1,i+2,... j).

Now, finding the maximum value among those indices can be easily done using a Segment Tree. We can build the segment Tree to get maximum value in a continuous segment of the array in log(N) time.

Brief Working of the Segment Tree

  • The segment tree is used for finding maximum and minimum and various operations on a continuous segment of an array in log(N) time. In our case, we want to determine the maximum value of a continuous segment of the sumarr.
  • It is a tree where each node contains its children's max(here) value. Refer to the below figure for more understanding. The height of the segment tree is log(N).
  • Now let us suppose we have to answer the query a[l… r]. For this, we check if the current node contains the value of [l… r] only. If so, we return the value. If not, we recursively visit the left child and the right child. We note that for each level, we visit a maximum of 4 nodes, and as the height is log(N), the time for each query is maximum 4 x log(N).

 

Segment Tree for an array [1,21,20,30].

  • Here the arr=[1,21,20,30]. The leaves have the value 1,21,20 and 30. The level above the leaves contains the max(1,21) and max(20,30. So their values is 21 and 30. The root node contains max(21,30), i.e. their max of the left and right child. Thus we data of the segment tree is [30, 30, 21, 30, 1, 21, 20, 30]. Here note that the function is max.

Thus the steps of our approach are:

  • Store both of the arrays A and B in an array arr(for sorting them based on A)
  • Sort them based on A
  • Make an variable iterator which keeps the count of the maximum j such that (i+1,i+2, …j) satisfies 0<=A[j]-A[i]<=k
  • Make a Segment Tree to find out the maximum value of A[index]+B[index] such that the index belongs to (i+1,i+2, …j). 
  • Iterate the array and for each i, find the j such that 0<=A[j]-A[i]<=k.
  • Find the maximum value for the i, the maximum value will be (B[i] - A[i]) + (B[j]+A[j]) = (B[i] - A[i]) + the maximum value from the Segment Tree = (B[i] - A[i]) + s.query(current+1,iterator1). The s.query(current+1,iterator1) gives the maximum value from the segment (current+1, current+2 ,... iterator1-1) from the sumarr.
  • If the value found is greater than the current answer, update the answer.
  • Return the answer.

Pseudo Code

DEFINE CLASS SegmentTree:
    DEFINE FUNCTION __init__(self, data, default=0, func=max):
        """initialize the segment tree with data"""
        SET self._default TO default
        SET self._func TO func
        SET self._len TO len(data)
        SET self._size TO _size TO 1 << (self._len - 1).bit_length()
        SET self.data TO [default] * (2 * _size)
        SET self.data[_size:_size + self._len] TO data
        FOR i IN reversed(range(_size)):
            SET self.data[i] TO func(self.data[i + i], self.data[i + i + 1])

    DEFINE FUNCTION __delitem__(self, idx):
        SET self[idx] TO self._default

    DEFINE FUNCTION __getitem__(self, idx):
        RETURN self.data[idx + self._size]

    DEFINE FUNCTION __setitem__(self, idx, value):
        idx += self._size
        SET self.data[idx] TO value
        idx >>= 1
        WHILE idx:
            SET self.data[idx] TO self._func(self.data[2 * idx], self.data[2 * idx + 1])
            idx >>= 1

    DEFINE FUNCTION __len__(self):
        RETURN self._len

    DEFINE FUNCTION query(self, start, stop):
        """func of data[start, stop)"""
        start += self._size
        stop += self._size

        SET res_left TO res_right TO self._default
        WHILE start < stop:
            IF start & 1:
                SET res_left TO self._func(res_left, self.data[start])
                start += 1
            IF stop & 1:
                stop -= 1
                SET res_right TO self._func(self.data[stop], res_right)
            start >>= 1
            stop >>= 1

        RETURN self._func(res_left, res_right)

    DEFINE FUNCTION __repr__(self):
        RETURN "SegmentTree({0})".format(self.data)
DEFINE FUNCTION maximize(a,b,k):
    n=len(a)
    arr=[[i,j] FOR i,j IN zip(a,b)]
    arr.sort()
    a=[i[0] FOR i IN arr]
    b=[i[1] FOR i IN arr]
    # OUTPUT(arr)
    iterator1=0
    sumarr=[i+j FOR i,j IN zip(a,b)]
    s=SegmentTree(sumarr,float("-inf"),max)
    answer=float("-inf")
    FOR current IN range(n):
        WHILE iterator1<n and a[iterator1]-a[current]<=k:
            # we increase the iterator UNTIL the condition of k is satisfied
            iterator1+=1
        # OUTPUT(current,s)
        # we update the answer with best value .
        # OUTPUT(current,a[current],b[current])
        IF iterator1-current-1>0:
            answer=max(answer,b[current]-a[current]+s.query(current+1,iterator1))
    IF answer==float("-inf"):
        # IF answer was not updated then it was not found thus we RETURN -1
        RETURN -1
    RETURN answer
A=[1,10,20,30]
B=[4,5,3,7]
K=11
ans=maximize(A,B,K)
OUTPUT(ans)

Python3 Implementation

class SegmentTree:
    def __init__(self, data, default=0, func=max):
        """initialize the segment tree with data"""
        self._default = default
        self._func = func
        self._len = len(data)
        self._size = _size = 1 << (self._len - 1).bit_length()
 
        self.data = [default] * (2 * _size)
        self.data[_size:_size + self._len] = data
        for i in reversed(range(_size)):
            self.data[i] = func(self.data[i + i], self.data[i + i + 1])
 
    def __delitem__(self, idx):
        self[idx] = self._default
 
    def __getitem__(self, idx):
        return self.data[idx + self._size]
 
    def __setitem__(self, idx, value):
        idx += self._size
        self.data[idx] = value
        idx >>= 1
        while idx:
            self.data[idx] = self._func(self.data[2 * idx], self.data[2 * idx + 1])
            idx >>= 1
 
    def __len__(self):
        return self._len
 
    def query(self, start, stop):
        """func of data[start, stop)"""
        start += self._size
        stop += self._size
 
        res_left = res_right = self._default
        while start < stop:
            if start & 1:
                res_left = self._func(res_left, self.data[start])
                start += 1
            if stop & 1:
                stop -= 1
                res_right = self._func(self.data[stop], res_right)
            start >>= 1
            stop >>= 1
 
        return self._func(res_left, res_right)
 
    def __repr__(self):
        return "SegmentTree({0})".format(self.data)
def maximize(a,b,k):
    n=len(a)
    arr=[[i,j] for i,j in zip(a,b)]
    arr.sort()
    a=[i[0] for i in arr]
    b=[i[1] for i in arr]
    # print(arr)
    iterator1=0
    sumarr=[i+j for i,j in zip(a,b)]
    s=SegmentTree(sumarr,float("-inf"),max)
    answer=float("-inf")
    for current in range(n):
        while iterator1<n and a[iterator1]-a[current]<=k:
            # we increase the iterator until the condition of k is satisfied
            iterator1+=1
        # print(current,s)
        # we update the answer with best value .
        # print(current,a[current],b[current])
        if iterator1-current-1>0:
            answer=max(answer,b[current]-a[current]+s.query(current+1,iterator1))
    if answer==float("-inf"):
        # if answer was not updated then it was not found thus we return -1
        return -1
    return answer
A=[1,21,20,30]
B=[4,5,19,18]
K=5
ans=maximize(A,B,K)
print(ans) # 25

Input

A=[1,21,20,30]
B=[4,5,19,18]
K=5

Output:

25

Time Complexity

The time complexity for the above program is O(N x log(N)), where N is the Arrays A and B size. 

Making the array arr from A and B takes O(N) time. Then sorting it takes O(N x log(N)) time. 

We are also making a Segment Tree of the arr sumarr. Building the segment tree takes O(N) time. But the query in the Segment Tree takes log(N) time. Since we ask for N queries, the time complexity for all the queries in O(N x log(N)) time.

Space Complexity

The space complexity of the above code is O(N).

We are making an array arr from array A and array B containing 2xN elements. 

Again we make sumarr that contains O(N) elements.

The Segment Tree also contains O(N) elements. As all the arrays contain O(N) elements, the space complexity of the program is O(N).

FAQs

  1. What is the brute force method, and what is its time complexity?
    In the brute force approach we check for every (i,j) pair and find the pair having maximum value B[i]+B[j] + abs(A[i]-A[j]). Thus the time complexity will be O(NxN).
     
  2. What is its space complexity in the naive approach?
    The extra space complexity will be O(1) as we would only have one variable to store the answer. However, the input array contains N elements, so the space complexity is O(N).
     
  3. What is the use of Segment Tree in the code?
    Using a Segment Tree, we can add, delete and find the maximum element in approximate log(N) time where N is the size of the Segment Tree. 
     
  4. What are the other methods in which we could have found the maximum element quickly?
    We could have found the maximum element quickly using a range query. We want to find the maximum element among a continuous segment of array, we could have used a range query.
     
  5. What is a Segment Tree?
    A segment tree is a tree data structure used for storing a piece of particular information about segments or intervals of an array which we can query at any point in time. 

Key Takeaways

In this article, we first learnt how to simplify a given condition. Then we used an application of Segment Tree to find out the maximum value on a segment of an array.

This is one of the widely popular applications of the Segment Tree.

If you want to learn segment trees from scratch, then do check out this amazing tutorial.

Some of the questions based on applications of the segment tree, which you must solve - 

To practice more problems based on the segment tree, you can check out - Segment Tree Archives

Check out Coding Ninjas Studio to learn more about competitive programming, DSA, attempt mock tests, interview preparation and much more.

Happy Learning!!!

Live masterclass