Facebook Pixel

3828. Final Element After Subarray Deletions

MediumBrainteaserArrayMathGame Theory
LeetCode ↗

Problem Description

You are given an integer array nums.

Two players, Alice and Bob, play a game in turns, with Alice playing first.

  • In each turn, the current player chooses any subarray nums[l..r] such that r - l + 1 < m, where m is the current length of the array.
  • The selected subarray is removed, and the remaining elements are concatenated to form the new array.
  • The game continues until only one element remains.

Alice aims to maximize the final element, while Bob aims to minimize it. Assuming both play optimally, return the value of the final remaining element.

Explanation:

Let's understand what each move allows. On any turn, a player must remove a subarray (a contiguous block of elements) whose length is strictly less than the current array length m. This means a player cannot remove the entire array in one move, but they can remove any contiguous portion as long as at least one element is left behind.

The key observation comes from focusing on the first and last elements of the array:

  • Since Alice plays first, she has the power to remove a large contiguous chunk. In particular, Alice can remove every element except the first and last elements in a single move. After such a move, only nums[0] and nums[n - 1] remain. From there, removing one of the two leaves the larger as the final element. This shows the result can be at least max(nums[0], nums[n - 1]).

  • Now consider the middle elements at indices 1, 2, ..., n - 2. Suppose Alice wanted to preserve one of these middle values to be the final answer. Whenever it gets down to a small array containing that middle element, Bob can simply remove a subarray that includes it. Because the element is in the middle, it is never "protected" by being at an end, so Bob can always eliminate it. This shows the result can be at most max(nums[0], nums[n - 1]).

Combining both bounds, the final remaining element is exactly max(nums[0], nums[n - 1]).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Math / Bit Manipulation?

This problem maps to Math / Bit Manipulation through a short path in the full flowchart.

Math orbittricks?yesGametheory?yesMath / BitManipulation

The game-theoretic analysis shows the final element is always max(nums[0], nums[n-1]) regardless of play strategy, a mathematical result.

Open in Flowchart

Intuition

At first glance this looks like a complicated game-theory problem where we might need to simulate every possible move for both Alice and Bob. But before jumping into complex search or dynamic programming, it helps to ask a simpler question: which elements can each player actually control?

Notice the special role of the two endpoints, nums[0] and nums[n - 1]. These elements are different from the rest because they sit at the boundaries of the array. A middle element can always be "surrounded" and swallowed up inside a removed subarray, but the endpoints are harder to get rid of without removing a large piece of the array.

Now think about Alice's advantage: she moves first, and the rule only forbids removing the entire array at once. So on her very first turn, Alice can remove the whole middle section, leaving just nums[0] and nums[n - 1]. With only two elements left, whoever moves next must remove exactly one of them, leaving the other. This means Alice can guarantee a result of at least max(nums[0], nums[n - 1]) — she steers the game straight toward the better of the two endpoints.

Next, think about whether Alice could ever do better than the endpoints by keeping some large middle value. Here Bob steps in. Any middle element is reachable by a subarray removal that doesn't touch both ends, so Bob can always cut out a chunk containing that middle value whenever it's his turn. Because Bob wants to minimize, he will never let a middle element survive to the end. So Alice can never force a middle value to be the answer, meaning the result is at most max(nums[0], nums[n - 1]).

Putting the two ideas together — Alice can guarantee at least max(nums[0], nums[n - 1]), and Bob prevents anything larger — the game must settle exactly at max(nums[0], nums[n - 1]). The seemingly complex game collapses into a single comparison between the two endpoints, no simulation needed.

Pattern Learn more about Math patterns.

Solution Approach

Based on the intuition above, the implementation becomes remarkably simple — we don't need any game simulation, recursion, or dynamic programming. The entire problem reduces to a single comparison.

Algorithm / Pattern used: This is a brain teaser (math observation) problem. The whole "game" is a distraction; once we prove that the answer is always max(nums[0], nums[n - 1]), the coding part is trivial.

Steps:

  1. Identify the first element of the array, nums[0].
  2. Identify the last element of the array, nums[n - 1] (written as nums[-1] in Python).
  3. Return the larger of these two values using max.

