Facebook Pixel

2822. Inversion of Object

Problem Description

This problem asks you to create a function that inverts the key-value relationship of an object or array.

Given an input object obj, you need to return a new object invertedObj where:

  • The values from the original object become the keys in the inverted object
  • The keys from the original object become the values in the inverted object
  • For arrays, treat the array indices as keys

The main challenge is handling duplicate values. When multiple keys in the original object have the same value, the inverted object should map that value to an array containing all the corresponding keys.

For example:

  • If obj = {a: "1", b: "2"}, then invertedObj = {"1": "a", "2": "b"}
  • If obj = {a: "1", b: "1"}, then invertedObj = {"1": ["a", "b"]} (duplicate handling)
  • If obj = ["x", "y"], then invertedObj = {"x": "0", "y": "1"} (indices as keys)

The solution iterates through each key-value pair in the input object. For each value:

  1. If it doesn't exist as a key in the result object, create it with the current key as its value
  2. If it already exists with a single value, convert that value to an array containing both the existing and new keys
  3. If it already exists with an array value, push the new key to that array

This approach ensures all original keys are preserved when values are duplicated, maintaining a complete inverse mapping.

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

Intuition

The key insight is recognizing that we're essentially creating a reverse mapping. In a normal object, each key maps to exactly one value, but when we invert it, multiple keys might map to the same value - which means in the inverted object, that value needs to map back to multiple keys.

Think of it like a phone book. Originally, we have names (keys) mapping to phone numbers (values). When we invert it, we want phone numbers to map back to names. But what if two people share the same phone number? We need to keep track of both names.

This leads us to three distinct cases we need to handle:

  1. First occurrence: When we encounter a value for the first time, we simply map it to its corresponding key. This is straightforward - invertedObj[value] = key.

  2. Second occurrence: When we see the same value again, we now have two keys that need to be associated with it. We transform the single key into an array containing both the original key and the new one: [originalKey, newKey].

  3. Subsequent occurrences: Once we already have an array, we just keep adding new keys to it.

The natural approach is to iterate through the object once, checking for each value whether it already exists in our result. Using hasOwnProperty() ensures we correctly handle edge cases where values might be falsy (like empty strings or "0").

For arrays, the solution elegantly handles them by treating indices as keys - JavaScript's for...in loop naturally iterates over array indices as strings, so arrays are processed just like objects without needing special handling.

This single-pass approach with conditional logic for handling duplicates gives us an efficient O(n) solution where n is the number of key-value pairs in the input.

Solution Approach

The implementation uses a hash table (JavaScript object) to build the inverted mapping. Here's a step-by-step walkthrough of the algorithm:

Initialize the result object:

const ans: Record<any, any> = {};

We create an empty object that will store our inverted key-value pairs.

Iterate through all keys:

