41 Google Java Developer Interview Questions & Answers

google icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

1. Reverse only the numbers in a string.CodingEasyGoogle

Question Details

Given a string such as 72abd18b, reverse only the digits while leaving the letters in place.

Short Interview Answer (30-60 seconds)

I would use two pointers, one from the left and one from the right. I convert the string to a character array so I can swap characters. Each pointer skips non-digit characters until it reaches a digit. When both pointers are on digits, I swap them and move inward. This reverses only the digits while every letter stays in its original position. The time complexity is O(n), and the auxiliary space is O(n) because Java creates a character array.

Detailed Explanation

See the Code while reading this explanation.

The input is a string that contains letters and digits. We only reverse the digits. The letters must stay exactly where they are. For example, the input is 72abd18b. Its digits from left to right are 7, 2, 1, 8. Reversing those digits gives 8, 1, 2, 7. Putting them back into the same digit positions gives 81abd27b. A two-pointer method fits well because one pointer can find the next digit from the left while the other finds the next digit from the right.

Useful Questions to Ask the Interviewer
  1. Should only digit characters be reversed while all non-digit characters stay in their original positions?
  2. Is returning a new string acceptable, since Java String objects are immutable?
Reverse only the numbers in a string. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is 72abd18b. The required output is 81abd27b. The letters a, b, d, and b stay at indices 2, 3, 4, and

  1. Only the characters at digit positions change. The original digit order is 7, 2, 1,
  2. The reversed digit order is 8, 1, 2, 7.
2. Choose the algorithm and data structure

I use two pointers and a char array. The left pointer searches from the beginning for the next digit. The right pointer searches from the end for the next digit. The char array lets me swap characters because Java String objects cannot be modified. The invariant is that letters never move, and digit positions outside the current pointer range already contain their final reversed digit values.

3. Initialize the state

I convert the input into the character array [7, 2, a, b, d, 1, 8, b]. I set left = 0 and right = 7. Traversal starts at both ends of the array.

4. Walk through the example

At the first step, left is 0 and points to 7. Right starts at 7, which contains b, so right moves to 6, which contains 8. Both pointers are now on digits. I swap indices 0 and 6. The state changes from 72abd18b to 82abd17b. Then left becomes 1 and right becomes 5.

At the second step, left is 1 and points to 2. Right is 5 and points to 1. Both pointers are on digits, so I swap indices 1 and 5. The state changes from 82abd17b to 81abd27b. Then left becomes 2 and right becomes 4.

Next, left sees a at index 2 and moves to index 3. It sees b at index 3 and moves to index 4. Now left = 4 and right = 4. The condition left < right is false, so the loop stops. The d at index 4 is never moved. The final result is 81abd27b.

5. Explain why the result is correct

The left pointer finds the next digit that still needs a value from the right side. The right pointer finds the matching digit from the opposite side. Swapping those two digits puts the correct reversed values at both ends of the active range. Non-digit characters are only skipped, so their positions never change. Repeating this until the pointers meet reverses all digit positions correctly.

6. Explain the Java implementation

The method converts the input String to char[]. It initializes left at 0 and right at the last index. Inside the main loop, the left inner loop skips non-digits from the front. The right inner loop skips non-digits from the back. When both pointers are on digits and left < right, the code swaps chars[left] and chars[right], then increments left and decrements right. When the pointers meet or cross, the method returns a new String built from the modified array.

7. Explain complexity and edge cases

The time complexity is O(n). Each pointer moves only inward, so each character is examined at most a constant number of times. The auxiliary space is O(n) because toCharArray() creates a character array whose size grows with the input. Relevant edge cases are a string with no digits, a string containing only digits, a string with one digit, and adjacent or repeated digits.

Key Insight / Why This Solution Works

The key idea is to change only digit positions. A left pointer searches for the next digit from the front, and a right pointer searches for the next digit from the back. Non-digit characters are skipped. When both pointers are on digits, those two digits are swapped and both pointers move inward. The central invariant is that letters remain at their original indices, while digit positions outside the current [left, right] range already contain their final reversed values. This lets the algorithm reverse the digit sequence directly without storing a separate list of digits.

Code
public class Main {

    public static String reverseDigits(String s) {
        // Convert the immutable String to a mutable character array
        // so digit positions can be swapped.
        char[] chars = s.toCharArray();

        // Start one pointer at each end of the array.
        int left = 0;
        int right = chars.length - 1;

        // Continue while there are still two different positions to compare.
        while (left < right) {
            // Skip non-digit characters from the left.
            // They must remain in their current positions.
            while (left < right && !Character.isDigit(chars[left])) {
                left++;
            }

            // Skip non-digit characters from the right.
            // They also remain in their current positions.
            while (left < right && !Character.isDigit(chars[right])) {
                right--;
            }

            // When both pointers are on digits, swap those two digits.
            if (left < right) {
                char temp = chars[left];
                chars[left] = chars[right];
                chars[right] = temp;

                // These two digit positions are now correct, so move inward.
                left++;
                right--;
            }
        }

        // Build the final String from the modified character array.
        return new String(chars);
    }

    public static void main(String[] args) {
        // Run the exact example used in the diagram.
        String input = "72abd18b";
        String result = reverseDigits(input);

        // Expected output: 81abd27b
        System.out.println(result);
    }
}
Time & Space Complexity

Let n be the number of characters in the string. The time complexity is O(n). The left pointer only moves right, and the right pointer only moves left, so the input is processed at most a constant number of times per character. The auxiliary space is O(n). Java creates a char array of size n so the characters can be swapped. The returned String is then built from that final character array.

Where it is used

This two-pointer pattern is useful when selected elements must be rearranged while other positions stay fixed. Similar logic can reverse only digits, only letters, only vowels, or other characters that match a condition without moving the remaining characters.

Why Interviewers Ask This

This problem tests whether you can recognize a two-pointer pattern and apply it only to selected characters. The interviewer can evaluate whether you move each pointer for the correct reason, keep letters fixed, maintain a useful invariant, and stop when the pointers meet. It also checks basic Java string handling, Character.isDigit(...), swapping logic, edge-case thinking, and whether you can explain O(n) time and O(n) auxiliary space correctly.

Common interview mistakes

One common mistake is swapping before both pointers are on digits. That can move a letter and break the requirement. Another mistake is moving the wrong pointer when a non-digit character is found. A candidate may also forget to move both pointers inward after a successful swap, which can process the same digit again. Another mistake is reversing the whole string instead of only the digit positions. Finally, do not claim O(1) auxiliary space for this Java implementation because the char array grows with the input size.

Interview tip

State the invariant before coding: letters never move, and digit positions outside the current pointer range are already correct. Then trace the exact two swaps, 7 with 8 and 2 with 1. This makes the pointer movement and stopping condition easy to explain.

Interviewer may ask next
Can the auxiliary space be reduced?

If the input is a Java String, this implementation needs O(n) auxiliary space because String is immutable and toCharArray() creates mutable storage. If the input were already a mutable char[], the same two-pointer swaps could be done with O(1) extra space. The time complexity would remain O(n). The tradeoff is that changing the input type allows in-place mutation.

What happens if the string contains no digits or only one digit?

The same algorithm works without any special case. With no digits, the pointers skip non-digit characters until they meet, so the returned string is unchanged. With one digit, there is no second digit to swap with, so the pointers also meet without changing the string. The time complexity remains O(n), and the Java String version still uses O(n) auxiliary space.

2. Decompress a nested encoded string.CodingHardGoogle

Question Details

Given an encoded string in which a parenthesized group is followed by a repeat count, decompress the string, including nested groups.

Short Interview Answer (30-60 seconds)

I would scan the encoded string from left to right and use two stacks. One stack saves the text built before each bracketed group, and the other saves the repeat count. When I see a digit, I build the full number. When I see a closing bracket, I restore the saved text and repeat the current part. That works because each nested group is finished in order. The time is O(n), and the extra space is O(n).

Detailed Explanation

See the Code while reading this explanation.

This problem asks us to turn a coded string into normal text. A number before brackets means repeat the text inside those brackets that many times. The hard part is that one group can sit inside another group, so we must remember the outer text while we work on the inner text. The best fit is to save the old text and the repeat count, then rebuild the string when each group closes. The example 3[a2[c]] becomes accaccacc.

Useful Questions to Ask the Interviewer
  1. Can I assume the input is always valid and every opening bracket has a matching closing bracket?
  2. Should I return the full decoded string even if it becomes very large?
Decompress a nested encoded string. diagram
How to Explain It in an Interview
1. Understand the input and output

The input is a coded string like 3[a2[c]]. The output is the fully decoded string accaccacc. Each number tells us how many times to repeat the text inside the matching brackets. Nested groups mean we must finish the inner group before the outer group can continue.

2. Choose the stacks

I use two stacks. One stack saves the text that was already built before a new group starts. The other stack saves the repeat count for that group. The key idea is that currStr always holds the decoded text for the current open group.

3. Initialize the state

I start with empty stacks, an empty StringBuilder, and currNum = 0. The builder holds the text for the current group. currNum builds multi-digit numbers one digit at a time.

4. Walk through the example

For 3[a2[c]], I read 3 and build the repeat count. When I see [, I save the empty outer text and the count 3. I read a and append it. I read 2, then the next [, so I save a and the count 2. I read c, then ], so I restore a and repeat c two times to get acc. On the final ], I restore the empty outer text and repeat acc three times to get accaccacc.

5. Explain why the result is correct

Every time I enter a new bracket group, I save the exact outer state. Every time I leave one, I restore that state and expand the group the correct number of times. This keeps nested groups in the right order, so the final string is correct.

6. Explain the Java implementation and complexity

The code loops through the string once. Digits update currNum. [ pushes the current text and count, then resets the current state. ] pops the saved state and repeats the current text. Letters append directly. The time is O(n), and the extra space is O(n). Useful edge cases are empty input, no brackets, one group, multi-digit counts, and deep nesting.

