Freedom Trail

Updated on 30 May, 2025
Freedom Trail header image

Problem Statement

In the renowned video game Fallout 4, one challenge—"Road to Freedom"—immerses players into a scenario where they need to interact with a mechanism known as the "Freedom Trail Ring" to unlock a door. This mechanism simulation is presented with two primary data representations:

  • A string ring which encaptures the sequence of characters engraved on a rotatable dial.
  • A string key represents the sequence of characters (a "password") that needs to be sequentially aligned to a fixed point (the "12:00" direction) on this dial to progress in the challenge.

The task is to compute the minimum number of steps required to align each character from key in the target direction using the dial. A "step" can either be a rotation of the dial (either clockwise or anticlockwise) or the act of selecting the aligned character (by pressing a center button). The challenge initiates with the first character of the ring aligning at the "12:00" direction, serving as a starting point for the character alignment of key.

This computational problem requires optimizing the number and direction of rotations to minimize the overall steps needed to complete the alignment of all characters in key.

Examples

Example 1

Input:

ring = "godding"
key = "gd"

Output:

4

Explanation:

For the first key character 'g', since it is already in place, we just need 1 step to spell this character.
For the second key character 'd', we need to rotate the ring anticlockwise by 2 steps to bring 'd' to the 12:00 position.
Also, we need 1 more step to select the character.
Total steps = 1 + 2 + 1 = 4.

Example 2

Input:

ring = "godding"
key = "godding"

Output:

13

Constraints

  • 1 <= ring.length, key.length <= 100
  • ring and key consist of only lowercase English letters.
  • It is guaranteed that key can always be spelled by rotating ring.

Approach and Intuition

The problem can be resolved through a dynamic programming approach where the focus is on:

  1. Representing each position of the dial as a state. These states change based on the rotations performed.

  2. Using a memoization table to track the minimal steps required to align characters from key, starting at any index in ring.

  3. For each character in key, consider:

    • All positions in ring that contain the character.
    • The steps to rotate from the current position to the target position (clockwise and anticlockwise).
    • Adding 1 step for selecting the character once it’s aligned.
  4. Update the DP table to store the minimal steps required to reach each new state.

  5. After processing all characters, return the minimum steps from the final DP state.

This approach leverages the circular nature of the dial and ensures all possibilities are considered efficiently using dynamic programming.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
public:
    int rotateSteps(string ring, string key) {
        int lenRing = ring.size();
        int lenKey = key.size();

        // Map to store positions of each character in the ring
        unordered_map<char, vector<int>> indexMap;
        for (int i = 0; i < ring.size(); i++) {
            char c = ring[i];
            indexMap[c].push_back(i);
        }

        // Min-heap to explore minimum steps first
        priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> minHeap;
        minHeap.push({0, 0, 0});

        // Set to track the visited {ring position, key index}
        unordered_set<string> visited;

        // Processing the heap to find the minimum rotation steps
        int minSteps = 0;
        while (!minHeap.empty()) {
            vector<int> current = minHeap.top();
            minHeap.pop();
            minSteps = current[0];
            int currentIndex = current[1];
            int currentKeyIndex = current[2];

            // If we have spelled the entire key
            if (currentKeyIndex == lenKey) {
                break;
            }

            string visitKey = to_string(currentIndex) + "-" + to_string(currentKeyIndex);
            if (visited.find(visitKey) != visited.end()) {
                continue;
            }

            // Marking the state as visited
            visited.insert(visitKey);

            // Traverse the visible states and push them to the heap
            for (int newIndex : indexMap[key[currentKeyIndex]]) {
                minHeap.push({minSteps + getMinSteps(currentIndex, newIndex, lenRing),
                              newIndex, currentKeyIndex + 1});
            }
        }

        return minSteps + lenKey;
    }

private:
    // Helper function to calculate minimal rotation steps between two indices
    int getMinSteps(int start, int dest, int length) {
        int direct = abs(start - dest);
        int reverse = length - direct;
        return min(direct, reverse);
    }
};

This C++ solution for the problem titled "Freedom Trail" is designed to find the minimum number of steps needed to spell a given "key" by rotating a circular "ring." To achieve this, it employs a breadth-first search (BFS) approach using a minimum heap (priority queue) to systematically explore the minimum steps first.

Key Components of the Solution:

  • Data Structures Used:

    • unordered_map to store positions of each character in the 'ring'.
    • priority_queue (min-heap) to explore states by minimum accumulated steps.
    • unordered_set to track visited states to avoid redundant processing.
  • Algorithm Steps:

    • Initialize the indexMap with positions of each character in the 'ring'.
    • Start BFS with the initial state in the minHeap, representing the starting point of the ring and the key with zero steps taken.
    • Within the BFS loop, check and track the visited status of states using a unique key formatted as currentIndex-currentKeyIndex.
    • For each valid transition, calculate the possible steps using the getMinSteps() function which determines the minimal rotation steps between two indices on the ring.
    • Continue exploring until the entire 'key' is processed, and derive the final step count.

Helper Function:

  • getMinSteps(int start, int dest, int length): Calculates the minimum steps required to move from a start index to a destination index in a circular array, accounting for both direct and reverse paths.

The solution ensures efficiency by:

  • Leveraging a min-heap to always process the least costly step path at any given point.
  • Avoiding revisiting already processed states with the help of an unordered_set.

This algorithm effectively finds the optimal rotation steps required for the ring to perfectly align with each subsequent character of the 'key', thereby spelling out the entire sequence in minimal steps.