for (const key in obj) {

The for...in loop iterates over all enumerable properties of the object. For arrays, this includes the indices as strings ("0", "1", "2", etc.).

Check if the value already exists as a key in the result:

if (ans.hasOwnProperty(obj[key])) {

This checks whether we've already encountered this value before. Using hasOwnProperty() is important to avoid issues with inherited properties and to correctly handle falsy values.

Handle duplicate values:

When the value already exists in our result object, we have two sub-cases:

  1. If the existing value is already an array:

    if (Array.isArray(ans[obj[key]])) {
        ans[obj[key]].push(key);
    }

    Simply append the new key to the existing array.

  2. If the existing value is a single key:

    else {
        ans[obj[key]] = [ans[obj[key]], key];
    }

    Convert it to an array containing both the existing key and the new key.

Handle first occurrence:

else {
    ans[obj[key]] = key;
}

When encountering a value for the first time, directly map it to its corresponding key.

The algorithm maintains the following invariants:

  • Each unique value from the original object becomes a key in the result
  • Single mappings are stored as simple values
  • Multiple mappings are stored as arrays
  • The order of keys in the arrays follows the iteration order of the original object

Time Complexity: O(n) where n is the number of properties in the input object Space Complexity: O(n) for storing the inverted object

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 solution with a concrete example to see how the algorithm handles different cases:

Input: obj = {a: "1", b: "2", c: "1", d: "1"}

Step-by-step execution:

  1. Initialize: ans = {}

  2. Process key "a" with value "1":

    • Check: Does ans have property "1"? No
    • Action: Set ans["1"] = "a"
    • Result: ans = {"1": "a"}
  3. Process key "b" with value "2":

    • Check: Does ans have property "2"? No
    • Action: Set ans["2"] = "b"
    • Result: ans = {"1": "a", "2": "b"}
  4. Process key "c" with value "1":

    • Check: Does ans have property "1"? Yes
    • Check: Is ans["1"] an array? No (it's "a")
    • Action: Convert to array: ans["1"] = ["a", "c"]
    • Result: ans = {"1": ["a", "c"], "2": "b"}
  5. Process key "d" with value "1":

    • Check: Does ans have property "1"? Yes
    • Check: Is ans["1"] an array? Yes
    • Action: Push "d" to array: ans["1"].push("d")
    • Result: ans = {"1": ["a", "c", "d"], "2": "b"}

Final Output: {"1": ["a", "c", "d"], "2": "b"}

This example demonstrates all three cases:

  • First occurrence (steps 2 & 3): Direct assignment
  • Second occurrence (step 4): Convert single value to array
  • Subsequent occurrences (step 5): Append to existing array

For an array input like ["x", "y", "x"], the same logic applies but with indices as keys:

  • Process index "0" with value "x" → {"x": "0"}
  • Process index "1" with value "y" → {"x": "0", "y": "1"}
  • Process index "2" with value "x" → {"x": ["0", "2"], "y": "1"}

Solution Implementation

1def invertObject(obj):
2    """
3    Inverts an object by swapping its keys and values.
4    If multiple keys have the same value, they are collected into an array.
5
6    Args:
7        obj: The input dictionary to be inverted
8
9    Returns:
10        A new dictionary with keys and values swapped
11    """
12    # Initialize the result dictionary to store inverted key-value pairs
13    result = {}
14
15    # Iterate through each key-value pair in the original dictionary
16    for key, value in obj.items():
17        # Check if the value already exists as a key in the result
18        if value in result:
19            # If the existing entry is already a list, append the new key
20            if isinstance(result[value], list):
21                result[value].append(key)
22            else:
23                # Convert single value to list and add the new key
24                result[value] = [result[value], key]
25        else:
26            # First occurrence of this value, simply assign the key
27            result[value] = key
28
29    return result
30
1import java.util.*;
2
3public class ObjectInverter {
4    /**
5     * Inverts an object by swapping its keys and values.
6     * If multiple keys have the same value, they are collected into an array.
7     * @param obj - The input map to be inverted
8     * @return A new map with keys and values swapped
9     */
10    public static Map<Object, Object> invertObject(Map<Object, Object> obj) {
11        // Initialize the result map to store inverted key-value pairs
12        Map<Object, Object> result = new HashMap<>();
13
14        // Iterate through each entry in the original map
15        for (Map.Entry<Object, Object> entry : obj.entrySet()) {
16            Object key = entry.getKey();
17            Object value = entry.getValue();
18
19            // Check if the value already exists as a key in the result
20            if (result.containsKey(value)) {
21                Object existingValue = result.get(value);
22
23                // If the existing entry is already a list, append the new key
24                if (existingValue instanceof List) {
25                    @SuppressWarnings("unchecked")
26                    List<Object> list = (List<Object>) existingValue;
27                    list.add(key);
28                } else {
29                    // Convert single value to list and add the new key
30                    List<Object> list = new ArrayList<>();
31                    list.add(existingValue);
32                    list.add(key);
33                    result.put(value, list);
34                }
35            } else {
36                // First occurrence of this value, simply assign the key
37                result.put(value, key);
38            }
39        }
40
41        return result;
42    }
43}
44
1#include <unordered_map>
2#include <vector>
3#include <variant>
4#include <string>
5#include <any>
6
7/**
8 * Inverts an object by swapping its keys and values.
9 * If multiple keys have the same value, they are collected into a vector.
10 * @param obj - The input map to be inverted
11 * @returns A new map with keys and values swapped
12 */
13std::unordered_map<std::string, std::variant<std::string, std::vector<std::string>>>
14invertObject(const std::unordered_map<std::string, std::string>& obj) {
15    // Initialize the result map to store inverted key-value pairs
16    std::unordered_map<std::string, std::variant<std::string, std::vector<std::string>>> result;
17
18    // Iterate through each key-value pair in the original map
19    for (const auto& [key, value] : obj) {
20        // Check if the value already exists as a key in the result
21        auto it = result.find(value);
22
23        if (it != result.end()) {
24            // Value exists in result map
25            if (std::holds_alternative<std::vector<std::string>>(it->second)) {
26                // If the existing entry is already a vector, append the new key
27                std::get<std::vector<std::string>>(it->second).push_back(key);
28            } else {
29                // Convert single value to vector and add the new key
30                std::string existing_key = std::get<std::string>(it->second);
31                it->second = std::vector<std::string>{existing_key, key};
32            }
33        } else {
34            // First occurrence of this value, simply assign the key
35            result[value] = key;
36        }
37    }
38
39    return result;
40}
41
1/**
2 * Inverts an object by swapping its keys and values.
3 * If multiple keys have the same value, they are collected into an array.
4 * @param obj - The input object to be inverted
5 * @returns A new object with keys and values swapped
6 */
7function invertObject(obj: Record<any, any>): Record<any, any> {
8    // Initialize the result object to store inverted key-value pairs
9    const result: Record<any, any> = {};
10
11    // Iterate through each key in the original object
12    for (const key in obj) {
13        const value = obj[key];
14
15        // Check if the value already exists as a key in the result
16        if (result.hasOwnProperty(value)) {
17            // If the existing entry is already an array, append the new key
18            if (Array.isArray(result[value])) {
19                result[value].push(key);
20            } else {
21                // Convert single value to array and add the new key
22                result[value] = [result[value], key];
23            }
24        } else {
25            // First occurrence of this value, simply assign the key
26            result[value] = key;
27        }
28    }
29
30    return result;
31}
32

Time and Space Complexity

Time Complexity: O(n) where n is the number of key-value pairs in the input object.

  • The code iterates through each key in the input object exactly once using a for...in loop
  • For each iteration, the operations performed are:
    • Checking if a property exists using hasOwnProperty(): O(1)
    • Checking if a value is an array using Array.isArray(): O(1)
    • Array push operation (when applicable): O(1) amortized
    • Property assignment: O(1)
  • Since we perform constant time operations for each of the n keys, the overall time complexity is O(n)

Space Complexity: O(n) where n is the number of key-value pairs in the input object.

  • The algorithm creates a new object ans to store the inverted mapping
  • In the worst case (when all values in the original object are unique), the new object will have exactly n key-value pairs
  • In cases where multiple keys map to the same value, arrays are created to store these keys, but the total number of stored elements (keys from the original object) remains n
  • The space used for the output object and its contents is proportional to the size of the input, resulting in O(n) space complexity

Common Pitfalls

1. Type Coercion Issues with Object Keys

One significant pitfall occurs when values in the original object are not valid object key types. In JavaScript/TypeScript, object keys are always strings (or Symbols), so non-string values get automatically converted to strings. This can lead to unexpected collisions.

Problem Example:

const obj = {a: 1, b: "1", c: true, d: "true"};
const inverted = invertObject(obj);
// Result: {"1": ["a", "b"], "true": ["c", "d"]}
// The number 1 and string "1" both become the string key "1"
// The boolean true and string "true" both become the string key "true"

Solution: To handle this properly, you could:

  1. Use a Map instead of a plain object, which preserves type distinctions:
function invertObjectWithMap(obj) {
    const result = new Map();
    for (const key in obj) {
        const value = obj[key];
        if (result.has(value)) {
            const existing = result.get(value);
            result.set(value, Array.isArray(existing) ? [...existing, key] : [existing, key]);
        } else {
            result.set(value, key);
        }
    }
    return result;
}
  1. Or maintain type information by wrapping values:
function invertObjectPreserveTypes(obj) {
    const result = {};
    for (const key in obj) {
        const value = obj[key];
        const valueKey = JSON.stringify({type: typeof value, val: value});
        if (result.hasOwnProperty(valueKey)) {
            if (Array.isArray(result[valueKey])) {
                result[valueKey].push(key);
            } else {
                result[valueKey] = [result[valueKey], key];
            }
        } else {
            result[valueKey] = key;
        }
    }
    return result;
}

2. Handling Null and Undefined Values

Another pitfall is dealing with null or undefined values, which can cause runtime errors or unexpected behavior.

Problem Example:

const obj = {a: null, b: undefined, c: null};
const inverted = invertObject(obj);
// Could result in {"null": ["a", "c"], "undefined": "b"}
// But accessing properties on null/undefined in certain contexts might throw errors

Solution: Add validation to skip or handle these special cases:

function invertObjectSafe(obj) {
    const result = {};
    for (const key in obj) {
        const value = obj[key];
        // Skip null or undefined values
        if (value == null) continue;

        // Or convert them to a special string representation
        const safeValue = value ?? `__${value}__`;

        if (result.hasOwnProperty(safeValue)) {
            if (Array.isArray(result[safeValue])) {
                result[safeValue].push(key);
            } else {
                result[safeValue] = [result[safeValue], key];
            }
        } else {
            result[safeValue] = key;
        }
    }
    return result;
}

3. Array Index Type Inconsistency

When inverting arrays, indices are strings in the result, which might not match expectations when comparing with numbers.

Problem Example:

const arr = ["a", "b", "c"];
const inverted = invertObject(arr);
// Result: {"a": "0", "b": "1", "c": "2"}
// Note: values are strings, not numbers
console.log(inverted["a"] === 0); // false
console.log(inverted["a"] === "0"); // true

Solution: Convert indices to numbers if numeric indices are expected:

function invertArray(arr) {
    const result = {};
    for (let i = 0; i < arr.length; i++) {
        const value = arr[i];
        if (result.hasOwnProperty(value)) {
            if (Array.isArray(result[value])) {
                result[value].push(i); // Use numeric index
            } else {
                result[value] = [result[value], i];
            }
        } else {
            result[value] = i; // Store as number
        }
    }
    return result;
}
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More