1695. Maximum Erasure Value


Problem Description

The task is to find the maximum score you can obtain by erasing exactly one subarray from an array of positive integers nums. A subarray consists of consecutive elements from the given array, and a score is calculated by taking the sum of the unique elements within that subarray. We are looking for the subarray with the highest sum where all elements are unique. The challenge is to devise an efficient algorithm to perform this action rather than using brute-force, which could be very slow for large arrays.

Intuition

The intuition behind the solution is to utilize a sliding window approach along with a hash table (or dictionary in Python) to keep track of the most recent index of each number in the array. The sliding window allows us to consider a subarray of unique elements which we can dynamically resize as we traverse the array.

Here's an outline of our approach:

  1. Initialize a dictionary or hash table to store the most recent index where each element has appeared as we iterate through the array.
  2. Keep a cumulative sum array which will help in getting the sum of any subarray in constant time.
  3. Set two pointers, j starting at the beginning of the array and i which we will move forward through the array.
  4. For each element v at index i:
    • Update j to be the maximum of it own current value or the index stored in the hash table for the current element v. This is required to ensure our window does not include duplicate values.
    • Calculate the sum of elements in the current window (subarray without duplicates) using the cumulative sum array and update the answer if this sum is greater than the previously recorded answer.
    • Update the hash table with the current index for the element v.
  5. Return the maximum sum recorded during this process.

The use of the cumulative sum and a hash table allows us to efficiently calculate the sum of each potential unique subarray and track the presence of duplicates respectively. The sliding window dynamically adjusts to skip over elements that would lead to duplication, thus ensuring that at all times, the subarray being considered is unique. The max operations ensure that the window only expands when needed and shrinks appropriately to exclude duplicates.

Learn more about Sliding Window patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?

Solution Approach

The implementation of the solution uses a sliding window approach to consider every possible subarray that contains unique elements. Here is the detailed walk-through:

  1. Data Structures used:

    • A dictionary d with integer keys and integer values, which serves as a hash table to keep track of the last index at which each element has appeared.
    • An array s, which is a cumulative sum array, such that s[i] is the sum of the first i numbers in nums.
  2. Initialization:

    • The dictionary d starts off empty.
    • The cumulative sum array s is initialized with 0 as the first element and the cumulative sums of nums after that.
  3. Sliding Window:

    • Define two pointers: i represents the current position in nums, and j is the start index of the window which ensures all elements to its right, up to i, are unique.
    • An ans variable is initialized to 0 for storing the maximum sum of subarrays encountered.
  4. Iterating through nums:

    • The outer loop goes through each element v at index i (1-indexed for simplicity, due to the initial 0 in s).
    • For each element v, check for duplicates by looking up v in the hash table d. If v is found, update j to be the maximum of its current value or the one stored in d[v], the index after the last occurrence of v.
    • While keeping j as the start of the unique window, compute the sum of the current window by subtracting s[j] from s[i]. If this sum is greater than ans, update ans.
    • Update the hash table d[v] with the new index i to mark the most recent occurrence of v.
  5. Maintaining the Maximum Score:

    • As the loop progresses, the ans variable keeps track of the maximum score seen so far, and it is updated accordingly when a larger sum of a unique subarray is found.
  6. Return the Result:

    • After the loop has finished iterating over all elements in nums, ans holds the maximum score attainable by erasing exactly one subarray of unique elements. It is returned as the final answer.

Through the use of cumulative sums, we are able to tell the sum of the subarray nums[j+1...i] in constant time by a simple subtraction: s[i] - s[j]. Using the hash map d, we can dynamically adjust our unique window's start position j whenever a duplicate discovers that it has appeared previously in the window. The algorithm efficiently computes the maximum sum subarray with no duplicates with a time complexity of O(N), where N is the size of the array nums, since each element is processed exactly once.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?

Example Walkthrough

