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:
- Initialize a dictionary or hash table to store the most recent index where each element has appeared as we iterate through the array.
- Keep a cumulative sum array which will help in getting the sum of any subarray in constant time.
- Set two pointers,
j
starting at the beginning of the array andi
which we will move forward through the array. - For each element
v
at indexi
:- Update
j
to be the maximum of it own current value or the index stored in the hash table for the current elementv
. 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
.
- Update
- 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.
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:
-
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 thats[i]
is the sum of the firsti
numbers innums
.
- A dictionary
-
Initialization:
- The dictionary
d
starts off empty. - The cumulative sum array
s
is initialized with0
as the first element and the cumulative sums ofnums
after that.
- The dictionary
-
- Define two pointers:
i
represents the current position innums
, andj
is the start index of the window which ensures all elements to its right, up toi
, are unique. - An
ans
variable is initialized to0
for storing the maximum sum of subarrays encountered.
- Define two pointers:
-
Iterating through
nums
:- The outer loop goes through each element
v
at indexi
(1-indexed for simplicity, due to the initial0
ins
). - For each element
v
, check for duplicates by looking upv
in the hash tabled
. Ifv
is found, updatej
to be the maximum of its current value or the one stored ind[v]
, the index after the last occurrence ofv
. - While keeping
j
as the start of the unique window, compute the sum of the current window by subtractings[j]
froms[i]
. If this sum is greater thanans
, updateans
. - Update the hash table
d[v]
with the new indexi
to mark the most recent occurrence ofv
.
- The outer loop goes through each element
-
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.
- As the loop progresses, the
-
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.
- After the loop has finished iterating over all elements in
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.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample 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.
-
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.
- We initialize an empty dictionary
-
Initializing Variables:
- The dictionary
d
is{}
. - The cumulative sum array
s
is[0, 3, 5, 6, 8, 11, 14]
.
- The dictionary
-
Starting with a Sliding Window:
- Two pointers are set:
i
starts at 1 andj
starts at 0. - A variable
ans
is initialized to hold the maximum sum, starting at0
.
- Two pointers are set:
-
Iteration:
- At
i=1
,nums[i]
is3
.d
does not contain3
, so no change toj
. We add3
tod
with the value1
.ans
becomess[1] - s[j] = 3 - 0 = 3
. - At
i=2
,nums[i]
is2
. No2
ind
, soj
stays. Add2
tod
with value2
.ans
becomess[2] - s[j] = 5 - 0 = 5
. - At
i=3
,nums[i]
is1
. No1
ind
, soj
stays. Add1
tod
with value3
.ans
becomess[3] - s[j] = 6 - 0 = 6
. - At
i=4
,nums[i]
is2
again, butd
does contain2
, so we updatej
tomax(j, d[2] + 1) = max(0, 3) = 3
. Update2
ind
with4
.ans
is still6
becauses[4] - s[j] = 8 - 6 = 2
. - At
i=5
,nums[i]
is3
. As3
is ind
, updatej
tomax(j, d[3] + 1) = max(3, 2) = 3
. Update3
ind
with5
.ans
remains6
. - At
i=6
,nums[i]
is3
. As3
is ind
, we updatej
tomax(j, d[3] + 1) = max(3, 6) = 6
. Update3
ind
with6
.ans
remains6
.
- At
-
Maintaining Maximum Score:
- Each iteration considers whether the current unique window sum is greater than
ans
. The variableans
is kept up to date at all times.
- Each iteration considers whether the current unique window sum is greater than
-
Returning Result:
- After completing the loop, we have
ans = 6
, which is the maximum score obtainable by erasing one subarray of unique elements fromnums
.
- After completing the loop, we have
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
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:
-
The
d
dictionary stores each unique value encountered in thenums
array with its latest index position. In the worst case, if all elements are unique, the dictionary could hold up ton
key-value pairs. -
The
s
list is a prefix sum array which contains a cumulative sum of thenums
array, and it hasn + 1
elements (including the initial 0). Thus, it also consumesO(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.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
https algomonster s3 us east 2 amazonaws com cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!