Key Insight / Why This Solution Works

The key idea is to keep the decoded text for the current nesting level in one builder, and to save the outer level on stacks when a new bracketed group starts. The string stack stores the text built before the group. The count stack stores how many times the group must repeat. When the closing bracket appears, we pop both values, repeat the current group, and attach it back to the saved outer text. The invariant is that the current builder always represents the decoded text for the current open group, and the stacks hold the unfinished outer groups.

Code
import java.util.Stack;

public class Main {

    public static String decompress(String s) {
        Stack<String> prevStrStack = new Stack<>();
        Stack<Integer> countStack = new Stack<>();
        StringBuilder currStr = new StringBuilder();
        int currNum = 0;

        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);

            if (Character.isDigit(ch)) {
                // Build a multi-digit repeat count like 10 or 123.
                currNum = currNum * 10 + (ch - '0');
            } else if (ch == '[') {
                // Save the outer text and its repeat count before starting the inner group.
                prevStrStack.push(currStr.toString());
                countStack.push(currNum);

                // Start fresh for the nested part.
                currStr.setLength(0);
                currNum = 0;
            } else if (ch == ']') {
                // Finish the current group and rebuild the outer text around it.
                int repeatCount = countStack.pop();
                String prevStr = prevStrStack.pop();

                StringBuilder next = new StringBuilder(prevStr);
                for (int k = 0; k < repeatCount; k++) {
                    next.append(currStr);
                }
                currStr = next;
            } else {
                // Plain letters belong to the current group.
                currStr.append(ch);
            }
        }

        return currStr.toString();
    }

    public static void main(String[] args) {
        String input = "3[a2[c]]";
        System.out.println(decompress(input)); // accaccacc
    }
}
Time & Space Complexity

We read the string from left to right once. Each character is handled when we see it. Digits are combined into one repeat count, letters are added to the current text, and brackets save or restore state. That gives O(n) time, where n is the input length. The stacks can grow with nesting depth, so the extra space is O(n).

Where it is used

This pattern is useful when parsing nested text formats, template strings, and simple expression languages. It also helps any time an inner block must be expanded first and then merged back into the outer text.

Why Interviewers Ask This

This question checks whether you can recognize nested parsing, choose the right data structure, and keep the state correct while you move through the string. It also shows whether you can handle multi-digit counts, restore previous text in the right order, and explain the code clearly in Java. Interviewers use it to see if you can reason about stacks, nesting, and complexity without mixing up the current state with the saved state.

Common interview mistakes