Let's walk through an example to illustrate the solution approach. Consider the array nums as [3, 2, 1, 2, 3, 3]. We want to find the maximum sum of a subarray with unique elements after erasing exactly one subarray.

  1. Using Data Structures:

    • We initialize an empty dictionary d.
    • We also create a cumulative sum array s=[0, 3, 5, 6, 8, 11, 14], representing the cumulative sum up to each index.
  2. Initializing Variables:

    • The dictionary d is {}.
    • The cumulative sum array s is [0, 3, 5, 6, 8, 11, 14].
  3. Starting with a Sliding Window:

    • Two pointers are set: i starts at 1 and j starts at 0.
    • A variable ans is initialized to hold the maximum sum, starting at 0.
  4. Iteration:

    • At i=1, nums[i] is 3. d does not contain 3, so no change to j. We add 3 to d with the value 1. ans becomes s[1] - s[j] = 3 - 0 = 3.
    • At i=2, nums[i] is 2. No 2 in d, so j stays. Add 2 to d with value 2. ans becomes s[2] - s[j] = 5 - 0 = 5.
    • At i=3, nums[i] is 1. No 1 in d, so j stays. Add 1 to d with value 3. ans becomes s[3] - s[j] = 6 - 0 = 6.
    • At i=4, nums[i] is 2 again, but d does contain 2, so we update j to max(j, d[2] + 1) = max(0, 3) = 3. Update 2 in d with 4. ans is still 6 because s[4] - s[j] = 8 - 6 = 2.
    • At i=5, nums[i] is 3. As 3 is in d, update j to max(j, d[3] + 1) = max(3, 2) = 3. Update 3 in d with 5. ans remains 6.
    • At i=6, nums[i] is 3. As 3 is in d, we update j to max(j, d[3] + 1) = max(3, 6) = 6. Update 3 in d with 6. ans remains 6.
  5. Maintaining Maximum Score:

    • Each iteration considers whether the current unique window sum is greater than ans. The variable ans is kept up to date at all times.
  6. Returning Result:

    • After completing the loop, we have ans = 6, which is the maximum score obtainable by erasing one subarray of unique elements from nums.

By employing this method, we have considered all subarrays with unique elements by dynamically adjusting the beginning of the subarray using the j pointer. Instead of calculating the sum each time, we utilized the cumulative sum array for quick calculations. The dictionary d helped maintain indices of previous occurrences. Thus, we arrived at the maximum score efficiently.

Solution Implementation

