Facebook Pixel

2833. Furthest Point From Origin

EasyStringCounting
Leetcode Link

Problem Description

You start at position 0 on a number line. You're given a string moves of length n that contains only the characters 'L', 'R', and '_'.

Each character in the string represents a move you can make:

  • 'L' means you must move left (position decreases by 1)
  • 'R' means you must move right (position increases by 1)
  • '_' means you have a choice - you can move either left OR right

You need to process all n moves in order. Your goal is to find the maximum possible distance from the origin (position 0) that you can achieve after making all moves.

For example, if moves = "L_R__":

  • First move: 'L' forces you to go left to position -1
  • Second move: '_' gives you a choice (you could go to -2 or 0)
  • Third move: 'R' forces you to move right from wherever you are
  • Fourth and fifth moves: '_' characters give you more choices

The solution leverages a key insight: to maximize distance from origin, you should move all flexible '_' moves in the same direction as the majority of forced moves. The final distance is |count('L') - count('R')| + count('_'), which represents moving all '_' characters to amplify the existing imbalance between left and right moves.

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

Intuition

To maximize our distance from the origin, we need to think about what happens with the forced moves ('L' and 'R') versus the flexible moves ('_').

The forced moves create a "baseline" position. If we have 3 'L' moves and 1 'R' move, we're forced to end up at least 2 units away from the origin in the left direction, regardless of what we do with the '_' moves.

Now here's the key insight: the '_' moves give us flexibility, and to maximize distance, we should use ALL of them to push us further in the same direction we're already going.

Think of it like this:

  • Count all the 'L' moves - these push us left
  • Count all the 'R' moves - these push us right
  • The difference |count('L') - count('R')| tells us our "forced" distance from origin

For the '_' moves, we have a choice. To get as far as possible from the origin, we should treat all '_' as moves in the dominant direction. If we have more 'L' than 'R', treat all '_' as 'L'. If we have more 'R' than 'L', treat all '_' as 'R'.

This gives us the formula: |count('L') - count('R')| + count('_')

The absolute value handles both cases (whether left or right dominates), and adding all the '_' counts ensures we use every flexible move to push further in that dominant direction.

Solution Approach

The implementation follows a greedy strategy that can be solved in a single pass through the string.

Step 1: Count the occurrences of each character

We need to count three things:

  • Number of 'L' moves: moves.count("L")
  • Number of 'R' moves: moves.count("R")
  • Number of '_' moves: moves.count("_")

Step 2: Calculate the forced displacement

The forced moves ('L' and 'R') determine our base position:

  • Each 'L' moves us -1 from current position
  • Each 'R' moves us +1 from current position
  • The net displacement is count('R') - count('L') (or count('L') - count('R') depending on perspective)

Since we want the distance (not the position), we take the absolute value: |count('L') - count('R')|

Step 3: Add the flexible moves optimally

All '_' characters should be used to move in the same direction as our net displacement to maximize distance:

  • If we have more 'L' moves, treat all '_' as 'L'
  • If we have more 'R' moves, treat all '_' as 'R'
  • If 'L' and 'R' are equal, all '_' moves go in one direction (either works)

Final Formula:

return abs(moves.count("L") - moves.count("R")) + moves.count("_")

This greedy approach guarantees the maximum distance because:

  1. The forced moves create a minimum distance we must travel
  2. Using all flexible moves in the same direction as the net forced movement maximizes our final distance from origin

The time complexity is O(n) where n is the length of the string (we traverse it to count characters), and space complexity is O(1) as we only store the counts.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the example moves = "L_R__" step by step to illustrate the solution approach.

Step 1: Count each character type

  • Count of 'L': 1
  • Count of 'R': 1
  • Count of '_': 3

Step 2: Calculate forced displacement

  • The forced moves are 1 'L' and 1 'R'
  • Net forced displacement: |1 - 1| = 0
  • This means the forced moves cancel each other out

Step 3: Add flexible moves optimally

  • We have 3 '_' moves that we can use freely
  • Since the forced moves result in position 0, we can choose to move all '_' characters in the same direction
  • Moving all 3 '_' moves to the right: position becomes +3
  • Or moving all 3 '_' moves to the left: position becomes -3
  • Either way, the distance from origin is 3