A common mistake is forgetting to build multi-digit numbers like 10 before the bracket starts. Another mistake is saving the current text too late or popping the stacks in the wrong order, which loses the outer string. Some candidates repeat only the inner text and forget to restore the saved outer text around it. Another mistake is not resetting currNum after [ or claiming the wrong complexity.

Interview tip

Say the invariant out loud: currStr is always the decoded text for the current open group, and the stacks save the outer state until the group closes.

Interviewer may ask next
What changes if the input might be invalid, such as a missing bracket or an extra closing bracket?

I would keep the same stack-based approach, but I would add validation before every pop. If a closing bracket appears with empty stacks, or if the string ends while a group is still open, I would return an error or throw an exception. The core decoding logic stays the same.

What changes if the repeat count can be too large for an int?

I would change currNum and the count stack to long, or to BigInteger if the counts can exceed long. The parsing logic stays the same. I would also watch the output size, because the decoded string itself may become too large to build in memory.

3. Given two arrays A and B, remove duplicates from A that are present in B while preserving order.CodingEasyGoogle

Question Details

Given two arrays A and B, remove the duplicates from A that are present in B. Follow up: how would you preserve the original order of A, and how would you return the reverse order?

Short Interview Answer (30-60 seconds)

I would build one HashSet from B so I can quickly identify the values that need deduplication. Then I scan A from left to right. Every value not found in B is always added to the result. For a value found in B, I add it only when a second HashSet has not recorded it before. This keeps its first occurrence and preserves A’s order. The expected time is O(|A| + |B|), and the auxiliary space is O(|B|), excluding the output.

Detailed Explanation

See the Code while reading this explanation.

The task is to filter array A by using array B as a rule. Values that are not in B must stay exactly as they appear in A, including repeated values. Values that are in B may appear only once. We keep their first appearance because we read A from left to right. Two HashSets make these checks simple and fast on average.

Useful Questions to Ask the Interviewer
  1. Should repeated values that are not in B remain unchanged? In this solution, yes.
  2. For values found in B, should I keep the first occurrence from A? Yes.
  3. Must the result preserve the original left-to-right order of A? Yes.
  4. Can A or B be empty? The solution handles both cases.
  5. Should the method return a new array rather than modify A? This solution returns a new array.
Given two arrays A and B, remove duplicates from A that are present in B while preserving order. diagram
How to Explain It in an Interview
1. Understand the input and required output

The inputs are two integer arrays named A and B. The output is a new integer array based on A. Every value not present in B stays in the result. Repeated values also stay when they are not in B. A value present in B may appear only once in the result. Because A is scanned from left to right, its first occurrence is kept.

For the example, A is [4, 2, 2, 5, 3, 2, 5, 6, 3] and B is [2, 3]. The returned result is [4, 2, 5, 3, 5, 6]. The later 2 and 3 values are removed. Both 5 values remain because 5 is not in B.

2. Choose the algorithm and data structures

The first HashSet is named valuesInB. It stores every distinct value from B. It tells us whether the current value from A needs duplicate handling.

The second HashSet is named keptFromB. It stores the values from B that have already been added to the result. It tells us whether a B-value has already been kept.

The central invariant is this: after each processed element of A, the result is the correctly filtered version of the processed prefix of A. Values not in B are never removed. Each value in B appears at most once.

3. Initialize the state

First, add every value from B to valuesInB. For B = [2, 3], valuesInB becomes {2, 3}.

Next, create an empty keptFromB set and an empty result list. Start at index 0 of A and move from left to right.

4. Walk through the example

At index 0, the value is 4. It is not in valuesInB, so add it. Result: [4].

At index 1, the value is 2. It is in B, but keptFromB does not contain it. Add 2 to keptFromB and to the result. keptFromB: {2}. Result: [4, 2].

At index 2, the value is 2 again. It is in B and already in keptFromB, so skip it. Result stays [4, 2].

At index 3, the value is 5. It is not in B, so add it. Result: [4, 2, 5].

At index 4, the value is 3. It is in B and has not been kept yet. Add 3 to keptFromB and to the result. keptFromB: {2, 3}. Result: [4, 2, 5, 3].

At index 5, the value is 2. It is already in keptFromB, so skip it. Result stays [4, 2, 5, 3].

At index 6, the value is 5. It is not in B, so add it even though another 5 is already present. Result: [4, 2, 5, 3, 5].

At index 7, the value is 6. It is not in B, so add it. Result: [4, 2, 5, 3, 5, 6].

At index 8, the value is 3. It is already in keptFromB, so skip it. The final returned array is [4, 2, 5, 3, 5, 6].

5. Explain why the result is correct

For each value from A, there are two cases. If the value is not in B, the algorithm always adds it, so no required value is lost. If the value is in B, keptFromB allows only its first occurrence to be added. Later occurrences are skipped. Because A is processed from left to right, the original order is preserved.

6. Explain the Java implementation

The code first fills valuesInB from B. It then creates keptFromB and an ArrayList named result. During the loop over A, the condition adds a value when it is not in B or when keptFromB.add(value) returns true. HashSet.add returns true only when that value was not already present. Finally, the list is converted to a primitive int array.

7. Explain complexity and edge cases

Building valuesInB takes O(|B|) expected time. Scanning A takes O(|A|) expected time because HashSet lookup and insertion are O(1) on average. The total expected time is O(|A| + |B|).

The two sets together store at most a number of distinct values proportional to |B|. Therefore, the auxiliary space is O(|B|), excluding the output.

If A is empty, the result is empty. If B is empty, all values in A remain unchanged. Duplicate values inside B do not cause a problem because a HashSet stores each distinct value once. Repeated values in A that are not in B remain unchanged.

Key Insight / Why This Solution Works

Only values listed in B need special duplicate handling. The valuesInB HashSet stores the distinct values from B and provides an average O(1) membership check. The keptFromB HashSet records which B-values have already been emitted. While scanning A from left to right, the algorithm always copies values outside B. For a value inside B, it copies the value only when keptFromB.add(value) succeeds for the first time. The invariant is that the result always equals the correctly filtered version of the processed prefix of A. This preserves order without sorting and prevents repeated searches through B or the result.

Code
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Main {

    public static int[] removeDuplicatesPresentInB(int[] A, int[] B) {
        // Step 1: Store every distinct value from B.
        // These are the values that may appear only once in the result.
        Set<Integer> valuesInB = new HashSet<>();
        for (int value : B) {
            valuesInB.add(value);
        }

        // Step 2: Track which values from B have already been kept.
        Set<Integer> keptFromB = new HashSet<>();

        // Step 3: Build the result while preserving A's order.
        List<Integer> result = new ArrayList<>();

        // Step 4: Scan A from left to right.
        for (int value : A) {
            // Keep every value that is not in B.
            // For a value in B, add() returns true only the first time.
            if (!valuesInB.contains(value) || keptFromB.add(value)) {
                result.add(value);
            }
        }

        // Step 5: Convert the result list to a primitive int array.
        return result.stream().mapToInt(Integer::intValue).toArray();
    }

    public static void main(String[] args) {
        int[] A = { 4, 2, 2, 5, 3, 2, 5, 6, 3 };
        int[] B = { 2, 3 };

        int[] output = removeDuplicatesPresentInB(A, B);
        System.out.println(Arrays.toString(output));
        // Expected output: [4, 2, 5, 3, 5, 6]
    }
}
Time & Space Complexity

Let |A| be the number of elements in A and |B| be the number of elements in B. Building valuesInB takes O(|B|) expected time. The algorithm then processes each element of A once. HashSet lookup and insertion are O(1) on average, so scanning A takes O(|A|) expected time. The total expected time is O(|A| + |B|). The two sets store values related to B, so the auxiliary space is O(|B|), excluding the returned array. HashSet operations are average-case O(1), not guaranteed worst-case O(1).

Where it is used

This pattern is useful when one collection defines which values need special duplicate rules. Examples include keeping only the first occurrence of selected identifiers, filtering imported records, cleaning event streams, or deduplicating controlled categories while leaving unrelated repeated values unchanged. The two-set design is useful when membership and already-processed state must be tracked separately.

Why Interviewers Ask This

This question checks whether a candidate reads the duplicate rule carefully instead of applying ordinary deduplication to the entire array. It tests choosing HashSet for average constant-time membership checks, separating two kinds of state, preserving the original order, handling duplicates correctly, and writing clear Java code. It also tests whether the candidate can maintain an invariant, explain the difference between first and last occurrence, and describe hash-based complexity accurately.

Common interview mistakes

A common mistake is removing all duplicates from A. Only values present in B should be deduplicated, so both 5 values must remain in the example. Another mistake is using one set for both membership and already-kept state, which mixes two different responsibilities. Sorting A is also incorrect because it changes the original order. Scanning from right to left is not equivalent because it keeps the last occurrence of each B-value rather than the first. Candidates may also incorrectly claim guaranteed O(|A| + |B|) time instead of expected time for HashSet operations.

Interview tip

Explain the responsibility of each set before writing the loop. valuesInB answers whether the current value needs deduplication. keptFromB answers whether that B-value has already been added. This makes the condition easy to justify and prevents accidental removal of duplicates that are not in B.

Interviewer may ask next
How would you return the result in reverse order while still keeping the first occurrence from A for values in B?

First build the normal preserved-order result with the same left-to-right two-set algorithm. Then reverse the completed result before returning it. This keeps the first-occurrence decision based on the original order of A. Scanning A from right to left would instead retain the last occurrence of each value from B. The expected time remains O(|A| + |B|). The auxiliary space remains O(|B|), excluding the output.

Can the solution use less auxiliary space?

A lower-space approach could repeatedly scan B to decide whether a value needs deduplication and repeatedly scan the current result to see whether that value was already kept. That reduces or removes the sets, but the running time can grow to O(|A| × |B|) or worse because of repeated searches. The two HashSets use O(|B|) auxiliary space and provide the standard expected O(|A| + |B|) solution while preserving order.

4. Given an array, find the number of odd numbers.CodingEasyGoogle

Question Details

Given an array, count how many elements are odd.

Short Interview Answer (30-60 seconds)

I would keep a counter and scan the array from left to right. For each number, I check whether num % 2 != 0. If that condition is true, the number is odd, so I increment the counter. After all elements are processed, I return the counter. This works because the counter always equals the number of odd values seen so far. The time complexity is O(n), and the auxiliary space complexity is O(1).

Detailed Explanation

See the Code while reading this explanation.

The input is an array of integers, and the goal is to return how many values in that array are odd. We do not need to return the odd values or their positions. We only need one number: their count. I start a counter at zero and examine each array value from left to right. When a value is odd, I increase the counter by one. When all values have been processed, the counter is the answer. This simple approach directly matches the required output and uses only constant extra memory.

Useful Questions to Ask the Interviewer
  1. Can the input array be empty?
  2. Can the array contain negative numbers?
Given an array, find the number of odd numbers. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an integer array. The output is one integer representing the number of odd elements. For the example [2, 7, 4, 9, 10, 11], the correct output is 3 because 7, 9, and 11 are odd.

2. Choose the algorithm and maintain the invariant

Use a simple linear traversal with one counter. No map, set, or extra array is needed. The central invariant is: after processing the first k elements, count equals the number of odd values among those processed elements.

3. Initialize the state

Set count to 0 and begin at index 0. This is correct because no elements have been processed yet, so zero odd values have been found.

4. Walk through the example

At index 0, the value is 2. The condition 2 % 2 != 0 is false, so count stays 0. At index 1, the value is 7. The condition 7 % 2 != 0 is true, so count changes from 0 to 1. At index 2, the value is 4. The condition is false, so count stays 1. At index 3, the value is 9. The condition is true, so count changes from 1 to 2. At index 4, the value is 10. The condition is false, so count stays 2. At index 5, the value is 11. The condition is true, so count changes from 2 to 3. All 6 elements have now been processed, so the returned result is 3.

5. Explain why the result is correct

Whenever an odd value is processed, count increases by exactly one. When an even value is processed, count stays unchanged. Therefore, after every step, count still equals the number of odd values processed so far. When the loop ends, all elements have been processed, so count equals the total number of odd elements in the array.

6. Explain the Java implementation

The method starts count at 0. A for-each loop visits every value in nums. The expression num % 2 != 0 checks whether the current value is odd. If it is true, count is incremented. After the loop finishes, the method returns count.

7. Explain complexity and edge cases

The algorithm takes O(n) time because it examines each of the n elements once. It uses O(1) auxiliary space because only the counter and loop variable are needed. An empty array returns 0. An array containing only even values returns 0. An array containing only odd values returns its length. Negative odd values are counted correctly because num % 2 != 0 is also true for odd negative integers in Java.

Key Insight / Why This Solution Works

The key insight is that the problem asks only for a count, so there is no reason to store the odd values themselves. Start count at 0 and process the array from left to right. For each value, test num % 2 != 0. If the condition is true, increment count. The invariant is: after processing the first k elements, count equals the number of odd values among those k elements. Each new element either increases that total by one or leaves it unchanged, so the invariant remains true until the traversal ends.

Code
public class Main {

    public static int countOddNumbers(int[] nums) {
        // Start at zero because no values have been processed yet.
        int count = 0;

        // Visit each array value once, in the same left-to-right order as the diagram.
        for (int num : nums) {
            // A non-zero remainder after division by 2 means the integer is odd.
            // This condition also works correctly for negative odd integers in Java.
            if (num % 2 != 0) {
                // One more odd value has been found, so update the running total.
                count++;
            }
        }

        // Every element has been processed, so count is the final number of odd values.
        return count;
    }

    public static void main(String[] args) {
        // Use the exact example from the approved diagram.
        int[] nums = { 2, 7, 4, 9, 10, 11 };

        // Run the same counting algorithm shown in the walkthrough.
        int result = countOddNumbers(nums);

        // The expected result for this example is 3.
        System.out.println(result);
    }
}
Time & Space Complexity

Let n be the number of elements in the array. The time complexity is O(n) because every element is examined once. The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. The amount of extra memory stays constant because the algorithm stores only the running counter and the current loop value, no matter how large the array becomes.

Where it is used

This pattern is useful when software needs to count items that satisfy a simple condition. Examples include counting invalid records, values above a limit, completed tasks, or numbers with a certain property. A direct linear scan works well when each item can be checked independently and only the final count is needed.

Why Interviewers Ask This

This question checks whether a candidate can translate a simple requirement into correct and minimal Java code. It tests array traversal, modulo arithmetic, condition handling, state updates, and accurate complexity analysis. It also shows whether the candidate avoids unnecessary data structures, handles cases such as negative odd values correctly, and can explain a simple loop invariant that proves why the final count is correct.

Common interview mistakes

One common mistake is using num % 2 == 1. In Java, negative odd values can produce -1 as the remainder, so num % 2 != 0 is the safer test and matches the diagram. Another mistake is reversing the condition and incrementing count for even values. Candidates may also return the odd values or their indices instead of returning the count. Another unnecessary mistake is creating a list, set, or other input-sized structure even though only a running total is needed. The illustrated solution uses O(1) auxiliary space.

Interview tip

While coding, state the invariant clearly: after every processed element, count equals the number of odd values seen so far. That makes both the loop logic and the correctness argument easy to explain.

Interviewer may ask next
How would the solution change if the numbers arrived one at a time as a stream?

The main algorithm would stay the same. Keep count as persistent state. For every incoming number, test num % 2 != 0 and increment count when the value is odd. There is no need to store earlier values. Processing n values still takes O(n) total time, and the auxiliary space remains O(1). The tradeoff is that the algorithm keeps only the count, so earlier values are unavailable unless another part of the system stores them.

How would the solution change if you had to return the odd values instead of only their count?

I would keep the same left-to-right traversal and the same num % 2 != 0 condition, but I would add each odd value to a result list instead of only incrementing a counter. Correctness is preserved because exactly the values that satisfy the odd condition are stored. The time complexity remains O(n). The additional space becomes O(k), where k is the number of odd values returned. The tradeoff is the extra memory required to keep those values.

5. Given a CPU task list with queued time and execution time, return the execution order.CodingMediumGoogle

Question Details

A CPU task is defined by id, queued_time, and exec_time. Given a collection of tasks, return the order in which a one-core CPU executes them. Include the follow-up where tasks also have priorities.

Short Interview Answer (30-60 seconds)

I would sort the tasks by queued time and use a min heap for the tasks that are currently available. The heap chooses the smallest execution time first and uses the smaller task id as the tie-breaker. If the heap is empty, I jump the current time to the next queued task. After a task runs, I advance time by its execution time. This correctly follows the CPU rule in O(n log n) time with O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

We have a list of CPU tasks. Each task has an id, a time when it becomes ready, and a time needed to finish. We must return the ids in the exact order that one CPU runs them. The CPU can run only a task that is already ready. When several tasks are ready, it chooses the one that needs the least time. If two need the same time, it chooses the smaller id. If nothing is ready, the clock moves forward to the next task that becomes ready.

Useful Questions to Ask the Interviewer
  1. When two available tasks have the same execution time, should the smaller id run first?
  2. Can the accumulated CPU time exceed the range of a Java int?
  3. For the priority follow-up, does a smaller or larger priority value mean higher priority?
Given a CPU task list with queued time and execution time, return the execution order. diagram
How to Explain It in an Interview
1. Understand the input and required output

Each task is represented by its id, queued_time, and exec_time. The output is the sequence of task ids in the order the one-core CPU executes them. A task cannot run before its queued_time. Once the CPU chooses a task, it runs for that task's full exec_time before choosing another task.

2. Choose the algorithm and data structure

First, store each task as [queued_time, exec_time, id] and sort these records by queued_time. Keep a pointer to the next task that has not entered the available set. Use a Java PriorityQueue as a min heap for tasks that have already arrived. The heap compares exec_time first. If execution times are equal, it compares id.

The central invariant is: before the CPU chooses the next task, the heap contains exactly the tasks whose queued_time is at most the current time and that have not already run.

3. Initialize the state

Start with time = 0, i = 0, an empty result array, and an empty min heap. The sorted records let us discover task arrivals in order. If the heap is empty and the next task is still in the future, move time directly to that task's queued_time. This represents an idle CPU without advancing the clock one unit at a time.

4. Walk through the verified example

The example is id 0 -> [1, 2], id 1 -> [2, 4], id 2 -> [3, 2], and id 3 -> [4, 1]. Each pair is [queued_time, exec_time].

At time 0, the heap is empty and the next queued_time is 1, so time jumps to 1. Task 0 is added. The heap contains [(2, 0)]. The CPU chooses task 0, runs it for 2 units, and time becomes 3. The order is [0].

At time 3, tasks 1 and 2 have arrived. They are added to the heap. Before the next choice, the heap contains [(2, 2), (4, 1)]. Task 2 wins because its exec_time is 2 instead of 4. It runs for 2 units. Time becomes 5, and the order becomes [0, 2].

At time 5, task 3 has arrived. It is added to the heap. The heap contains [(1, 3), (4, 1)]. Task 3 wins because exec_time 1 is smaller than 4. It runs for 1 unit. Time becomes 6, and the order becomes [0, 2, 3].

At time 6, only task 1 remains in the heap as [(4, 1)]. It runs for 4 units. Time becomes 10, and the final returned order is [0, 2, 3, 1].

5. Explain why the result is correct

At every decision point, the heap contains exactly the tasks that have already arrived and are not yet processed. Therefore, removing the smallest heap item applies the CPU rule to the correct set of available tasks. Sorting by queued_time makes tasks enter the heap as soon as they become available. Jumping time only when the heap is empty correctly handles true idle gaps.

6. Explain the Java implementation

The code copies each task into a record containing queued_time, exec_time, and its original id. It sorts those records by queued_time. A PriorityQueue stores the currently available tasks and orders them by exec_time, then id. The main loop jumps time when the CPU is idle, adds every task that has arrived, removes the best available task, appends its id to the answer, and adds its exec_time to the current time.

7. Explain complexity and edge cases

Sorting costs O(n log n). Every task is pushed into the heap once and popped once. Each heap operation costs O(log n), so the total running time is O(n log n). Auxiliary space is O(n). Important cases from this scheduling rule are several tasks with the same queued_time, equal exec_time where the smaller id runs first, an idle gap before the next task arrives, and a single-task input. Java should use long for the current time because cumulative execution time can exceed int.

Key Insight / Why This Solution Works

The key idea is to separate task arrival from task selection. Sorting the records by queued_time lets us discover newly available tasks in order. A min heap stores only tasks that can run now. Its key is (exec_time, id), so the heap exposes exactly the task the CPU should choose next. The invariant is that, before every selection, the heap contains every arrived but not-yet-run task. If the heap is empty, jumping time to the next queued_time safely skips an idle period.

Code
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Main {

    // Keep the platform-style solution method shown in the diagram.
    static class Solution {

        public int[] getOrder(int[][] tasks) {
            int n = tasks.length;

            // Build [queued_time, exec_time, id] records.
            // The original id must be preserved because the records will be sorted.
            int[][] arr = new int[n][3];
            for (int id = 0; id < n; id++) {
                arr[id][0] = tasks[id][0];
                arr[id][1] = tasks[id][1];
                arr[id][2] = id;
            }

            // Sort by queued_time so arrivals can be processed from left to right.
            Arrays.sort(arr, Comparator.comparingInt(task -> task[0]));

            // Min heap of currently available tasks.
            // Smaller exec_time wins. If equal, smaller original id wins.
            PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> {
                if (a[1] != b[1]) {
                    return Integer.compare(a[1], b[1]);
                }
                return Integer.compare(a[2], b[2]);
            });

            int[] order = new int[n];

            // Use long because cumulative execution time can exceed int range.
            long time = 0;
            int i = 0;
            int k = 0;

            // Continue until every task has been added and every available task has run.
            while (i < n || !pq.isEmpty()) {
                // If no task is available, jump directly to the next queued_time.
                if (pq.isEmpty() && time < arr[i][0]) {
                    time = arr[i][0];
                }

                // Add every task that has arrived by the current CPU time.
                while (i < n && arr[i][0] <= time) {
                    pq.offer(arr[i]);
                    i++;
                }

                // The heap now exposes the correct next task by exec_time, then id.
                int[] task = pq.poll();

                // Record the original task id in execution order.
                order[k] = task[2];
                k++;

                // The chosen task runs completely before another task is selected.
                time += task[1];
            }

            return order;
        }
    }

    public static void main(String[] args) {
        // Exact example from the approved diagram: [queued_time, exec_time].
        int[][] tasks = { { 1, 2 }, { 2, 4 }, { 3, 2 }, { 4, 1 } };

        Solution solution = new Solution();
        int[] result = solution.getOrder(tasks);

        // Expected output: [0, 2, 3, 1]
        System.out.println(Arrays.toString(result));
    }
}
Time & Space Complexity