1from collections import defaultdict
2from itertools import accumulate
3
4class Solution:
5    def maximumUniqueSubarray(self, nums: List[int]) -> int:
6        # Initialize a dictionary to store the most recent index of each number
7        index_dict = defaultdict(int)
8        # Create a prefix sum array with an initial value of 0 for convenience
9        prefix_sums = list(accumulate(nums, initial=0))
10        # Initialize the maximum subarray sum and the start index for the subarray
11        max_sum = start_index = 0
12      
13        # Iterate over the numbers alongside their indices (starting from 1)
14        for current_index, value in enumerate(nums, 1):
15            # Update the start index to the maximum of the current start_index and the next index
16            # after the previous occurrence of the current value
17            start_index = max(start_index, index_dict[value])
18            # Update the maximum sum if the current subarray sum is greater
19            current_subarray_sum = prefix_sums[current_index] - prefix_sums[start_index]
20            max_sum = max(max_sum, current_subarray_sum)
21            # Update the dictionary with the current index of the value
22            index_dict[value] = current_index
23      
24        # Return the maximum subarray sum containing unique elements
25        return max_sum
26
1class Solution {
2    public int maximumUniqueSubarray(int[] nums) {
3        int[] lastIndex = new int[10001]; // Array to store the last index of each number
4        int length = nums.length; // Length of nums
5        int[] prefixSum = new int[length + 1]; // Array to store prefix sum
6      
7        // Calculate prefix sum array
8        for (int i = 0; i < length; ++i) {
9            prefixSum[i + 1] = prefixSum[i] + nums[i];
10        }
11      
12        int maxSum = 0; // Initialize the maximum sum of a unique subarray
13        int windowStart = 0; // Initialize the start of the current window
14      
15        // Iterate over the nums array
16        for (int i = 1; i <= length; ++i) {
17            int value = nums[i - 1]; // Current value
18            windowStart = Math.max(windowStart, lastIndex[value]); // Update the start of the window to be after the last occurrence of value
19            // Calculate the current sum of unique subarray and compare it with the maximum sum found so far
20            maxSum = Math.max(maxSum, prefixSum[i] - prefixSum[windowStart]);
21            lastIndex[value] = i; // Update the last index of the current value
22        }
23      
24        return maxSum; // Return the maximum sum of a unique subarray
25    }
26}
27
1class Solution {
2public:
3    int maximumUniqueSubarray(vector<int>& nums) {
4        // Create a frequency array initialized to zero for the possible range of values in nums
5        int frequency[10001]{};
6      
7        // Store the size of the nums vector
8        int numsSize = nums.size();
9      
10        // Create an array to store the prefix sum of nums
11        int prefixSum[numsSize + 1];
12        prefixSum[0] = 0; // Initialize the first element as 0 for correct prefix sum calculation
13      
14        // Populate the prefixSum array
15        for (int i = 0; i < numsSize; ++i) {
16            prefixSum[i + 1] = prefixSum[i] + nums[i];
17        }
18      
19        // Initialize the maximum sum of unique elements
20        int maxSum = 0;
21      
22        // Initialize the start index of the current subarray
23        int startIndex = 0;
24      
25        // Iterate through the nums array to find the max sum of a unique-subarray
26        for (int i = 1; i <= numsSize; ++i) {
27            int currentValue = nums[i - 1];
28            // Update the startIndex to be the maximum of the current startIndex or the
29            // last index where currentValue was found (to ensure uniqueness in subarray)
30            startIndex = max(startIndex, frequency[currentValue]);
31            // Calculate the maxSum by considering the current unique subarray sum
32            maxSum = max(maxSum, prefixSum[i] - prefixSum[startIndex]);
33            // Update the index in frequency array to the current position for currentValue
34            frequency[currentValue] = i;
35        }
36      
37        // Return the maximum sum of a unique element subarray
38        return maxSum;
39    }
40};
41
1// Define the array of frequencies with a default value of zero for a range of possible values in nums array
2let frequency: number[] = new Array(10001).fill(0);
3
4// Function to calculate the maximum sum of unique-elements subarray
5function maximumUniqueSubarray(nums: number[]): number {
6    // Store the size of the nums array
7    const numsSize: number = nums.length;
8  
9    // Create an array to store the prefix sum of nums
10    let prefixSum: number[] = new Array(numsSize + 1);
11    prefixSum[0] = 0; // Initialize the first element as 0 for correct prefix sum calculation
12  
13    // Populate the prefixSum array
14    for (let i = 0; i < numsSize; ++i) {
15        prefixSum[i + 1] = prefixSum[i] + nums[i];
16    }
17  
18    // Initialize the maximum sum of unique elements
19    let maxSum: number = 0;
20  
21    // Initialize the start index of the current subarray
22    let startIndex: number = 0;
23  
24    // Iterate through the nums array to find the max sum of a unique-element subarray
25    for (let i = 1; i <= numsSize; ++i) {
26        let currentValue = nums[i - 1];
27      
28        // Update the startIndex to be the maximum of the current startIndex or the
29        // last index where currentValue was found (to maintain uniqueness in subarray)
30        startIndex = Math.max(startIndex, frequency[currentValue]);
31      
32        // Calculate the maxSum by considering the current unique subarray sum
33        maxSum = Math.max(maxSum, prefixSum[i] - prefixSum[startIndex]);
34      
35        // Update the index in frequency array to the current position for currentValue
36        frequency[currentValue] = i;
37    }
38  
39    // Return the maximum sum of a unique element subarray
40    return maxSum;
41}
42
Not Sure What to Study? Take the 2-min Quiz:

Which one best describes the time complexity of the following code?

1int factorial(int n) {
2  if (n < 0) {
3    return -1;
4  } else if (n == 0) {
5    return 1;
6  } else {
7    return n * factorial(n - 1);
8  }
9}

Time and Space Complexity

Time Complexity

The time complexity of the code is O(n), where n is the length of the nums array. This is because the code involves a single loop that iterates over nums, and within this loop, the operations performed (accessing the dictionary d, updating the sliding window's sum using the prefix sum s, and calculating the maximum ans) are all constant time operations.

Space Complexity

The space complexity of the code is also O(n) due to the following reasons:

  1. The d dictionary stores each unique value encountered in the nums array with its latest index position. In the worst case, if all elements are unique, the dictionary could hold up to n key-value pairs.

  2. The s list is a prefix sum array which contains a cumulative sum of the nums array, and it has n + 1 elements (including the initial 0). Thus, it also consumes O(n) space.

Combining the above, the total space complexity remains O(n).

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