Smallest Common Region

Updated on 24 June, 2025
Smallest Common Region header image

Problem Statement

In this task, you are provided with a hierarchical structure of geographic regions represented as lists, where each list embodies a hierarchy of containment. The first region in each list is considered to encompass all other regions listed after it. It’s similar to parent-child relationships where the parent includes the children, and adjacent siblings don't overlap in coverage.

The challenge is to find the smallest or most immediate common region that encompasses two given regions, referred to here as region1 and region2. Each region within the nested lists implicitly or explicitly contains itself. For example, given specific region relationships modeled in the lists, and two regions for inquiry, you should determine the most immediate 'parent' region that is an ancestor to both. There should not be any scenario within the input data where one region is contained by multiple other regions at the same hierarchical level.

The problem also comes with a set of assurances:

  • The structure is well-formed such that each child has only one direct parent.
  • The smallest region that can contain both region1 and region2 is guaranteed to exist.

Examples

Example 1

Input:

regions = [["Earth","North America","South America"],
           ["North America","United States","Canada"],
           ["United States","New York","Boston"],
           ["Canada","Ontario","Quebec"],
           ["South America","Brazil"]],
region1 = "Quebec",
region2 = "New York"

Output:

"North America"

Example 2

Input:

regions = [["Earth", "North America", "South America"],
           ["North America", "United States", "Canada"],
           ["United States", "New York", "Boston"],
           ["Canada", "Ontario", "Quebec"],
           ["South America", "Brazil"]],
region1 = "Canada",
region2 = "South America"

Output:

"Earth"

Constraints

  • 2 <= regions.length <= 10^4
  • 2 <= regions[i].length <= 20
  • 1 <= regions[i][j].length, region1.length, region2.length <= 20
  • region1 != region2
  • regions[i][j], region1, and region2 consist of English letters.
  • The input is generated such that there exists a region which contains all the other regions, either directly or indirectly.

Approach and Intuition

The problem essentially boils down to finding the lowest common ancestor in a tree that is not explicitly defined but inferred from lists. Here's a step-by-step strategy to approach this:

  1. Build Parent Map:

    • Create a dictionary parent_map where each child region points to its immediate parent.
    • Iterate through each list in regions, assigning the first element as the parent of all subsequent ones.
  2. Trace Ancestors of region1:

    • Starting from region1, move up the hierarchy using the parent_map.
    • Store each ancestor in a set ancestors1.
  3. Find First Common Ancestor:

    • Starting from region2, trace its path to the root.
    • The first region found that also exists in ancestors1 is the lowest common region.
  4. Guarantees Ensure Validity:

    • The problem guarantees a valid containment tree with one root and no cycles, so this approach will always yield a result.

This strategy ensures optimal performance and correctness, utilizing the tree's unique properties and a simple traversal mechanism to identify the common ancestor.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
    vector<string> getPathFromRoot(
        string currentNode, unordered_map<string, string>& parentMapping) {
        vector<string> pathList;
        pathList.push_back(currentNode);

        while (parentMapping.find(currentNode) != parentMapping.end()) {
            string parent = parentMapping[currentNode];
            pathList.push_back(parent);
            currentNode = parent;
        }
        reverse(pathList.begin(), pathList.end());
        return pathList;
    }

public:
    string locateSmallestCommonRegion(vector<vector<string>>& areas, string area1,
                                       string area2) {
        unordered_map<string, string> regionHierarchy;
        for (auto& region : areas) {
            string parent = region[0];
            for (int idx = 1; idx < region.size(); ++idx) {
                regionHierarchy[region[idx]] = parent;
            }
        }

        auto pathForArea1 = getPathFromRoot(area1, regionHierarchy);
        auto pathForArea2 = getPathFromRoot(area2, regionHierarchy);

        int i = 0, j = 0;
        string commonAncestor;
        while (i < pathForArea1.size() && j < pathForArea2.size() && pathForArea1[i] == pathForArea2[j]) {
            commonAncestor = pathForArea1[i];
            i++;
            j++;
        }

        return commonAncestor;
    }
};

The given C++ code defines a solution for identifying the smallest common region from a hierarchical structure of geographical areas. This code follows these main steps:

  1. It creates a function getPathFromRoot that, given an area and a mapping of child to parent relationships, constructs a path from the area up to the root of the hierarchy. This is achieved by continuously mapping the current node to its parent until the root is reached, reversing the resultant path list to reflect root to node order.

  2. The primary function locateSmallestCommonRegion first constructs a mapping (regionHierarchy) from the given list of lists (areas), representing child to parent relationships where the first item is the parent and the subsequent items are the children.

  3. For both areas of interest (area1 and area2), the path from the area to the root is determined using the getPathFromRoot function.

  4. The smallest common region is then determined by comparing these paths and finding the deepest (or lowest) common ancestor. This is efficiently achieved by comparing the elements of the two paths step-by-step until a difference is found.

This solution is efficient as it transforms the problem into a manageable form by leveraging hierarchical parent-child mappings and path comparison techniques. The use of data structures like vector for paths and unordered_map for the hierarchy makes the operations fast and memory efficient.