Let n be the number of tasks. Sorting the task records by queued_time takes O(n log n) time. Each task is inserted into the min heap exactly once and removed exactly once. Each push or pop costs O(log n), so all heap work is O(n log n). The total time is O(n log n). The sorted task records and heap can each grow with the input, so the auxiliary space is O(n). The CPU clock is stored in a long to avoid overflow from accumulated execution times.

Where it is used

This sorted-arrival plus priority-queue pattern is useful in systems where jobs become available at different times and the next job is chosen by a ranking rule. Examples include CPU scheduling simulations, background job workers, batch-processing systems, and event simulators. The same structure also supports the priority follow-up because the arrival handling stays the same and only the heap ordering rule changes.

Why Interviewers Ask This

This problem tests whether you can combine sorting with a priority queue for tasks that become available over time. The interviewer is checking whether you separate arrival order from execution order, maintain the correct available-task invariant, preserve original ids after sorting, implement the exec_time and id tie-break rules correctly, handle CPU idle gaps, use a safe type for accumulated time, and explain the O(n log n) time and O(n) auxiliary-space costs accurately.

Common interview mistakes

One common mistake is adding future tasks to the heap before their queued_time. The heap must contain only currently available tasks. Another mistake is comparing only exec_time and forgetting that a smaller id breaks a tie. Candidates can also lose the original ids after sorting the records. Another mistake is advancing time one unit at a time when the heap is empty instead of jumping to the next queued_time. Finally, using int for accumulated CPU time can overflow even when individual task times fit in int.

Interview tip

Say the invariant before coding: the heap contains exactly the tasks that have arrived and have not yet run. Then implement the loop in the same order as the diagram: jump time if necessary, add all arrived tasks, pop the best task, record its id, and advance time.

Interviewer may ask next
How would the solution change if every task also had a priority?

The sorted sweep by queued_time stays the same. The available-task heap also stays the same type of data structure, but its comparator changes. It should compare priority first using the priority direction defined by the problem, then exec_time, then id. Correctness is preserved because the heap still contains exactly the arrived, unfinished tasks and its top item now follows the new scheduling rule. The total time remains O(n log n), auxiliary space remains O(n), and the main tradeoff is a more detailed ordering rule.

What happens when several tasks have the same queued_time or the same exec_time?

Before choosing a task, the algorithm adds every task with queued_time <= current time, so tasks arriving together are considered in the same available set. The heap chooses the smaller exec_time first. If two available tasks have the same exec_time, the smaller id runs first. The invariant and algorithm do not change. The total time stays O(n log n), and the auxiliary space stays O(n).

6. Given HTML-like parsed trees, determine whether two documents contain the same text.CodingMediumGoogle

Question Details

Given a tree representation of HTML parsed output, determine whether two HTML documents contain the same text. Follow up: handle the case where the full documents do not fit in memory.

Short Interview Answer (30-60 seconds)

I would compare the documents lazily instead of building two complete strings. I create one text-character iterator for each parsed tree. Each iterator uses iterative DFS and returns the next text character in document order. I compare those characters immediately. If they differ, I return false. If both iterators finish together, I return true. Each node and text character is processed at most once, so time is O(total tree nodes + total text characters), with O(h1 + h2) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The inputs are two trees that represent HTML-like documents. Some nodes represent tags, and some nodes contain text. We only care about the text and the order in which that text appears. The tree shapes and tag names can be different. For example, one document may split "Hi AI!" across several text nodes while another keeps it in one text node. We must return true only when reading all text from left to right gives exactly the same characters. We compare the text little by little instead of building two full strings.

Useful Questions to Ask the Interviewer
  1. Should tag names and tree structure be ignored as long as the document-order text is identical?
  2. Should spaces and punctuation be compared exactly?
  3. For the large-document follow-up, can the parser provide text nodes lazily instead of loading the complete document?