Final calculation using our formula:

Maximum distance = |count('L') - count('R')| + count('_')
                 = |1 - 1| + 3
                 = 0 + 3
                 = 3

Verification by tracing one optimal path:

  • Start at position 0
  • Move 1: 'L' → must go left → position = -1
  • Move 2: '_' → choose left → position = -2
  • Move 3: 'R' → must go right → position = -1
  • Move 4: '_' → choose left → position = -2
  • Move 5: '_' → choose left → position = -3

Final distance from origin: |-3| = 3

The formula correctly predicts that by using all flexible moves in one direction, we achieve the maximum possible distance of 3 from the origin.

Solution Implementation

1class Solution:
2    def furthestDistanceFromOrigin(self, moves: str) -> int:
3        """
4        Calculate the furthest possible distance from the origin after executing all moves.
5      
6        Args:
7            moves: A string containing 'L' (left), 'R' (right), and '_' (wildcard) moves
8      
9        Returns:
10            The maximum distance from origin that can be achieved
11        """
12        # Count the number of left moves
13        left_count = moves.count("L")
14      
15        # Count the number of right moves
16        right_count = moves.count("R")
17      
18        # Count the number of wildcard moves (can be either left or right)
19        wildcard_count = moves.count("_")
20      
21        # The furthest distance is achieved by:
22        # 1. Finding the absolute difference between fixed left and right moves
23        # 2. Adding all wildcards in the direction that increases distance
24        furthest_distance = abs(left_count - right_count) + wildcard_count
25      
26        return furthest_distance
27
1class Solution {
2    /**
3     * Calculates the furthest distance from the origin after executing all moves.
4     * 'L' moves left (-1), 'R' moves right (+1), '_' can be either left or right.
5     * To maximize distance, all '_' moves should go in the same direction as the majority move.
6     * 
7     * @param moves String containing 'L', 'R', and '_' characters representing moves
8     * @return The maximum possible distance from the origin
9     */
10    public int furthestDistanceFromOrigin(String moves) {
11        // Calculate the net distance from L and R moves
12        int leftCount = countCharacter(moves, 'L');
13        int rightCount = countCharacter(moves, 'R');
14        int wildcardCount = countCharacter(moves, '_');
15      
16        // The furthest distance is achieved by adding all wildcards to the majority direction
17        // Math.abs(leftCount - rightCount) gives the net displacement from L and R moves
18        // Adding wildcardCount maximizes the distance in the dominant direction
19        return Math.abs(leftCount - rightCount) + wildcardCount;
20    }
21
22    /**
23     * Counts the occurrences of a specific character in a string.
24     * 
25     * @param str The input string to search
26     * @param targetChar The character to count
27     * @return The number of occurrences of targetChar in str
28     */
29    private int countCharacter(String str, char targetChar) {
30        int count = 0;
31      
32        // Iterate through each character in the string
33        for (int i = 0; i < str.length(); i++) {
34            if (str.charAt(i) == targetChar) {
35                count++;
36            }
37        }
38      
39        return count;
40    }
41}
42
1class Solution {
2public:
3    int furthestDistanceFromOrigin(string moves) {
4        // Count occurrences of left moves
5        int leftCount = 0;
6        // Count occurrences of right moves
7        int rightCount = 0;
8        // Count occurrences of wildcards (can be either left or right)
9        int wildcardCount = 0;
10      
11        // Iterate through each character in the moves string
12        for (char move : moves) {
13            if (move == 'L') {
14                leftCount++;
15            } else if (move == 'R') {
16                rightCount++;
17            } else if (move == '_') {
18                wildcardCount++;
19            }
20        }
21      
22        // The maximum distance is achieved by:
23        // 1. Taking the absolute difference between left and right moves
24        // 2. Adding all wildcards to the direction that increases distance
25        return abs(leftCount - rightCount) + wildcardCount;
26    }
27};
28
1/**
2 * Calculates the furthest distance from origin after executing moves
3 * @param moves - String containing 'L' (left), 'R' (right), and '_' (wildcard) moves
4 * @returns The maximum possible distance from origin
5 */
6function furthestDistanceFromOrigin(moves: string): number {
7    // Helper function to count occurrences of a specific character in the moves string
8    const countCharacter = (character: string): number => {
9        return moves.split('').filter((move: string) => move === character).length;
10    };
11  
12    // Count the number of left moves
13    const leftCount: number = countCharacter('L');
14  
15    // Count the number of right moves
16    const rightCount: number = countCharacter('R');
17  
18    // Count the number of wildcard moves (can be either left or right)
19    const wildcardCount: number = countCharacter('_');
20  
21    // The furthest distance is achieved by using all wildcards in the same direction
22    // as the majority move direction, resulting in |L - R| + wildcards
23    return Math.abs(leftCount - rightCount) + wildcardCount;
24}
25