java
class Solution {
    public int calculateSteps(String dial, String target) {
        int dialSize = dial.length();
        int targetLength = target.length();

        // Map to hold character positions in the dial
        Map<Character, List<Integer>> characterLocations = new HashMap<>();
        for (int i = 0; i < dialSize; i++) {
            char charAtPos = dial.charAt(i);
            characterLocations.computeIfAbsent(charAtPos, x -> new ArrayList<>()).add(i);
        }

        // Priority queue to manage states during exploration
        PriorityQueue<int[]> statesQueue = new PriorityQueue<>(Comparator.comparingInt(s -> s[0]));
        statesQueue.add(new int[] {0, 0, 0});

        // Set to track visited states
        Set<Pair<Integer, Integer>> visited = new HashSet<>();

        int minSteps = 0;
        while (!statesQueue.isEmpty()) {
            int[] currentState = statesQueue.poll();
            minSteps = currentState[0];
            int currentDialIndex = currentState[1];
            int targetIndex = currentState[2];

            if (targetIndex == targetLength) {
                break;
            }

            Pair<Integer, Integer> stateKey = new Pair<>(currentDialIndex, targetIndex);
            if (visited.contains(stateKey)) {
                continue;
            }

            visited.add(stateKey);
            
            char targetChar = target.charAt(targetIndex);
            for (int newIndex : characterLocations.get(targetChar)) {
                int steps = minSteps + getDistance(currentDialIndex, newIndex, dialSize);
                statesQueue.offer(new int[] {steps, newIndex, targetIndex + 1});
            }
        }

        return minSteps + targetLength; 
    }

    private int getDistance(int startIndex, int endIndex, int totalLength) {
        int directDistance = Math.abs(startIndex - endIndex);
        return Math.min(directDistance, totalLength - directDistance);
    }
}

In the provided Java solution for the "Freedom Trail" problem, a method calculateSteps is used to determine the minimum number of steps required to spell out a target word using a rotary dial. Here's a step-by-step breakdown of how the solution is implemented:

  1. Initialize variables for the sizes of the dial and target strings.
  2. Use a HashMap to store the locations of characters in the dial. Each character in the dial may appear in multiple indices, so the value for each character key is a list of integers.
  3. Utilize a PriorityQueue to facilitate a state-based exploration. Each state, represented as an array of three integers, includes cumulative steps, the current index on the dial, and the current index in the target string. The queue orders elements based on the number of steps.
  4. Utilize a HashSet to keep track of visited states and prevent revisiting them. Each state in the set is represented by a pair of current dial index and target index.
  5. In the main loop, continue processing until the priority queue is empty:
    • Dequeue the state with the minimum steps.
    • Check if the target has been fully spelled out; if so, break out of the loop.
    • If the current state has not been visited, mark it as visited.
    • For each position of the current target character on the dial, calculate the step count to reach that position from the current dial index. Enqueue new states for subsequent exploration.

Efficiently compute the minimum steps between two indices on the dial, considering the rotational nature of the dial, reducing any circular travel by comparing direct and wrap-around distances.

The final number of steps returned factors in both movement steps and the characters of the target word itself, effectively providing a solution for minimum rotations and character selections. This approach leverages a breadth-first search strategy with optimizations for the cyclic structure of the dial and early termination conditions for explored sequences.

python
class Solution:
    def minimumRotateSteps(self, dial_str: str, target_str: str) -> int:
        dial_length = len(dial_str)
        target_length = len(target_str)

        # Helper function to calculate minimum step difference
        def calc_steps(start, end):
            diff = abs(start - end)
            alt_diff = dial_length - diff
            return min(diff, alt_diff)

        # Store indices where each character appears in the dial
        char_locations = collections.defaultdict(list)
        for idx, c in enumerate(dial_str):
            char_locations[c].append(idx)

        # Min-heap priority queue initialization with tuple (total_moves, current_position, target_char_index)
        priority_queue = [(0, 0, 0)]
        visited_states = set()

        # Process to perform dialing to reach the target sequence
        while priority_queue:
            current_steps, current_index, target_index = heapq.heappop(priority_queue)

            # Check if complete target sequence is matched
            if target_index == target_length:
                break

            if (current_index, target_index) in visited_states:
                continue

            # Mark the current state as visited
            visited_states.add((current_index, target_index))

            # Push new states into the heap
            for new_position in char_locations[target_str[target_index]]:
                new_steps = current_steps + calc_steps(current_index, new_position) + 1
                heapq.heappush(priority_queue, (new_steps, new_position, target_index + 1))

        # Return the total number of moves made including key presses
        return current_steps + target_length

The code provided solves the problem of finding the minimum number of steps required to spell a target string on a rotary dial (like an old telephone dial). This Python function works as follows:

  • Initialization and Helper Function:

    • Get the lengths of the dial string and the target string.
    • Define a helper function calc_steps() to calculate the minimum number of steps between two positions on the dial (taking into account dial wrapping).
  • Tracking Character Locations:

    • Create a dictionary to store the indices of each character in the dial string for quick lookup.
  • Priority Queue for Efficient Search:

    • Employ a min-heap priority queue to process positions efficiently based on the cost (number of moves taken). The queue stores tuples containing the total number of moves so far, the current position on the dial, and the index of the next target character.
    • Use a set to keep track of visited states to avoid redundant processing.
  • Dialing Process:

    • Extract the next position from the priority queue. If the target sequence is fully matched or the state has already been visited, skip to the next iteration.
    • For each possible position of the next character in the target sequence, calculate the new total steps using the helper function and add this new state to the priority queue.
  • Final Output:

    • Return the number of total moves needed, accounting for moving the dial and pressing the dial to confirm each character selection in the target.

This solution leverages a combination of heap-based priority queue management and dynamic programming principles (storing visited states) to ensure optimal time complexity while navigating through the dial configurations to achieve the target word formation. This approach avoids unnecessary recalculations and ensures that the shortest path (in terms of dial rotations and character presses) is found efficiently.

Comments

No comments yet.