java
class Solution {
    private List<String> gatherTreeNodePath(String currentNode, Map<String, String> hierarchyMap) {
        List<String> pathList = new ArrayList<>();
        pathList.add(currentNode);

        while (hierarchyMap.containsKey(currentNode)) {
            String parent = hierarchyMap.get(currentNode);
            pathList.add(parent);
            currentNode = parent;
        }
        Collections.reverse(pathList);
        return pathList;
    }

    public String locateMinimalRegion(List<List<String>> regionHierarchy, String regionOne, String regionTwo) {
        Map<String, String> parentMapping = new HashMap<>();

        for (List<String> hierarchy : regionHierarchy) {
            String parent = hierarchy.get(0);
            for (int idx = 1; idx < hierarchy.size(); idx++) {
                parentMapping.put(hierarchy.get(idx), parent);
            }
        }

        List<String> pathForFirstRegion = gatherTreeNodePath(regionOne, parentMapping);
        List<String> pathForSecondRegion = gatherTreeNodePath(regionTwo, parentMapping);

        int pathIndexOne = 0;
        int pathIndexTwo = 0;
        String commonAncestor = "";
        while (pathIndexOne < pathForFirstRegion.size() && pathIndexTwo < pathForSecondRegion.size() 
            && pathForFirstRegion.get(pathIndexOne).equals(pathForSecondRegion.get(pathIndexTwo))) {
            commonAncestor = pathForFirstRegion.get(pathIndexOne);
            pathIndexOne++;
            pathIndexTwo++;
        }

        return commonAncestor;
    }
}

The Java program provided aims to find the smallest common region in a hierarchical structure of regions, often conceptualized as finding the lowest common ancestor in a tree data structure. The key tasks the program accomplishes are:

  1. Parse and organize the hierarchical region structure into a parent-child mapping. This is implemented through a loop that iterates over each parent-to-child list, mapping each child to its direct parent.

  2. Determine the path from each of the two specified regions to the root of the hierarchy. This involves traversing from the region up to its highest ancestor, collecting all ancestors along the way.

  3. Compare the paths from each region to the root. The smallest common region or lowest common ancestor is the last common region appearing in both paths.

To achieve these tasks, the code utilizes a helper function gatherTreeNodePath(String currentNode, Map<String, String> hierarchyMap) which builds a path for a given starting region up to the top-most ancestor. For both specified regions, this function generates a list of ancestors. The program then iterates through both lists simultaneously, and the deepest (last common region in both lists) region where both paths coincide is identified as the smallest common region or the common ancestor.

This method is efficient because it minimizes the number of iterations through the hierarchy and utilizes a list and map for O(1) access times, assuming hashing efficiency. The comparison is direct and carried out linearly with both ancestorial paths. The correctness hinges on accurate input data modeling a well-formed hierarchy where all nodes except the topmost have exactly one parent, and there are no cycles.

The implementation reflects a fine balance between efficiency, clarity, and robustness, making it well-suited for hierarchical structure analysis in Java.

python
class Solution:
    def retrieve_node_path(self, current_node, mapping_of_nodes):
        node_path = []
        node_path.append(current_node)
        
        while current_node in mapping_of_nodes:
            parent = mapping_of_nodes[current_node]
            node_path.append(parent)
            current_node = parent
        
        node_path.reverse()
        return node_path

    def findLeastCommonAncestor(self, node_regions: List[List[str]], first_region: str, second_region: str) -> str:
        relationship_map = {}

        for group in node_regions:
            parent = group[0]
            for child_idx in range(1, len(group)):
                relationship_map[group[child_idx]] = parent

        first_region_path = self.retrieve_node_path(first_region, relationship_map)
        second_region_path = self.retrieve_node_path(second_region, relationship_map)

        idx1, idx2 = 0, 0
        common_ancestor = ""
        while idx1 < len(first_region_path) and idx2 < len(second_region_path) and first_region_path[idx1] == second_region_path[idx2]:
            common_ancestor = first_region_path[idx1]
            idx1 += 1
            idx2 += 1

        return common_ancestor

The solution provided in Python falls under tree data structure algorithms, specifically designed to find the smallest common region or the least common ancestor in a hierarchy of regions.

  • Begin by creating a helper function retrieve_node_path that constructs a list of the regions from a specified node up to the root based on a given mapping of nodes. This function uses a while loop to traverse up the hierarchy by finding parent nodes until the root is reached, and then reverses the list to provide the path from the root to the node.

  • The main function findLeastCommonAncestor initializes a dictionary relationship_map to store parent-child relationships. Iterate through each group of regions, setting the first value as the parent and the remaining values as children. This structure maps out the hierarchy from the provided region groups.

  • Retrieve the paths from two specified regions to the root using the retrieve_node_path function.

  • Walk through both paths simultaneously using two indexes, comparing each node. Continue this comparison until finding the divergence point. The most recent common node before this divergence is the least common ancestor or smallest common region.

  • The function returns the identified common ancestor after it exits the comparison loop. This efficient method ensures that both regions' paths are only created and traversed once, making the solution both space and time effective.

This methodology efficiently handles the search for communal ancestors within a hierarchical structure of regions, leveraging the tree's parent-child relationship efficiently.

Comments

No comments yet.