Data structures: None are needed beyond the input array itself. We only access two positions by index.

The code directly mirrors these three steps:

class Solution:
    def finalElement(self, nums: List[int]) -> int:
        return max(nums[0], nums[-1])

Why this works:

  • nums[0] and nums[-1] give us constant-time access to the two endpoints.
  • max(...) picks the bigger endpoint, which we proved is exactly the value both players are forced into when playing optimally.

Complexity Analysis:

  • Time complexity: O(1), since we only read two array elements and perform one comparison, regardless of the array's size.
  • Space complexity: O(1), as no extra space proportional to the input is used.

This is a good reminder that some "game" problems hide a simple closed-form answer behind a complicated-sounding setup — recognizing the controlling structure (here, the two endpoints) lets us skip any heavy computation entirely.

Example Walkthrough

Let's trace through a concrete example to see how the closed-form answer plays out in an actual game.

Input: nums = [3, 9, 1, 2, 7], so n = 5 and the current length m = 5.

According to our solution, the answer should be:

max(nums[0], nums[-1]) = max(3, 7) = 7

Now let's verify this by walking through the optimal moves both players would make.

Step 1 — Alice's first turn (she wants to maximize).

The array is [3, 9, 1, 2, 7] with length m = 5. Alice may remove any subarray of length strictly less than 5. The two endpoints are nums[0] = 3 and nums[4] = 7, and the larger one is 7. Alice wants to steer the game toward keeping 7.

She removes the middle chunk [9, 1, 2] (indices 1..3), which has length 3 < 5. This is a legal move.

  • Removed: [9, 1, 2]
  • Remaining after concatenation: [3, 7]

Notice Alice deliberately threw away the large middle value 9. Even though 9 > 7, she cannot preserve it, because a middle element is never protected — Bob would eliminate it on his turn. So Alice's best play is to collapse straight down to the two endpoints.

Step 2 — Bob's turn (he wants to minimize).

The array is now [3, 7] with length m = 2. Bob may remove a subarray of length strictly less than 2, i.e. exactly length 1. He must remove one element.

Bob wants the final value to be as small as possible, so he removes 7 to try to leave 3... but wait — can he? Let's check both of his options:

  • Remove [7] → leaves [3]
  • Remove [3] → leaves [7]

Bob, minimizing, removes 7 and leaves [3].

Hold on — that gives 3, not 7! This reveals why Alice's move in Step 1 must be even more careful. Let's reconsider.

Step 1 (corrected) — Alice secures the larger endpoint directly.

Instead of leaving both endpoints for Bob to choose from, Alice removes everything except the larger endpoint 7. She removes the subarray [3, 9, 1, 2] (indices 0..3), which has length 4 < 5 — a legal move since it's not the whole array.

  • Removed: [3, 9, 1, 2]
  • Remaining: [7]

Only one element remains, so the game ends immediately with the value 7.

Why this is optimal:

  • Alice could reach 7 (the larger endpoint) in a single move, so the result is at least max(3, 7) = 7.
  • Alice can never do better: the only way to beat 7 would be to preserve a middle value like 9, but Bob can always surround and remove any middle element, so the result is at most 7.

Both bounds meet at 7, matching our formula max(nums[0], nums[-1]) = max(3, 7) = **7**.

This walkthrough confirms the key insight: the entire game collapses to a single comparison between the two endpoints, and Alice's first-move power lets her lock in the larger one.

Solution Implementation