Time and Space Complexity

The time complexity is O(n), where n is the length of the string moves. This is because the count() method needs to traverse through the entire string to count occurrences of each character ('L', 'R', and '_'). Since we call count() three times, the total time is 3 * O(n) = O(n).

The space complexity is O(1). The algorithm only uses a constant amount of extra space to store the count values and compute the result, regardless of the input size. No additional data structures that scale with the input are created.

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

Common Pitfalls

Pitfall 1: Misunderstanding the Order of Moves

The Mistake: A common error is thinking that you can rearrange the moves or group all '_' characters together. Some might incorrectly assume they can execute all 'L' moves first, then all 'R' moves, then decide on '_' moves.

Why It Happens: The problem states you must process moves "in order," but when we see the mathematical solution |count('L') - count('R')| + count('_'), it might seem like order doesn't matter since we're just counting.

The Reality: While the final formula doesn't depend on order, this is only true because of the specific nature of this problem. Each move changes your position by exactly 1, and we're looking for maximum distance (not a specific final position). The order independence is a mathematical property of this specific problem, not a general rule.

Example to Clarify: For moves = "L_R__", you cannot do:

  • First do the 'L' (go to -1)
  • Then do all three '_' moves (go to -4)
  • Then do the 'R' (go to -3)

You must follow the exact sequence: L, then _, then R, then _, then _.

Pitfall 2: Trying to Track Position During Execution

The Mistake: Attempting to simulate the actual movement step by step, maintaining current position and trying different paths for each '_' character.

# Incorrect overcomplicated approach
def furthestDistanceFromOrigin(moves: str) -> int:
    positions = [0]
    for move in moves:
        new_positions = []
        for pos in positions:
            if move == 'L':
                new_positions.append(pos - 1)
            elif move == 'R':
                new_positions.append(pos + 1)
            else:  # move == '_'
                new_positions.append(pos - 1)
                new_positions.append(pos + 1)
        positions = new_positions
    return max(abs(pos) for pos in positions)

Why It's Wrong: This approach has exponential time complexity O(2^w) where w is the number of wildcards, making it inefficient for large inputs.

The Solution: Recognize that to maximize distance, all wildcards should move in the same direction. The counting approach elegantly captures this without simulation.

Pitfall 3: Confusing Distance with Position

The Mistake: Returning the final position instead of the distance from origin, forgetting to take the absolute value.

# Incorrect - returns position, not distance
def furthestDistanceFromOrigin(moves: str) -> int:
    return moves.count("R") - moves.count("L") + moves.count("_")

The Fix: Always remember that distance from origin means the absolute value of the position:

# Correct
def furthestDistanceFromOrigin(moves: str) -> int:
    return abs(moves.count("L") - moves.count("R")) + moves.count("_")

Pitfall 4: Overthinking the Wildcard Strategy

The Mistake: Trying to determine whether wildcards should go left or right based on complex conditions, or splitting wildcards between directions.

The Insight: All wildcards should always move in the same direction to maximize distance. If there are more 'L' moves than 'R' moves (or vice versa), add wildcards to amplify that difference. If 'L' and 'R' are balanced, all wildcards go in one direction (either works).

The beauty of the formula abs(L - R) + wildcards is that it automatically handles all these cases without conditional logic.

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

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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

Load More