Given HTML-like parsed trees, determine whether two documents contain the same text. diagram
How to Explain It in an Interview
1. Understand the input and required output

Each input is an HTML-like parsed tree. Element nodes represent tags. Text nodes contain characters. We return true when the concatenation of all text nodes in document order is identical for both trees. Otherwise, we return false. The tag names and tree shapes do not need to match.

The diagram uses this example. Document A represents <div>"Hi "<b>"AI"</b>"!"</div>. Its text nodes in order are "Hi ", "AI", and "!". Document B represents <section><p>"Hi AI!"</p></section>. Its text nodes in order are "Hi AI!". Both documents produce the same character stream: H, i, space, A, I, !.

2. Choose the traversal and data structure

I create a TextCharIterator for each root. Each iterator performs iterative depth-first traversal in document order. It stores a stack of Frame objects. A frame stores the current node, the next child index for an element node, and the next character index for a text node.

The central invariant is that every call to nextChar() returns the next unconsumed text character in document order. Tags only guide traversal. They are not compared.

3. Initialize the state

it1 is the iterator for document A, and it2 is the iterator for document B. Each iterator starts with its root frame on its DFS stack. No text has been consumed yet.

When nextChar() sees an element node, it pushes the next child. When all children have been processed, it pops that element frame. When it sees a text node, it returns one character and advances charIndex. When that text node is exhausted, it pops the text frame and continues traversal.

4. Walk through the verified example

Step 1: document A returns H, and document B returns H. They are equal, so continue.

Step 2: A returns i, and B returns i. They are equal, so continue.

Step 3: A returns a space, and B returns a space. They are equal, so continue.

Step 4: A traverses into the <b> child and returns A. B continues reading its single text node and also returns A. They are equal.

Step 5: A returns I, and B returns I. They are equal.

Step 6: A finishes the <b> content, returns to the parent, reaches the final text node, and returns !. B returns !. They are equal.

Step 7: both iterators return null. This means both text streams are exhausted together, so the method returns true.

5. Explain why the result is correct

Each iterator always exposes the next unconsumed text character in document order. Therefore, the comparison loop compares the exact flattened text streams one character at a time. If two emitted characters differ, the documents cannot contain the same text. If one iterator ends before the other, one document has extra text. If both iterators end together without any mismatch, every character and the total text length are equal, so the answer is true.

6. Explain the Java implementation, complexity, and edge cases

Frame stores traversal progress for one node. TextCharIterator owns a Deque<Frame> and implements nextChar(). For a text node, it returns the next character while characters remain. When the text is exhausted, it pops that frame. For an element node, it pushes the next child until all children are processed, then pops the element frame.

sameText() creates two iterators and repeatedly requests one character from each. If either character is null, it returns true only when both are null. Otherwise, it compares the two Character values with equals(). A mismatch returns false immediately.

The time complexity is O(total tree nodes + total text characters). Each node and character is processed at most once. Auxiliary space is O(h1 + h2) for the two DFS stacks, where h1 and h2 are the tree heights. Relevant edge cases are both trees empty, empty text nodes, extra trailing text on one side, whitespace or punctuation differences, and deeply nested elements.

Key Insight / Why This Solution Works

The key idea is to compare the logical text streams instead of comparing the HTML structure or creating two flattened strings. Each tree gets a lazy TextCharIterator. The iterator performs iterative DFS and emits one text character at a time in document order. Its invariant is: nextChar() always returns the next unconsumed text character. The main loop compares the two emitted characters immediately. A character mismatch returns false. If one stream ends before the other, it returns false. If both end together, it returns true. This also solves the large-document follow-up because the complete document text is never materialized.

Code
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.List;

public class Main {

    static class Node {

        final boolean isText;
        final String text;
        final List<Node> children;

        // Create a text node. Text nodes have no child elements.
        Node(String text) {
            this.isText = true;
            this.text = text;
            this.children = Collections.emptyList();
        }

        // Create an element node. Its content is represented by its children.
        Node(List<Node> children) {
            this.isText = false;
            this.text = "";
            this.children = children;
        }
    }

    static class Frame {

        final Node node;
        int childIndex;
        int charIndex;

        // Store traversal progress for one node.
        Frame(Node node) {
            this.node = node;
            this.childIndex = 0;
            this.charIndex = 0;
        }
    }

    static class TextCharIterator {

        private final Deque<Frame> stack = new ArrayDeque<>();

        TextCharIterator(Node root) {
            // Start document-order DFS from the root.
            if (root != null) {
                stack.push(new Frame(root));
            }
        }

        Character nextChar() {
            // Keep traversing until one text character can be emitted.
            while (!stack.isEmpty()) {
                Frame frame = stack.peek();

                if (frame.node.isText) {
                    // Emit exactly one unconsumed text character.
                    if (frame.charIndex < frame.node.text.length()) {
                        return frame.node.text.charAt(frame.charIndex++);
                    }

                    // This text node is exhausted, so return to its parent.
                    stack.pop();
                    continue;
                }

                if (frame.childIndex < frame.node.children.size()) {
                    // Visit the next child in document order.
                    Node child = frame.node.children.get(frame.childIndex++);
                    stack.push(new Frame(child));
                } else {
                    // All children of this element have been processed.
                    stack.pop();
                }
            }

            // The document has no remaining text characters.
            return null;
        }
    }

    static boolean sameText(Node root1, Node root2) {
        TextCharIterator it1 = new TextCharIterator(root1);
        TextCharIterator it2 = new TextCharIterator(root2);

        while (true) {
            // Read the next document-order text character from both trees.
            Character c1 = it1.nextChar();
            Character c2 = it2.nextChar();

            // Equality requires both streams to finish at the same time.
            if (c1 == null || c2 == null) {
                return c1 == c2;
            }

            // Stop as soon as the two text streams differ.
            if (!c1.equals(c2)) {
                return false;
            }
        }
    }

    public static void main(String[] args) {
        // Document A: <div>"Hi "<b>"AI"</b>"!"</div>
        Node documentA = new Node(
            List.of(new Node("Hi "), new Node(List.of(new Node("AI"))), new Node("!"))
        );

        // Document B: <section><p>"Hi AI!"</p></section>
        Node documentB = new Node(List.of(new Node(List.of(new Node("Hi AI!")))));

        // Both document-order text streams are exactly "Hi AI!".
        System.out.println(sameText(documentA, documentB)); // true
    }
}
Time & Space Complexity

Let N be the total number of tree nodes across both documents, and let T be the total number of text characters. The time complexity is O(N + T). Every node is processed at most once, and every text character is emitted at most once. The algorithm can stop earlier when it finds a mismatch. Auxiliary space is O(h1 + h2), where h1 and h2 are the maximum heights of the two DFS stacks. It does not store the complete flattened text.

Where it is used

This pattern is useful when structured documents must be compared by their logical text instead of their exact tree structure. It is especially useful for parsers, large HTML-like documents, and streaming pipelines where data should be compared as it is produced rather than copied into large intermediate strings.

Why Interviewers Ask This

This problem tests whether you can separate logical content from tree structure and recognize that building complete intermediate strings is unnecessary. It checks document-order traversal, explicit stack state, lazy iteration, early termination, and correct Java equality handling. The follow-up also tests whether you can adapt the same idea to large or streaming inputs while keeping memory proportional to traversal state instead of total document size.

Common interview mistakes

A common mistake is comparing tag names or tree shapes even though only document-order text matters. Another mistake is flattening both complete documents into strings, which does not address the large-document follow-up. Candidates can also visit children in the wrong order and change the text stream. Another error is returning true when only one iterator is exhausted instead of requiring both to finish together. Spaces and punctuation must also be treated as real characters, so ignoring them can produce a wrong result.

Interview tip

State the invariant first: every call to nextChar() returns the next unconsumed text character in document order. Once that is clear, the comparison loop and the simultaneous-exhaustion condition are easy to justify.

Interviewer may ask next
How would you handle documents that are too large to fit in memory?

Use streaming parsers that lazily yield text nodes in document order. Keep only the current text node and character position for each stream, then compare emitted characters with the same loop. Correctness stays the same because each side still exposes its next unconsumed text character. The time remains proportional to all parsed nodes and text characters, while extra comparison memory depends on parser and traversal state instead of the full document contents.

What happens if one document contains extra trailing text after all earlier characters matched?

One iterator eventually returns another character while the other returns null. The condition c1 == null || c2 == null then returns false because only one side is exhausted. This is correct because equal text streams must contain the same characters and have the same length. The complexity remains O(total tree nodes + total text characters) time with O(h1 + h2) auxiliary stack space.

7. Given a binary tree of 0s and 1s, return the number of islands.CodingMediumGoogle

Question Details

Given a binary tree having nodes with values 0 and 1, return the number of islands. Follow up: return the sizes of the unique islands.

Short Interview Answer (30-60 seconds)

I would count islands with DFS. I treat every connected group of 1s as one island, and I keep a HashSet so I do not count the same node twice. When I find a new unvisited 1, I start DFS, measure that island’s size, and store it. Then I keep traversing the tree. This visits each node once, so the time is O(N). The extra space is O(H), where H is the tree height.

Detailed Explanation

See the Code while reading this explanation.

This problem asks me to count the separate groups of 1s in a binary tree. Two 1s belong to the same group when they are linked by parent-child edges. The follow-up asks for the size of each group too. The diagram solves this with DFS. I start a DFS only when I find a new 1 that has not been visited. That one search collects the whole group, counts its size, and stores it. Then I keep moving through the tree until every node is checked.

Useful Questions to Ask the Interviewer
  1. Do you want only the count, or the count plus each island size?
  2. Can I assume the tree contains only 0 and 1 values?
Given a binary tree of 0s and 1s, return the number of islands. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a binary tree with node values 0 or 1. The output is the number of islands, and for the follow-up, the size of each island in the order they are found. An island is one connected group of 1s.