1from typing import List
2
3
4class Solution:
5    def finalElement(self, nums: List[int]) -> int:
6        # Return the larger value between the first and last elements of the list
7        first_element = nums[0]
8        last_element = nums[-1]
9        return max(first_element, last_element)
10
1class Solution {
2    /**
3     * Returns the larger value between the first and last elements of the array.
4     *
5     * @param nums the input integer array (assumed to be non-empty)
6     * @return the maximum of the first and last elements
7     */
8    public int finalElement(int[] nums) {
9        // First element of the array
10        int firstElement = nums[0];
11
12        // Last element of the array
13        int lastElement = nums[nums.length - 1];
14
15        // Return whichever of the two is larger
16        return Math.max(firstElement, lastElement);
17    }
18}
19
1class Solution {
2public:
3    // Returns the larger value between the first and last elements of the array.
4    int finalElement(vector<int>& nums) {
5        // Compare the first element with the last element and return the maximum.
6        int first_element = nums.front();
7        int last_element = nums.back();
8        return max(first_element, last_element);
9    }
10};
11
1/**
2 * Returns the larger value between the first and last elements of the array.
3 *
4 * @param nums - The input array of numbers.
5 * @returns The maximum of the first and last elements.
6 */
7function finalElement(nums: number[]): number {
8    // Retrieve the first element of the array (index 0).
9    const firstElement: number = nums.at(0)!;
10
11    // Retrieve the last element of the array (index -1).
12    const lastElement: number = nums.at(-1)!;
13
14    // Return whichever of the two values is greater.
15    return Math.max(firstElement, lastElement);
16}
17

Time and Space Complexity

Time Complexity: O(1)

The function performs a constant number of operations regardless of the input size. It only accesses two elements of the array, nums[0] and nums[-1], and computes their maximum using max(). Indexing into a list and comparing two values are both constant-time operations, so the overall time complexity is O(1).

Space Complexity: O(1)

The function uses only a fixed amount of extra space. No additional data structures that scale with the input size are created; it merely returns the result of comparing two scalar values. Therefore, the space complexity is O(1).

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Overcomplicating the problem with game simulation or dynamic programming

The most common mistake is treating this as a "real" game-theory problem and attempting to simulate every possible move with recursion, memoization, or minimax dynamic programming. Because each turn allows removing any subarray of length less than m, the branching factor is enormous, and a naive simulation will explode combinatorially.

Why it's wrong: Trying to enumerate all subarray removals across alternating turns leads to exponential time complexity (and likely a Time Limit Exceeded or memory error on large inputs), even though the answer collapses to a single comparison.

Solution: Recognize that the game is a distraction. Prove the closed-form result max(nums[0], nums[-1]) first (using the endpoint argument), then implement the trivial O(1) solution instead of any simulation.

# WRONG: exponential minimax simulation (conceptual sketch)
def finalElement(self, nums):
    def play(arr, alice_turn):
        if len(arr) == 1:
            return arr[0]
        results = []
        for l in range(len(arr)):
            for r in range(l, len(arr)):
                if r - l + 1 < len(arr):          # valid move
                    new_arr = arr[:l] + arr[r+1:]
                    results.append(play(new_arr, not alice_turn))
        return max(results) if alice_turn else min(results)  # TLE / blows up
    return play(nums, True)

# RIGHT
def finalElement(self, nums):
    return max(nums[0], nums[-1])

Pitfall 2: Misreading the win condition and using min instead of max

Because Bob is the minimizer and plays second, it's tempting to think the answer should involve min(nums[0], nums[-1]). However, Alice moves first and controls the endgame, so she forces the larger endpoint to survive.

Solution: Keep clear which player acts first. Alice (the maximizer) goes first and dictates the outcome, so the answer uses max, not min.

Pitfall 3: Assuming an interior element can be the answer

Some solutions try to compute the maximum over the entire array (max(nums)), reasoning that Alice would simply preserve the global maximum if it lies in the middle.

Why it's wrong: Any interior element is never "protected" by an endpoint. Bob can always remove a subarray containing that middle value, since it is not pinned to either boundary. Only nums[0] and nums[-1] are defensible.

Solution: Restrict attention to the two endpoints only:

# WRONG: an interior maximum cannot be guaranteed
return max(nums)

# RIGHT: only endpoints are defensible
return max(nums[0], nums[-1])

Pitfall 4: Indexing errors on edge cases

If the array has a single element (n == 1), the game has already ended. Accessing nums[0] and nums[-1] still works because both point to the same element, so max(nums[0], nums[-1]) correctly returns that element. The pitfall is over-engineering a special case for n == 1 or assuming the input could be empty.

Solution: Trust that nums[0] and nums[-1] handle the single-element case naturally; just confirm the constraints guarantee nums is non-empty before indexing.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

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


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More