2. Choose the algorithm and data structure

I use DFS plus a HashSet named visited. The set stores node references that already belong to an island. The main idea is simple. When I find a new 1, I start one DFS from that node and count every connected 1 in that component.

3. Initialize the state

I start with islands = 0, an empty list sizes, and an empty visited set. This matches the diagram. At the beginning, no island has been counted yet.

4. Walk through the example

In the shown tree, the root 1 starts island A. DFS finds the whole left chain of 1s, so the size becomes 4. Then the right-left 1 starts island B, so size 1 is added. The right-right 1 starts island C, so another 1 is added. The exact states become islands = 1, then 2, then 3, and sizes = [4], then [4, 1], then [4, 1, 1].

5. Explain why the result is correct

The invariant is that every node in visited already belongs to one counted island. Each new DFS starts only from an unseen 1, so one DFS can never count two islands. Because the traversal checks the whole tree, every island is found once.

6. Explain the Java implementation

The method numberOfIslandsAndSizes(TreeNode root) resets the state, walks the tree, and then builds the final int[] result. The first entry is the island count. The remaining entries are the island sizes. The helper explore(TreeNode node) measures one island. It marks each node reference, then it goes left and right to collect the full connected group. After the walk, the method builds the final int[] result with the count in position 0 and each island size after that. The Main class builds the exact example tree from the diagram and prints [3, 4, 1, 1].

7. Explain complexity and edge cases

The time is O(N) because the tree nodes are processed once in the traversal. The diagram summarizes the extra working space as O(H), where H is the tree height. Good edge cases are an empty tree, all zeros, all ones, a single 0, and a single 1.

Key Insight / Why This Solution Works

I use DFS with a visited set. The main invariant is that every node reference in visited already belongs to one island. When I reach a node with value 1 that is not visited yet, I know I found a new island. I run DFS from that node to mark every connected 1 in the same component and count how many nodes are inside it. Then I store that size and continue. This works because each island is counted once, and every 1 node is assigned to exactly one island.

Code
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Main {

    public static void main(String[] args) {
        // Build the exact example tree from the diagram.
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(1);
        root.right = new TreeNode(0);

        root.left.left = new TreeNode(1);
        root.left.right = new TreeNode(0);

        root.left.left.left = new TreeNode(1);
        root.left.left.right = new TreeNode(0);

        root.right.left = new TreeNode(1);
        root.right.right = new TreeNode(1);

        Solution solution = new Solution();
        int[] result = solution.numberOfIslandsAndSizes(root);

        // The diagram's example output is [3, 4, 1, 1].
        System.out.println(Arrays.toString(result));
    }
}

class Solution {

    private int islands;
    private List<Integer> sizes;
    private Set<TreeNode> visited;

    public int[] numberOfIslandsAndSizes(TreeNode root) {
        // Reset the working state for one run.
        islands = 0;
        sizes = new ArrayList<>();
        visited = new HashSet<>();

        // Walk the whole tree and start one DFS for every new island.
        dfs(root);

        // Pack the count and the island sizes into one answer array.
        int[] result = new int[sizes.size() + 1];
        result[0] = islands;
        for (int i = 0; i < sizes.size(); i++) {
            result[i + 1] = sizes.get(i);
        }
        return result;
    }

    private void dfs(TreeNode node) {
        if (node == null) {
            return;
        }

        // A new island starts only when we see an unseen node with value 1.
        if (node.val == 1 && !visited.contains(node)) {
            islands++;
            int size = explore(node);
            sizes.add(size);
        }

        // Keep traversing so we can find later islands in the rest of the tree.
        dfs(node.left);
        dfs(node.right);
    }

    private int explore(TreeNode node) {
        if (node == null || node.val == 0 || visited.contains(node)) {
            return 0;
        }

        // Mark this node now so the same island is never counted twice.
        visited.add(node);

        int count = 1;
        count += explore(node.left);
        count += explore(node.right);
        return count;
    }
}

class TreeNode {

    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}
Time & Space Complexity

The diagram shows O(N) time because the tree traversal touches each node once. The extra memory is O(H), where H is the tree height, because the DFS recursion can go as deep as the longest path in the tree. The code also keeps a list of island sizes, but that list is the output we want to return. So the main working memory comes from the recursion stack.

Where it is used

This pattern is useful when a tree or graph has separate connected groups. Common examples are counting clusters, regions, or components. It is also useful when each group needs its own size, not just the total count.

Why Interviewers Ask This

They want to see whether you can recognize connected components in a tree. They also want to see if you can choose DFS, keep track of visited nodes, and avoid counting the same island more than once. This question also checks whether you can explain recursion, the order of traversal, and the final count and sizes in simple English. Correct complexity wording is part of the test too.

Common interview mistakes

A common mistake is to count every 1 as a separate island. That is wrong because connected 1s belong to the same group. Another mistake is to forget the visited set, which can make the same island count more than once. A third mistake is to stop after the first island and miss the later ones on the other side of the tree. It is also easy to mix up the final count with the list of sizes.

Interview tip

Say the invariant out loud: one new unseen 1 means one new island, and one DFS collects its full size.

Interviewer may ask next
What changes if I only need the number of islands and not the sizes?

I can remove the sizes list and the final array building. The DFS logic stays the same, and I still start one search for each new island. The time stays O(N). The code uses a little less memory because it no longer stores each island size.

What changes if the tree is very deep?

I can replace the recursive DFS with an explicit stack. The counting logic does not change. I still start one search for each new island and mark nodes visited. The time stays O(N). The tradeoff is that I avoid recursion overflow, but I still use extra stack memory.

8. Given a decreasing and then increasing series, return the index of the minimum element.CodingEasyGoogle

Question Details

Given a sequence that decreases and then increases, return the index of the minimum element.

Short Interview Answer (30-60 seconds)

I would use binary search on the valley shape. I keep an inclusive range from left to right and compare nums[mid] with nums[mid + 1]. If nums[mid] is greater, the sequence is still decreasing, so the minimum must be to the right. Otherwise, the minimum is at mid or to its left. I keep shrinking the range until left equals right, then return that index. The solution takes O(log n) time and O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input is a sequence whose values first go down and then go up. We need to return the position of the smallest value, not the smallest value itself. The useful property is that the direction around the middle tells us which side still contains the minimum. Because each comparison lets us remove about half of the remaining positions, binary search fits this problem well. We keep narrowing the possible range until only one index remains. That remaining index is the answer.

Useful Questions to Ask the Interviewer
  1. Is the sequence guaranteed to contain at least one element?
  2. Is the sequence strictly decreasing and then strictly increasing, so there is one clear valley minimum?
Given a decreasing and then increasing series, return the index of the minimum element. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an array whose values decrease and then increase. We must return the index of the minimum element. For the diagram example, the array is [9, 7, 5, 3, 4, 6, 8]. The minimum value is 3, and it is at index 3. Therefore, the returned result is 3.

2. Choose binary search

Use an inclusive interval [left, right]. The central invariant is that the minimum index always stays inside this interval. Compare nums[mid] with nums[mid + 1]. This tells us whether mid is still on the decreasing side or whether the minimum is at mid or somewhere to its left.

3. Initialize the state

Set left = 0 and right = nums.length - 1. For the example, the initial interval is [0, 6]. The loop runs only while left < right. Because of that condition, mid is always smaller than right, so nums[mid + 1] is safe to read.

4. Walk through the example

Iteration 1 starts with [0, 6]. mid = 0 + (6 - 0) / 2 = 3. nums[3] = 3 and nums[4] = 4. Since 3 < 4, the minimum cannot be strictly to the right of index 3. Keep mid and the left side by setting right = 3. The new interval is [0, 3].

Iteration 2 starts with [0, 3]. mid = 0 + (3 - 0) / 2 = 1. nums[1] = 7 and nums[2] = 5. Since 7 > 5, the sequence is still decreasing at mid. The minimum must be to the right of index 1. Set left = mid + 1 = 2. The new interval is [2, 3].

Iteration 3 starts with [2, 3]. mid = 2 + (3 - 2) / 2 = 2. nums[2] = 5 and nums[3] = 3. Since 5 > 3, the minimum must be to the right of index 2. Set left = mid + 1 = 3. The new interval is [3, 3].

Now left equals right, so the loop stops and we return index 3.

5. Explain why the result is correct

The minimum index always remains inside [left, right]. If nums[mid] > nums[mid + 1], the values are still moving downward at mid, so the minimum must be somewhere to the right. We can safely remove every index through mid. Otherwise, the sequence is rising from mid to mid + 1, or mid is the valley point, so the minimum is at mid or to its left. We keep mid by setting right = mid. Every update reduces the interval. When one index remains, it must be the minimum index.

6. Explain the Java implementation

The Java method stores left and right as integer indices. It calculates mid with left + (right - left) / 2. It compares nums[mid] with nums[mid + 1], then changes exactly one boundary. If the comparison is descending, left becomes mid + 1. Otherwise, right becomes mid. The loop stops when left == right, and the method returns left.

7. Explain complexity and edge cases

Each iteration removes about half of the remaining search interval, so the time complexity is O(log n). The algorithm uses only a few integer variables, so auxiliary space is O(1). If a one-element array is allowed, it immediately returns index 0. The same method works when the minimum is close to either side and when the values are negative, as long as the decreasing-then-increasing shape is preserved.

Key Insight / Why This Solution Works

The key insight is to use the local direction at mid. Keep an inclusive interval [left, right] that always contains the minimum index. If nums[mid] > nums[mid + 1], mid is on the decreasing side, so the minimum must be strictly to the right and left becomes mid + 1. Otherwise, the minimum is at mid or to its left, so right becomes mid. Each update reduces the interval while keeping the minimum inside it. This is why binary search can find the valley minimum without scanning every element.

Code
class Solution {

    public int findMinIndex(int[] nums) {
        // Start with the full inclusive search interval.
        // The minimum index always remains inside [left, right].
        int left = 0;
        int right = nums.length - 1;

        // Keep searching while more than one candidate index remains.
        while (left < right) {
            // Compute the midpoint without adding left and right directly.
            // Because left < right, mid + 1 is a valid index here.
            int mid = left + (right - left) / 2;

            // If the next value is smaller, mid is on the decreasing side.
            // The minimum must be strictly to the right of mid.
            if (nums[mid] > nums[mid + 1]) {
                left = mid + 1;
            } else {
                // Otherwise, the minimum is at mid or somewhere to its left.
                // Keep mid in the candidate interval.
                right = mid;
            }
        }

        // One candidate remains, so this index is the minimum index.
        return left;
    }
}

public class Main {

    public static void main(String[] args) {
        // Run the exact example used in the diagram.
        int[] nums = { 9, 7, 5, 3, 4, 6, 8 };

        // Call the same findMinIndex method shown by the solution.
        Solution solution = new Solution();
        int result = solution.findMinIndex(nums);

        // nums[3] is 3, so the expected returned index is 3.
        System.out.println(result);
    }
}
Time & Space Complexity

The time complexity is O(log n). Each iteration reduces the remaining search interval to about half its previous size. In the diagram, the interval changes from [0, 6] to [0, 3], then [2, 3], then [3, 3]. The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. We only store left, right, and mid, so the extra memory does not grow with the input size.

Where it is used

This pattern is useful when data has one turning point and the direction near the middle tells us which side contains it. Similar binary-search reasoning can be used for valley-shaped measurements, peak-or-valley problems, and other ordered data where one half of the remaining search area can be safely discarded after each comparison.

Why Interviewers Ask This

This problem tests whether a candidate recognizes a binary-search pattern from the decreasing-then-increasing shape. It also checks whether they can maintain an inclusive interval, reason correctly about nums[mid] and nums[mid + 1], keep the possible minimum when moving a boundary, avoid an infinite loop, distinguish an index from a value, write a safe midpoint calculation in Java, and explain why the solution uses O(log n) time and O(1) extra space.

Common interview mistakes

A common mistake is returning the minimum value instead of its index. Another is setting right = mid - 1 when nums[mid] <= nums[mid + 1]. That can remove mid even though mid itself may be the minimum. Setting left = mid instead of mid + 1 on the decreasing side can stop the interval from shrinking. Using a loop condition such as left <= right does not match this binary-search invariant and can make nums[mid + 1] unsafe. Another mistake is claiming O(n) time even though the search interval is halved each iteration.

Interview tip

State the invariant before writing the loop: the minimum index always remains inside the inclusive interval [left, right]. Then justify each boundary update by explaining why the discarded half cannot contain the minimum.

Interviewer may ask next
What changes if equal neighboring values are allowed?

The current binary-search decision depends on nums[mid] and nums[mid + 1] showing a clear direction. If equal values are allowed away from the minimum, equality may not tell us whether the true minimum is to the left or right. We would need an additional rule or stronger input guarantee. One safe approach may have to shrink an ambiguous boundary instead of discarding half immediately. That can reduce the worst-case time from O(log n) to O(n), while the auxiliary space can remain O(1). The tradeoff is supporting ambiguous duplicate values at the cost of weaker search progress.

How would you return the minimum value instead of its index?

The binary-search logic stays the same. We still narrow [left, right] until left equals right. Instead of returning left, return nums[left]. Correctness is unchanged because left is the minimum index when the loop ends. The time complexity remains O(log n), and the auxiliary space remains O(1). Only the output contract changes.

9. Check whether a word is an ambigram.CodingEasyGoogle

Question Details

Check whether a word reads the same upside down, using the mapping described in the source example.

Short Interview Answer (30-60 seconds)

I would use a fixed hash map for the valid upside-down character pairs and two pointers, one at each end of the word. For each mirrored pair, I map the left character and compare the mapped result with the right character. If the character is unsupported or the pair does not match, I return false immediately. Otherwise, I move both pointers inward. If every check passes, I return true. This takes O(n) expected time and O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input is one word. We need to decide whether that word still reads the same after a 180-degree rotation. Some supported characters change into another character when rotated. For example, "w" becomes "m", while "s" stays "s". We compare matching positions from the two ends of the word. A fixed character mapping tells us what each left character becomes after rotation. If every mirrored pair follows this mapping, the result is true. If any pair fails, the result is false.

Useful Questions to Ask the Interviewer
  1. Should I use exactly the supplied upside-down character mapping?
  2. Should an unsupported character make the result false?
  3. Should an empty string be considered valid if the API allows it?
Check whether a word is an ambigram. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a string. The output is a boolean value. We return true when rotating the word 180 degrees produces the same word. Otherwise, we return false.

The verified example is "swims". Its expected result is true.

2. Choose the algorithm and data structure

I use a fixed hash map. Each key is a supported character. Its value is that character's upside-down counterpart. For example, "w" maps to "m", "m" maps to "w", and "s" maps to "s".

I also use two pointers. The left pointer starts at index 0. The right pointer starts at the last index. A 180-degree rotation reverses position order, so the mapped left character must equal the character at the mirrored right position.

The invariant is: every outer pair already checked satisfies rotate[leftChar] = rightChar.

3. Initialize the state

For "swims", the indices are 0, 1, 2, 3, 4 and the characters are s, w, i, m, s.

We start with left = 0 and right = 4. The fixed hash map already contains the supported rotation pairs. We continue while left <= right. Using <= is important because an odd-length word has a center character that must also be checked.

4. Walk through the example

Step 1: left = 0 and right = 4. The characters are s and s. The lookup gives rotate[s] = s. The mapped character equals the right character, so the check passes. We move inward to left = 1 and right = 3.

Step 2: left = 1 and right = 3. The characters are w and m. The lookup gives rotate[w] = m. The mapped character equals the right character, so the check passes. We move inward to left = 2 and right = 2.

Step 3: both pointers are at index 2. The character is i. The lookup gives rotate[i] = i. The center character maps to itself, so the check passes. We move to left = 3 and right = 1.

Now left > right, so processing stops. Exactly three mirror checks were processed. All three passed. Therefore, "swims" is an ambigram and we return true.

5. Explain why the result is correct

Before each iteration, every pair outside the current pointers has already passed the rotation rule. During the iteration, we verify the current mirrored pair. If the left character is unsupported or its mapped value does not equal the right character, the whole word cannot satisfy the rule, so returning false immediately is correct. If the pointers cross after all checks pass, every required mirrored pair is valid. Therefore, returning true is correct.

6. Explain the Java implementation

The Java code first rejects null input. It then builds the fixed HashMap<Character, Character> containing the supported rotation pairs. Two integer pointers start at the two ends of the string. In each loop, the code reads the left and right characters. It checks whether the left character exists in the map and whether its mapped value equals the right character. A failure returns false immediately. A successful check moves both pointers inward. When left becomes greater than right, the method returns true.

7. Explain complexity and edge cases

We process each character pair at most once. HashMap lookup is O(1) on average, so the total expected time is O(n), where n is the word length. The rotation map contains a fixed number of entries, so the auxiliary space is O(1).

An unsupported character returns false. A mismatched mirrored pair returns false immediately. For an odd-length word, the center character must map to itself. An empty string returns true in this implementation because there are no pairs that violate the rule.

Key Insight / Why This Solution Works

The key insight is that a 180-degree rotation reverses the positions in the word. Because of this, each character on the left must rotate into the character at the matching position on the right. A fixed hash map stores character to upside-down counterpart. Two pointers compare mirrored positions from the outside toward the center. The invariant is that every pair already passed by the pointers satisfies rotate[leftChar] = rightChar. If one pair breaks this rule, the word cannot be an ambigram, so we return false immediately. If the pointers cross after all checks pass, every mirrored pair is valid and we return true.

Code
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static boolean isAmbigram(String word) {
        // Treat null as invalid input for this implementation.
        if (word == null) {
            return false;
        }

        // Store each supported character and the character it becomes
        // after a 180-degree rotation.
        Map<Character, Character> rotate = new HashMap<>();
        rotate.put('b', 'q');
        rotate.put('q', 'b');
        rotate.put('d', 'p');
        rotate.put('p', 'd');
        rotate.put('i', 'i');
        rotate.put('l', 'l');
        rotate.put('m', 'w');
        rotate.put('w', 'm');
        rotate.put('n', 'u');
        rotate.put('u', 'n');
        rotate.put('o', 'o');
        rotate.put('s', 's');
        rotate.put('x', 'x');
        rotate.put('z', 'z');

        // Start at both ends because rotation reverses the position order.
        int left = 0;
        int right = word.length() - 1;

        // Check each mirrored pair at most once.
        // The <= condition also checks the center character of an odd-length word.
        while (left <= right) {
            char leftChar = word.charAt(left);
            char rightChar = word.charAt(right);

            // The left character must be supported, and its rotated form
            // must exactly match the character at the mirrored right position.
            if (!rotate.containsKey(leftChar) || rotate.get(leftChar) != rightChar) {
                return false;
            }

            // This mirrored pair is valid, so move both pointers inward.
            left++;
            right--;
        }

        // The pointers crossed only after every required mirror check passed.
        return true;
    }

    public static void main(String[] args) {
        // Run the same verified example shown in the diagram.
        String word = "swims";
        boolean result = isAmbigram(word);

        // Expected output: true
        System.out.println(result);
    }
}
Time & Space Complexity

Let n be the number of characters in the word. We process each character pair at most once. Each Java HashMap lookup is O(1) on average, so the total expected time is O(n). The rotation map has a fixed number of entries and does not grow with n, so the auxiliary space is O(1). We use expected-time wording because HashMap operations are average O(1), not guaranteed O(1) in every possible case.

Where it is used

This pattern is useful when opposite positions in a string must follow a fixed relationship. It is similar to a palindrome check, but instead of requiring the two characters to be equal, we transform one character with a predefined mapping before comparing it with the mirrored character. The same pattern can help validate mirrored symbols, encoded character pairs, or other fixed transformation rules.

Why Interviewers Ask This

This problem tests whether you can recognize a mirrored two-pointer pattern and combine it with a fixed character mapping. The interviewer can see whether you choose a suitable data structure, define the mapping direction correctly, maintain a useful invariant, handle the center of an odd-length string, and reason about early return. It also tests clean Java implementation and whether you describe HashMap complexity correctly as average-case behavior.

Common interview mistakes

A common mistake is comparing the left and right characters directly instead of comparing the rotated left character with the right character. Another mistake is forgetting that some valid pairs use different characters, such as w and m. Candidates may also forget to reject an unsupported left character before using its mapped value. Using left < right instead of left <= right can skip the center character of an odd-length word. Another mistake is claiming guaranteed O(n) time instead of O(n) expected time when the analysis depends on average HashMap lookup.

Interview tip

State the invariant before coding: every pair outside the current left and right pointers has already satisfied rotate[leftChar] = rightChar. Then walk through "swims" using the three checks s to s, w to m, and i to i. This makes the pointer movement, early return, and final true result easy to explain.

Interviewer may ask next
Can we avoid building the rotation HashMap on every method call?

Yes. Because the mapping is fixed, we can create it once as a static final map and reuse it across calls. The two-pointer logic and invariant stay the same, so correctness does not change. The expected running time remains O(n), and the auxiliary space remains O(1) because the map has a fixed number of entries. The tradeoff is that the mapping becomes shared class-level state instead of local method state.

What happens if the word contains a character that is not in the rotation map?

The method returns false when that unsupported character is encountered as the left character of a mirrored check. A valid ambigram requires every checked character to have a known rotated counterpart that matches the mirrored right character. Returning immediately is correct and avoids extra work. The expected time remains O(n) in the longest scan, with possible earlier termination, and the auxiliary space remains O(1).

10. Check whether an ambigram word appears in a list of words.CodingEasyGoogle

Question Details

Given a word and a list of words, determine whether the ambigram version of the word is present in the list.

Short Interview Answer (30-60 seconds)

I would put all list words into a HashSet, then build the ambigram by reading the input word from right to left and applying the problem-defined character mapping. If any character has no mapping, I return false. After building the candidate, I check whether the HashSet contains it. For "mom", the candidate becomes "wow", so the result is true. The expected time is O(L + m), and the auxiliary space is O(k + m).

Detailed Explanation

See the Code while reading this explanation.

The input is one word and a list of words. We must build the word's 180-degree ambigram and check whether that new word appears in the list. The exact rotation rules must come from the problem. In this example, m becomes w and o stays o. Because rotation reverses character order, we read "mom" from right to left and build "wow". A HashSet lets us check the completed word without scanning the list again.

Useful Questions to Ask the Interviewer
  1. What exact character-to-character mappings define a valid 180-degree ambigram?
  2. Should a character with no defined mapping make the result false?
  3. Is matching case-sensitive?
Check whether an ambigram word appears in a list of words. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a String word and a list of Strings. The output is a boolean. We return true only when the complete ambigram version of word appears in the list. The diagram uses word = "mom" and words = ["java", "wow", "loop", "code"]. The expected result is true.

2. Choose the algorithm and data structure

First, copy the list words into a HashSet. Each set entry represents one complete word from the input list. The HashSet lets us test whether the final candidate is present without comparing it with every list word one by one. A StringBuilder stores the ambigram while we construct it.

3. Initialize the state

The HashSet contains "java", "wow", "loop", and "code". The StringBuilder candidate starts as "". Traversal begins at index 2, which is the last character of "mom". The invariant is that candidate always contains the correct ambigram for the suffix that has already been processed.

4. Walk through the example

At index 2, the current character is 'm'. The problem-defined mapping changes it to 'w'. The candidate changes from "" to "w" and processing continues.

At index 1, the current character is 'o'. Its mapping is 'o'. The candidate changes from "w" to "wo" and processing continues.

At index 0, the current character is 'm'. It maps to 'w'. The candidate changes from "wo" to "wow" and processing continues to the final membership check.

All characters are now processed. The code checks dictionary.contains("wow"). The HashSet contains "wow", so the method returns true.

5. Explain why the result is correct

Reading from right to left handles the order reversal caused by a 180-degree rotation. Every processed character is replaced by its problem-defined rotated character. Therefore, after the loop, candidate is exactly the ambigram for the entire input word. The final HashSet membership test is true exactly when that completed word appears in the supplied list.

6. Explain the Java implementation

The method builds a HashSet from the supplied list and creates an empty StringBuilder. It loops from word.length() - 1 down to 0. For each character, rotate returns the mapped character. If rotate returns null, the current character has no defined mapping, so the method returns false immediately. Otherwise, the mapped character is appended. After all characters are processed, contains checks whether the completed candidate is in the set.

7. Explain complexity and edge cases

Let k be the number of words in the list, L be the total number of characters across those words, and m be the input-word length. Building the HashSet takes expected O(L) time because the Strings must be hashed. Building and hashing the candidate takes O(m). Total expected time is O(L + m). Auxiliary space is O(k + m), excluding the existing input strings. Relevant cases are unsupported characters, duplicate words, self-mapping characters such as 'o' in this example, and an empty list.

Key Insight / Why This Solution Works

The key idea is to separate transformation from membership testing. Store every list word in a HashSet. Then construct the ambigram by traversing the input word from right to left because a 180-degree rotation reverses character order. Map each character using the rules supplied by the problem and append the mapped character to a StringBuilder. The invariant is that after each iteration, candidate is the correct ambigram for the suffix already processed. When construction finishes, one HashSet membership test decides whether that exact ambigram appears in the list.

Code
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Main {

    public static boolean containsAmbigram(String word, List<String> words) {
        // Store each complete list word in a HashSet so the completed
        // ambigram can be checked without scanning the whole list again.
        Set<String> dictionary = new HashSet<>(words);

        // This builder holds the ambigram produced from the processed suffix.
        StringBuilder candidate = new StringBuilder();

        // A 180-degree rotation reverses character order, so process
        // the original word from its last character to its first.
        for (int i = word.length() - 1; i >= 0; i--) {
            Character mapped = rotate(word.charAt(i));

            // If the problem defines no rotation for this character,
            // a complete valid ambigram cannot be formed.
            if (mapped == null) {
                return false;
            }

            // After this append, candidate is the correct ambigram
            // for the suffix processed so far.
            candidate.append(mapped);
        }

        // The full ambigram is now built. Return whether that exact
        // transformed word appears in the supplied list.
        return dictionary.contains(candidate.toString());
    }

    private static Character rotate(char ch) {
        // Illustrative mapping from the approved diagram.
        // Use the exact rotation rules defined by the problem.
        return switch (ch) {
            case 'm' -> 'w';
            case 'w' -> 'm';
            case 'o' -> 'o';
            default -> null;
        };
    }

    public static void main(String[] args) {
        // Run the exact example shown in the approved diagram.
        String word = "mom";
        List<String> words = List.of("java", "wow", "loop", "code");

        // "mom" becomes "wow", and "wow" is present in the list.
        boolean result = containsAmbigram(word, words);
        System.out.println(result); // true
    }
}
Time & Space Complexity

Let k be the number of words in the list, L be the total number of characters across all list words, and m be the input-word length. Building the HashSet takes expected O(L) time because each String must be hashed. Building the transformed word and hashing it for the final lookup takes O(m). Therefore, the expected total time is O(L + m). HashSet bucket lookup is O(1) on average after hashing. Auxiliary space is O(k + m), excluding the existing input strings.

Where it is used

This pattern is useful when a value must first be transformed into a standard form and then checked against a known collection. Similar ideas appear in validation systems, normalized identifier checks, dictionary-style lookup, and repeated membership tests where a HashSet avoids scanning the entire collection for each query.

Why Interviewers Ask This

This question checks whether you can turn a character-transformation rule into a precise algorithm. The interviewer can see whether you recognize that a 180-degree rotation reverses character order, choose a HashSet for membership testing, maintain a useful invariant, handle unsupported characters, write correct Java string logic, and explain hash-based complexity accurately instead of treating all String-related HashSet work as guaranteed constant time.

Common interview mistakes

One mistake is reading the word from left to right, which can build the rotated characters in the wrong order. Another is treating an illustrative character mapping as a universal ambigram rule instead of using the mapping defined by the problem. Candidates may also forget to return false for an unsupported character. Another mistake is scanning the list again after building the candidate instead of using the HashSet. Finally, claiming simple O(k) time ignores the cost of hashing the String contents.

Interview tip

State the invariant while coding: after each right-to-left iteration, the StringBuilder contains the correct ambigram for the suffix processed so far. This makes both the traversal direction and the final HashSet lookup easy to justify.

Interviewer may ask next
What happens if the input word contains a character that has no defined ambigram mapping?

The method returns false as soon as that character is processed because a complete valid ambigram cannot be formed. No later characters need to be processed. In the worst case, we still process at most m input characters. Including HashSet construction, the expected time remains O(L + m), and the auxiliary space remains O(k + m).

What would you change if the same word list were used for many ambigram queries?

Build the HashSet once and reuse it instead of rebuilding it for every query. The transformation logic and correctness stay the same. The one-time preprocessing cost is expected O(L) with O(k) set storage. Each later query takes O(m) expected time to build and hash its candidate and uses O(m) temporary space. The tradeoff is keeping the prebuilt HashSet in memory between queries.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.