35 Meta Java Developer Interview Questions & Answers

meta icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

1. How would you check whether a string is a palindrome?CodingEasyMeta

Question Details

Determine whether a string reads the same forward and backward, including basic edge cases.

Short Interview Answer (30-60 seconds)

I would use two pointers, one at the start of the string and one at the end. While the left pointer is before the right pointer, I compare the two characters. If they are different, I return false immediately. If they match, I move both pointers inward. If the pointers meet or cross without finding a mismatch, I return true. This works because every mirrored pair must match. The time complexity is O(n), and the auxiliary space complexity is O(1).

Detailed Explanation

See the Code while reading this explanation.

A palindrome is a string that reads the same from the beginning and the end. We need to return true when every character has the same character in its mirrored position. Otherwise, we return false. The two-pointer approach fits well because we can compare those mirrored characters directly. One pointer starts at the beginning and the other starts at the end. After a matching pair, both pointers move toward the middle. If any pair is different, we already know the string is not a palindrome and can stop immediately.

Useful Questions to Ask the Interviewer
  1. Should the comparison be case-sensitive and use every character exactly as it appears?
  2. Should an empty string and a one-character string be considered palindromes?
How would you check whether a string is a palindrome? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a string s. The output is a boolean. We return true if the string reads the same forward and backward. We return false if any mirrored pair of characters is different. The approved diagram uses exact character comparison, so the check is case-sensitive. For example, "Aa" returns false.

2. Choose the two-pointer approach

I use two integer indices called left and right. left starts at index

  1. right starts at s.length() -
  2. These pointers identify the next mirrored characters that still need to be checked. The central invariant is that every character outside the current [left, right] range has already matched its mirrored partner.
3. Initialize the state

For the diagram's example s = "racecar", the indices are 0 through 6 and the characters are r, a, c, e, c, a, r. I set left = 0 and right = 6. Traversal starts at both ends, so the first comparison is the first r against the last r.

4. Walk through the example

Step 1 starts with left = 0 and right = 6. We compare r with r. They match, so we move both pointers inward. The new state is left = 1 and right = 5, and processing continues.

Step 2 starts with left = 1 and right = 5. We compare a with a. They match, so we move inward again. The new state is left = 2 and right = 4, and processing continues.

Step 3 starts with left = 2 and right = 4. We compare c with c. They match, so the new state becomes left = 3 and right = 3.

At this point, left < right is false. No comparison is needed for the middle e because it is already at the same mirrored position. The loop stops and the method returns true.

5. Explain why the result is correct

Each successful comparison proves that one mirrored pair is equal. After that pair matches, moving both pointers inward is safe because that outer pair never needs to be checked again. If any pair is different, the string cannot read the same forward and backward, so returning false immediately is correct. If the pointers meet or cross without a mismatch, every required mirrored pair has matched, so the string is a palindrome.

6. Explain the Java implementation

The Java method initializes left and right at opposite ends of the string. The while loop runs only while left < right. Inside the loop, it compares s.charAt(left) with s.charAt(right). A mismatch returns false immediately. A match increments left and decrements right. When the pointers meet or cross, the loop finishes and the method returns true. The executable runner uses the exact diagram example, "racecar", and prints true.

7. Explain complexity and edge cases

The time complexity is O(n). We compare at most about half of the mirrored character pairs, which is still linear in the string length, and we can stop early on a mismatch. The auxiliary space complexity is O(1) because the algorithm stores only two integer indices. An empty string returns true because the loop never runs. A one-character string also returns true. An even-length palindrome such as "abba" returns true. Exact comparison is case-sensitive, so "Aa" returns false.

Key Insight / Why This Solution Works

The key insight is that a palindrome is defined by matching mirrored character pairs. We do not need to create a reversed copy of the string. Instead, left starts at the first character and right starts at the last character. If s.charAt(left) and s.charAt(right) differ, we immediately know the answer is false. If they match, both pointers move inward. The invariant is that all character pairs outside the current [left, right] range have already matched. When the pointers meet or cross without a mismatch, every required pair has matched, so the answer is true.

Code
public class Main {

    static class Solution {

        public boolean isPalindrome(String s) {
            // Start one pointer at the first character.
            int left = 0;

            // Start the other pointer at the last character.
            int right = s.length() - 1;

            // Compare mirrored character pairs until the pointers meet or cross.
            while (left < right) {
                // A mismatch proves that the string cannot be a palindrome.
                if (s.charAt(left) != s.charAt(right)) {
                    return false;
                }

                // This pair matched, so move both pointers to the next inner pair.
                left++;
                right--;
            }

            // Every required mirrored pair matched.
            return true;
        }
    }

    public static void main(String[] args) {
        // Run the exact example shown in the approved diagram.
        String s = "racecar";
        Solution solution = new Solution();

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

The time complexity is O(n), where n is the length of the string. The pointers move inward, so each mirrored character pair is checked at most once. The algorithm may also stop early when it finds a mismatch. The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. We only keep two integer indices, left and right, so the amount of extra memory does not grow with the input size.

Where it is used

The two-pointer pattern is useful when a problem can be checked from both ends at the same time. Palindrome checking is a direct example. The same pattern is also useful for comparing symmetric positions and for problems where moving inward from two boundaries reduces the remaining search area without needing extra storage.

Why Interviewers Ask This

This problem checks whether you can recognize the two-pointer pattern and turn it into simple, correct Java code. The interviewer can see whether you initialize the boundaries correctly, compare mirrored positions, move both pointers safely, and stop immediately on a mismatch. It also tests whether you can explain a useful invariant, handle basic cases such as empty and one-character strings, and give the correct O(n) time and O(1) auxiliary-space complexity.

Common interview mistakes

A common mistake is moving only one pointer after the characters match. Both pointers must move inward. Another mistake is continuing after a mismatch instead of returning false immediately. Candidates may also use left <= right and perform an unnecessary comparison of the middle character with itself. Another mistake is changing the input by lowercasing it or removing characters even though this solution uses exact character comparison. Building a reversed copy is correct, but it uses O(n) extra space instead of the O(1) auxiliary space used by the two-pointer approach.

Interview tip

State the invariant before you code: every mirrored pair outside the current left and right pointers has already matched. Then put the mismatch check before moving the pointers. This makes both the correctness reasoning and the early return easy to explain.

Interviewer may ask next
What would change if the comparison should ignore letter case and non-alphanumeric characters?

I would keep the same two-pointer structure. Before comparing, I would move left forward while it points to a character that should be ignored and move right backward for the same reason. Then I would compare the remaining characters using the required case normalization. The invariant is still that all relevant mirrored characters outside the current pointers have matched. The time complexity remains O(n) because each pointer moves across the string at most once. The auxiliary space remains O(1) if normalization is done character by character. The tradeoff is extra logic inside the loop.

Could you solve this by reversing the string instead?

Yes. I could create a reversed copy and compare it with the original string. That is correct because a palindrome is equal to its reverse. The time complexity would still be O(n), but the auxiliary space would become O(n) because the reversed copy grows with the string length. The two-pointer solution keeps O(n) time while using O(1) auxiliary space, so it matches the approved diagram's approach.

2. How would you solve Valid Palindrome II?CodingEasyMeta

Question Details

Given a string, determine whether it can become a palindrome after deleting at most one character.

Short Interview Answer (30-60 seconds)

I would use two pointers, one at each end of the string. While the characters match, I move both pointers inward. At the first mismatch, I try the only two valid choices: skip the left character or skip the right character. A helper checks whether the remaining range is a palindrome. If either check succeeds, I return true. This works in O(n) time and uses O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The problem gives us a string and asks whether it can become a palindrome after removing at most one character. A palindrome reads the same from left to right and right to left. We do not need to actually build a new string. We compare characters from both ends. If they match, we continue inward. At the first mismatch, there are only two useful choices: remove the left character or remove the right character. We check both remaining ranges and return true if either range is a palindrome.

Useful Questions to Ask the Interviewer
  1. Does deleting zero characters also count as valid? Yes, because the problem says at most one deletion.
  2. Should I treat the string exactly as given, including its character case? Yes, unless the interviewer gives a different rule.
How would you solve Valid Palindrome II? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is one string. The output is a boolean value. We return true when the string is already a palindrome or can become one after deleting one character. Otherwise, we return false.

The diagram uses the example s = "abca". The expected output is true.

2. Choose the two-pointer approach

I use two pointers. The left pointer starts at index 0. The right pointer starts at the last index.

The important idea is that, before the first mismatch, every pair that we already compared matches. This is the invariant. We keep moving toward the center while this remains true.

At the first mismatch, any valid one-deletion solution must delete either the character at left or the character at right. There is no third useful choice because all characters outside this pair already match.

3. Initialize the state

For s = "abca", the characters and indices are:

Index 0 = 'a' Index 1 = 'b' Index 2 = 'c' Index 3 = 'a'

We start with left = 0 and right = 3.

4. Walk through the example

Step 1: left = 0 and right = 3. We compare 'a' and 'a'. They match, so we move both pointers inward. The new state is left = 1 and right = 2.

Step 2: we compare 'b' and 'c'. They do not match. This is the first mismatch.

Now we test the two possible deletion choices. The Java expression first checks the skip-left range [2, 2]. That range contains only 'c'. A single character is a palindrome, so the helper returns true.

Because the first helper call succeeds, Java short-circuits the OR expression. The skip-right helper call for range [1, 1] is not executed in this run. The main method stops and returns true.

5. Explain why the result is correct

Before the mismatch, all outer character pairs already match. At the first mismatch, the only characters that can prevent the string from being a palindrome are the two mismatching characters. Since we may delete at most one character, a valid solution must skip either the left mismatching character or the right mismatching character. If either remaining range is a palindrome, the original string can be fixed with one deletion.

6. Explain the Java implementation

The main method keeps left and right pointers and compares characters while left < right. A matching pair moves both pointers inward. A mismatch immediately calls isPalindromeRange for the two possible remaining ranges using a short-circuit OR expression. The helper uses the same two-pointer idea inside the selected range. It returns false on any mismatch and true when the pointers meet or cross. If the main loop finishes without a mismatch, the original string is already a palindrome, so the method returns true.

7. Explain complexity and edge cases

The time complexity is O(n). The main scan moves inward through the string, and at most one mismatch causes palindrome checks over the remaining range. Even if both helper calls are needed, the total amount of work is still linear in the string length. The auxiliary space complexity is O(1) because the algorithm only stores a few integer variables and does not create another string.

Relevant edge cases are an empty string, one character, an already valid palindrome such as "racecar", a string such as "abc" that cannot be fixed with one deletion, and repeated characters such as "deeee".

Key Insight / Why This Solution Works

The key insight is to compare matching positions from the two ends of the string. As long as the characters match, both pointers safely move inward. The invariant is that every pair outside the current left and right pointers already matches. When the first mismatch appears, any valid solution using at most one deletion must remove either the left mismatching character or the right mismatching character. We therefore check those two remaining ranges with a palindrome helper. If either range is valid, the answer is true. No extra data structure is needed.

Code
public class Main {

    public static void main(String[] args) {
        String s = "abca";

        // Run the exact example used in the diagram.
        boolean result = validPalindrome(s);

        // Expected output: true
        System.out.println(result);
    }

    public static boolean validPalindrome(String s) {
        // Start one pointer at each end of the string.
        int left = 0;
        int right = s.length() - 1;

        // Compare matching positions while the pointers have not met.
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                // This is the first mismatch.
                // We may delete at most one character, so try the only two valid choices:
                // skip the left character or, if that fails, skip the right character.
                return (
                    isPalindromeRange(s, left + 1, right) || isPalindromeRange(s, left, right - 1)
                );
            }

            // The current pair matches, so move both pointers toward the center.
            left++;
            right--;
        }

        // No mismatch was found, so the original string is already a palindrome.
        return true;
    }

    private static boolean isPalindromeRange(String s, int left, int right) {
        // Check only the selected inclusive range using two pointers.
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                // A mismatch means this one-deletion choice cannot produce a palindrome.
                return false;
            }

            // The current pair matches, so continue toward the center.
            left++;
            right--;
        }

        // The whole selected range is a palindrome.
        return true;
    }
}
Time & Space Complexity

Let n be the length of the string. The time complexity is O(n). The main two-pointer scan moves inward through the string. At the first mismatch, the algorithm may check up to two remaining ranges, and each helper check is linear in the remaining length. The total work is still O(n). The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. We only store pointer variables and do not copy substrings or build another data structure.

Where it is used

This two-pointer pattern is useful when data can be compared from both ends, especially for palindrome checks and other symmetric string or array problems. It is also useful when we want to test a small allowed change, such as skipping one bad element, without copying the whole input.

Why Interviewers Ask This

This problem tests whether you recognize the two-pointer pattern and can maintain a simple invariant while scanning from both ends. It also tests whether you can reason carefully about the first mismatch and reduce many possible deletions to exactly two useful choices. Interviewers can evaluate early-return reasoning, short-circuit behavior, clean helper-method design, correct pointer movement, edge-case handling, and whether you can explain the O(n) time and O(1) auxiliary-space bounds accurately.

Common interview mistakes

A common mistake is allowing another deletion after the first mismatch. The problem allows at most one deletion total. Another mistake is moving only one pointer when the current characters already match. Both pointers should move inward after a match. Candidates may also check only one deletion choice instead of allowing both skip-left and skip-right possibilities. Another mistake is creating new substring objects unnecessarily, which loses the O(1) auxiliary-space benefit. Finally, do not describe work after an early return as though it was executed.

Interview tip

When you reach the first mismatch, say clearly: "Any valid one-deletion answer must remove either this left character or this right character." That sentence explains why checking exactly two remaining ranges is complete and correct.

Interviewer may ask next
What if the string may become a palindrome after deleting at most two characters?

At a mismatch, we now have to consider both deletion choices while tracking how many deletions remain. A recursive solution can use a state containing left, right, and the remaining deletion count. Memoization can store already solved states so they are not recomputed. For a general limit k, there can be O(n^2 * k) such states, giving O(n^2 * k) time and O(n^2 * k) memoization space in the worst case. The tradeoff is that supporting more deletions requires more state and memory than the O(1)-space one-deletion solution.

Can we return which character should be deleted instead of only true or false?

Yes. At the first mismatch, check the skip-left range and the skip-right range separately. If skipping left produces a palindrome, return the left index. Otherwise, if skipping right works, return the right index. If the original string is already a palindrome, return a special value such as -1 to mean no deletion is needed. The time remains O(n), and the auxiliary space remains O(1).

3. How would you find a random index of the maximum value in an array?CodingEasyMeta

Question Details

Return an index of the maximum value, choosing uniformly among all maximum-valued positions.

Short Interview Answer (30-60 seconds)

I would scan the array once and use reservoir sampling for the maximum values. I keep the current maximum, how many times that maximum has appeared, and one selected index. If I find a larger value, I reset all three values. If I find the same maximum again, I choose its index with probability 1 divided by the new count. This keeps every maximum index equally likely. The time complexity is O(n), and the auxiliary space is O(1).

Detailed Explanation

See the Code while reading this explanation.

The input is an integer array. I need to return an index, not the maximum value itself. If the largest value appears more than once, every index containing that value should have the same chance of being returned. I can do this while reading the array from left to right. I remember the largest value seen so far, how many times it has appeared, and one randomly selected index. This avoids storing every maximum index and still gives each maximum position an equal chance.

Useful Questions to Ask the Interviewer
  1. Can I assume the array is non-empty?
  2. Should every position containing the maximum value have exactly the same selection probability?
How would you find a random index of the maximum value in an array? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an int[]. The result must be an index. For the example nums = [5, 1, 7, 7, 3, 7], the maximum value is 7. It appears at indices 2, 3, and 5. Any of those indices is valid, but each one must have probability 1/3.

2. Choose reservoir sampling for the maximum positions

I use a small form of reservoir sampling. Reservoir sampling means I keep one random choice from a group without storing the whole group. I track maxValue, maxCount, and answerIndex. After processing the values seen so far, answerIndex is uniformly random among the seen indices whose value equals maxValue.

3. Initialize the state

The first value is 5, so I start with maxValue = 5, maxCount = 1, and answerIndex = 0. Traversal then begins at index 1.

4. Walk through the example

At index 1, the value is 1. Since 1 < 5, nothing changes. The state stays (5, 1, 0).

At index 2, the value is 7. Since 7 > 5, this is a new maximum. I set maxValue = 7, reset maxCount = 1, and set answerIndex = 2. The state becomes (7, 1, 2).

At index 3, the value is also 7. I increment maxCount to 2. The walkthrough uses rand.nextInt(2) = 1, so I keep index 2. The state becomes (7, 2, 2).

At index 4, the value is 3. Since 3 < 7, nothing changes. The state stays (7, 2, 2).

At index 5, the value is 7. I increment maxCount to 3. The walkthrough uses rand.nextInt(3) = 0, so I replace the selected index with 5. The final state is (7, 3, 5). This walkthrough therefore returns index 5.

5. Explain why the result is correct

When a larger value appears, every earlier smaller value becomes irrelevant, so resetting the selected index is correct. When the k-th occurrence of the current maximum appears, I select it with probability 1/k. This keeps every one of the k maximum positions equally likely. For the three maximum positions in the example, indices 2, 3, and 5 each have probability 1/3.

6. Explain the Java implementation

The loop starts at index 1 because index 0 initializes the state. A strictly larger number resets the maximum state. An equal maximum increments maxCount first. Then rand.nextInt(maxCount) == 0 decides whether the new index replaces the current sample. After all elements are processed, the method returns answerIndex.

7. Explain complexity and edge cases

The algorithm processes each array element at most once, so the time complexity is O(n). It stores only three integer state variables plus the random generator, so the auxiliary space is O(1). One element returns index 0. Negative values and zero work normally. If all values are equal, every index is still selected uniformly.

Key Insight / Why This Solution Works

The key idea is to combine maximum tracking with reservoir sampling. I do not need to store a list of every index containing the maximum. I keep only maxValue, maxCount, and answerIndex. The invariant is: after processing the values seen so far, answerIndex is uniformly random among the processed indices whose value equals the current maxValue. A larger value resets the reservoir because earlier smaller values cannot be final answers. For the k-th equal maximum, selecting the new index with probability 1/k keeps all k maximum indices equally likely.

Code
import java.util.Random;

public class Main {

    // Random is used by reservoir sampling to choose fairly among tied maxima.
    private final Random rand = new Random();

    public int randomIndexOfMax(int[] nums) {
        // A null or empty array has no valid index to return.
        if (nums == null || nums.length == 0) {
            throw new IllegalArgumentException("nums must be non-empty");
        }

        // Use the first element to initialize the current maximum state.
        int maxValue = nums[0];
        int maxCount = 1;
        int answerIndex = 0;

        // Process the remaining elements from left to right.
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > maxValue) {
                // A larger value makes all earlier smaller candidates ineligible.
                // Reset the reservoir so this index is the only current candidate.
                maxValue = nums[i];
                maxCount = 1;
                answerIndex = i;
            } else if (nums[i] == maxValue) {
                // Another maximum has appeared, so increase the candidate count first.
                maxCount++;

                // Select this new maximum with probability 1 / maxCount.
                // This keeps every maximum index seen so far equally likely.
                if (rand.nextInt(maxCount) == 0) {
                    answerIndex = i;
                }
            }
            // A value below maxValue cannot be a final maximum index, so no state changes.
        }

        // The sampled index points to one occurrence of the maximum value.
        return answerIndex;
    }

    public static void main(String[] args) {
        Main solution = new Main();

        // Run the exact input used in the approved diagram.
        int[] nums = { 5, 1, 7, 7, 3, 7 };

        // A valid run returns index 2, 3, or 5 because those positions contain 7.
        int result = solution.randomIndexOfMax(nums);
        System.out.println(result);
    }
}
Time & Space Complexity

The time complexity is O(n) because the algorithm goes through the array from left to right and processes each element at most once. If the array has n elements, the amount of work grows directly with n. The auxiliary space is O(1) because the algorithm keeps only a fixed amount of extra state: maxValue, maxCount, answerIndex, and a Random object. It does not create a list containing all maximum positions.

Where it is used

This pattern is useful when data is processed in one pass and we need one uniformly random choice without storing every candidate. For example, it can select one random record tied for the highest score while values arrive as a stream. The maximum tracking finds the best value seen so far, and reservoir sampling chooses fairly among positions tied for that maximum.

Why Interviewers Ask This

This question checks whether a candidate can combine normal maximum tracking with randomized selection. The interviewer can see whether the candidate distinguishes values from indices, handles duplicate maximum values correctly, maintains a clear invariant, and reasons about probability instead of simply choosing the first or last maximum. It also tests whether the candidate can write correct Java state updates, explain why reservoir sampling is fair, and give the correct O(n) time and O(1) auxiliary-space complexity.

Common interview mistakes

A common mistake is returning the maximum value 7 instead of an index such as 2, 3, or 5. Another mistake is replacing answerIndex on every tie, which would always favor the last maximum. Candidates may also call rand.nextInt(maxCount) before incrementing maxCount; that gives the wrong probabilities. Another mistake is failing to reset maxCount to 1 when a strictly larger maximum appears. Finally, storing every maximum index in a list works but loses the O(1) auxiliary-space benefit of this reservoir-sampling approach.

Interview tip

State the invariant before coding: after every processed element, answerIndex is uniformly random among the processed indices containing the current maximum. Then explain that a new maximum resets the reservoir, while the k-th equal maximum replaces the current sample with probability 1/k.

Interviewer may ask next
How would the solution change if the array were too large to store and the values arrived as a stream?

The core algorithm would not change. I would process each value as it arrives while keeping the current position, maxValue, maxCount, and answerIndex. A larger value resets the reservoir. An equal maximum joins it and is selected with probability 1/maxCount. The same invariant preserves correctness: the sampled index is uniform among maximum positions seen so far. The time remains O(n) for n streamed values, and the auxiliary space remains O(1). The main benefit is that the complete input does not need to be stored.

What if I needed to return all indices containing the maximum instead of one random index?

I would no longer need reservoir sampling. While scanning, I would keep the current maximum and a list of its indices. A larger value would clear the list and add only the new index. An equal maximum would append its index. This returns every maximum position correctly. The time complexity remains O(n). The auxiliary space becomes O(k), where k is the number of maximum-valued positions. The tradeoff is extra memory because all matching indices must be stored.

4. How would you solve Subarray Sum Equals K?CodingMediumMeta

Question Details

Count or detect subarrays whose sum equals a target value.

Short Interview Answer (30-60 seconds)

I would use a running prefix sum and a HashMap that stores each earlier prefix sum and how many times it has appeared. I start the map with 0 mapped to 1. For each number, I update the prefix sum, compute prefixSum - k, and add its stored frequency to the answer. Then I store the current prefix sum. This counts every valid contiguous subarray. The solution takes O(n) expected time and O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The problem asks us to count how many continuous parts of the array add up exactly to k. A continuous part means the numbers must stay next to each other in the original array. We do not need to return the parts themselves. We only return the total count. The main idea is to keep a running total while moving from left to right. We remember earlier running totals so we can quickly tell whether the numbers between an earlier position and the current position add up to k.

Useful Questions to Ask the Interviewer
  1. Should I return only the number of matching subarrays, not their indices or values?
  2. Can the array contain negative numbers and zeros?
  3. Can multiple different subarrays have the same sum k?
How would you solve Subarray Sum Equals K? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an integer array nums and an integer k. We must return the number of contiguous subarrays whose sum is exactly k. For the diagram example, nums = [1, 2, 1, 2, 1] and k = 3. The correct result is 4 because the valid index ranges are 0..1, 1..2, 2..3, and 3..4.

2. Choose the prefix-sum and hash-map approach

I keep a running value called prefixSum. It is the sum from the start of the array through the current position. I also keep a HashMap called prefixCount. Its key is an earlier prefix sum. Its value is the number of times that prefix sum has appeared. Before I store the current prefix sum, the map contains only prefix sums from earlier positions.

If the current prefix sum is currentPrefix, then I need an earlier prefix sum equal to currentPrefix - k. The difference between those two prefix sums is k. Each stored occurrence of that needed prefix sum represents one valid subarray ending at the current index.

3. Initialize the state

I start with prefixSum = 0 and count = 0. I also put 0 -> 1 into prefixCount. This initial entry represents the empty prefix before index 0. It lets the algorithm count a valid subarray that starts at index 0.

4. Walk through the example

At index 0, the value is 1. prefixSum becomes 1. needed = 1 - 3 = -2. The map is {0:1}, so there is no match. count stays 0. Then I store prefix sum 1, giving {0:1, 1:1}.

At index 1, the value is 2. prefixSum becomes 3. needed = 3 - 3 = 0. The map contains 0 once, so I add 1 to count. count becomes 1. This represents subarray 0..1. Then I store prefix sum 3, giving {0:1, 1:1, 3:1}.

At index 2, the value is 1. prefixSum becomes 4. needed = 4 - 3 = 1. Prefix sum 1 appeared once earlier, so count becomes 2. This represents subarray 1..2. Then I store prefix sum 4, giving {0:1, 1:1, 3:1, 4:1}.

At index 3, the value is 2. prefixSum becomes 6. needed = 6 - 3 = 3. Prefix sum 3 appeared once earlier, so count becomes 3. This represents subarray 2..3. Then I store prefix sum 6, giving {0:1, 1:1, 3:1, 4:1, 6:1}.

At index 4, the value is 1. prefixSum becomes 7. needed = 7 - 3 = 4. Prefix sum 4 appeared once earlier, so count becomes 4. This represents subarray 3..4. Then I store prefix sum 7, giving {0:1, 1:1, 3:1, 4:1, 6:1, 7:1}. After the loop, I return 4.

5. Explain why the result is correct

Before storing the current prefix sum, prefixCount contains frequencies of prefix sums from earlier positions only. If an earlier prefix sum equals currentPrefix - k, then currentPrefix minus that earlier sum is exactly k. Therefore the elements between those positions form a valid contiguous subarray. Every valid subarray is counted exactly when its ending index is processed.

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

The Java code keeps prefixSum and count as integers and uses HashMap<Integer, Integer> for prefix frequencies. For each number, it updates prefixSum, reads the frequency of prefixSum - k, adds that frequency to count, and only then updates the frequency of the current prefix sum. HashMap lookup and insertion are O(1) on average, so the overall expected time is O(n). The map can hold O(n) different prefix sums, so auxiliary space is O(n). Negative numbers, zeros, repeated values, multiple matching starts, and an empty array are handled naturally.

Key Insight / Why This Solution Works

The key insight is to turn each subarray-sum check into a difference between two prefix sums. If the current running sum is currentPrefix, then a subarray ending at the current index has sum k when an earlier prefix sum equals currentPrefix - k. The HashMap stores prefixSum -> frequency, not just whether a prefix sum exists, because the same prefix sum can appear more than once and each occurrence can represent a different valid starting point. The central invariant is that, before the current prefix sum is inserted, the map contains frequencies only for earlier prefix sums. This lets every valid subarray be counted exactly when its ending index is processed.

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

public class Main {

    public static int subarraySum(int[] nums, int k) {
        // Store each earlier prefix sum and how many times it has appeared.
        Map<Integer, Integer> prefixCount = new HashMap<>();

        // The empty prefix has sum 0 once. This lets us count a valid
        // subarray that starts at index 0.
        prefixCount.put(0, 1);

        // prefixSum is the running total through the current element.
        int prefixSum = 0;

        // count stores the total number of valid contiguous subarrays found.
        int count = 0;

        // Process the array from left to right.
        for (int num : nums) {
            // Add the current value to the running prefix sum.
            prefixSum += num;

            // An earlier prefix sum of prefixSum - k means the elements
            // after that earlier prefix through here have sum k.
            // Its frequency is the number of valid starts for this end.
            count += prefixCount.getOrDefault(prefixSum - k, 0);

            // Update the current prefix sum only after the lookup so the
            // lookup uses prefix sums from earlier positions only.
            prefixCount.put(prefixSum, prefixCount.getOrDefault(prefixSum, 0) + 1);
        }

        // Return the total number of matching contiguous subarrays.
        return count;
    }

    public static void main(String[] args) {
        // Use the exact example from the approved diagram.
        int[] nums = { 1, 2, 1, 2, 1 };
        int k = 3;

        // Valid ranges are 0..1, 1..2, 2..3, and 3..4.
        int result = subarraySum(nums, k);

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

Let n be the number of elements in nums. We move from left to right through the array once. Each HashMap lookup and insertion is O(1) on average in Java, with normal hashing and collision caveats. Therefore the overall expected time is O(n). The HashMap may store up to O(n) different prefix sums, so the auxiliary space is O(n). Auxiliary space means extra memory used by the algorithm.

Where it is used

This prefix-sum plus frequency-map pattern is useful when software needs to count contiguous ranges whose total matches a target. Examples include analyzing transaction sequences, event deltas, inventory changes, sensor readings, or other ordered numeric data where values may be positive, zero, or negative.

Why Interviewers Ask This

This problem tests whether you can recognize the prefix-sum pattern and combine it with a frequency HashMap. The interviewer is also checking whether you understand why the initial 0 -> 1 entry is needed, why frequencies matter, and why lookup happens before the current prefix sum is stored. It also tests handling of negative values and duplicates, writing correct Java map operations, maintaining a clear invariant, and describing expected O(n) time and O(n) auxiliary space accurately.

Common interview mistakes

A common mistake is forgetting the initial 0 -> 1 entry. Then a valid subarray that starts at index 0 may not be counted. Another mistake is storing the current prefix sum before checking prefixSum - k. In particular, when k is 0, that can incorrectly count the current prefix against itself. The correct order is lookup first, then update the map. Candidates also sometimes store only whether a prefix sum exists instead of its frequency. That fails when the same prefix sum appears multiple times. Another mistake is using a sliding window even though negative numbers can appear. Finally, do not claim guaranteed O(n) time. Java HashMap operations are O(1) on average, so the overall time is O(n) expected time.

Interview tip

State the invariant before coding: before I insert the current prefix sum, the map contains frequencies of prefix sums from earlier positions only. Then say the key equation out loud: earlierPrefix = currentPrefix - k. That makes the lookup order and the frequency counting easy to justify.

Interviewer may ask next
What changes if I need the actual subarrays instead of only the count?

The frequency map alone is not enough because it tells us how many matching earlier prefix sums exist but not where they occurred. I would store each prefix sum with a list of indices where it appeared. For a current index j, every stored index for currentPrefix - k gives one valid range ending at j. The prefix-sum correctness rule stays the same. Building and scanning the stored positions takes O(n + r) expected time, where r is the number of returned subarrays. The map and stored prefix positions use O(n) auxiliary space, while the returned output itself can require O(r) space. The tradeoff is extra memory to recover every range.

What is the worst-case behavior of the Java HashMap used by this solution?

The interview complexity is O(n) expected time because HashMap lookup and insertion are O(1) on average. That is not a guaranteed constant-time bound for every operation. Heavy hash collisions can make individual operations slower. Modern Java HashMap can use tree-based bins for sufficiently large collision buckets when its internal conditions are met, which improves lookup in those buckets compared with a simple linked list. The algorithm still uses O(n) auxiliary space. The main tradeoff is that the fast expected lookup depends on hash-table behavior.

5. How would you merge 3 sorted arrays?CodingMediumMeta

Question Details

Merge three sorted arrays into one sorted result while handling duplicates and order.

Short Interview Answer (30-60 seconds)

I would use three pointers, one for each sorted array, and one write index for the result. At each step, I compare the current available values and append the smallest one. Then I advance only the pointer for the array that supplied that value. This keeps duplicates and preserves sorted order. I stop when all three arrays are exhausted. The time complexity is O(nA + nB + nC), with O(1) auxiliary space beyond the output array.

Detailed Explanation

See the Code while reading this explanation.

The problem gives three arrays whose values are already sorted. We need to create one new sorted array that contains every value from all three inputs. Repeated values must stay in the result. The main idea is to look only at the next unused value from each array, choose the smallest available value, copy it to the result, and move only that array's pointer. Because each input is already sorted, we never need to move backward or sort the values again.

Useful Questions to Ask the Interviewer
  1. Should duplicate values be preserved in the merged result?
  2. Can any of the three input arrays be empty?
  3. Should the inputs remain unchanged, with the merged values written into a new array?
How would you merge 3 sorted arrays? diagram
How to Explain It in an Interview
1. Understand the input and required output

We have three individually sorted integer arrays. In the diagram, A is [1, 4], B is [1, 3, 5], and C is [2, 4]. We need one sorted result containing every occurrence from all three arrays. The expected result is [1, 1, 2, 3, 4, 4, 5]. These are values, not indices.

2. Choose the algorithm and state the invariant

I use three read pointers: i for A, j for B, and k for C. I also use r as the write position in the merged array. At every step, I choose the smallest current value among the non-exhausted arrays. The important invariant is that merged[0..r-1] is already sorted and contains the smallest values processed so far.

3. Initialize the state

The result array has size A.length + B.length + C.length, which is 7 for this example. We start with i = 0, j = 0, k = 0, and r = 0. The current heads are A[0] = 1, B[0] = 1, and C[0] = 2. No input value has been copied yet.

4. Walk through the example

Step 1: The heads are 1, 1, and 2. The comparison uses a deterministic <= tie rule, so A supplies the first 1. We write 1 and move i from 0 to 1. Result: [1].

Step 2: The heads are now 4, 1, and 2. B has the smallest value, 1. We write it and move j from 0 to 1. Result: [1, 1].

Step 3: The heads are 4, 3, and 2. C has the smallest value, 2. We write it and move k from 0 to 1. Result: [1, 1, 2].

Step 4: The heads are 4, 3, and 4. B has the smallest value, 3. We write it and move j from 1 to 2. Result: [1, 1, 2, 3].

Step 5: The heads are 4, 5, and 4. A and C both have 4. The <= rule chooses A first. We write 4 and move i from 1 to 2, so A is exhausted. Result: [1, 1, 2, 3, 4].

Step 6: Only B and C remain available, with heads 5 and 4. We write C's 4 and move k from 1 to 2. C is now exhausted. Result: [1, 1, 2, 3, 4, 4].

Step 7: Only B remains, with value 5. We write 5 and move j from 2 to 3. Now all three arrays are exhausted. Final result: [1, 1, 2, 3, 4, 4, 5].

5. Explain why the result is correct

Each array is sorted, so its current pointer always identifies its smallest unused value. Therefore, the smallest value among the three available heads is also the smallest value that has not yet been copied from any input. Appending that value keeps the result sorted. Moving only the pointer that supplied the value also makes sure every occurrence, including duplicates, is copied exactly once.

6. Explain the Java implementation

The Java code first creates the output array and initializes i, j, k, and r to zero. The loop continues while at least one input still has values. Bounds checks make exhausted arrays unavailable for comparison. The first condition chooses A when its current value is no larger than the other available heads. The second condition does the same for B. Otherwise C supplies the next value. After writing a value, only the matching input pointer and the result index move.

7. Explain complexity and edge cases

If the array lengths are nA, nB, and nC, every input element is copied exactly once. The time complexity is O(nA + nB + nC). The algorithm uses only the four pointer variables in addition to the required result array, so auxiliary space is O(1). The code also handles one empty array, all empty arrays, duplicate values, and negative values because the same bounds checks and comparisons still work.

Key Insight / Why This Solution Works

The key insight is that because all three inputs are already sorted, the next value in the merged result must be the smallest current head among the non-exhausted arrays. We therefore keep one pointer per input and compare only those three current values. After choosing the smallest value, we append it and advance only the pointer that supplied it. The invariant is: merged[0..r-1] is sorted and contains the smallest values processed so far. This gives a direct linear merge without sorting the combined data again.

Code
import java.util.Arrays;

public class Main {

    static class Solution {

        public int[] mergeThreeSortedArrays(int[] a, int[] b, int[] c) {
            // Allocate the required output array for every value from all three inputs.
            int[] merged = new int[a.length + b.length + c.length];

            // i, j, and k point to the next unused value in each input.
            // r points to the next position to write in the output.
            int i = 0,
                j = 0,
                k = 0,
                r = 0;

            // Continue until all three arrays have been completely consumed.
            while (i < a.length || j < b.length || k < c.length) {
                // Choose A when it is available and no larger than either other available head.
                // The <= comparisons give A priority on ties, matching the walkthrough.
                if (
                    i < a.length &&
                    (j >= b.length || a[i] <= b[j]) &&
                    (k >= c.length || a[i] <= c[k])
                ) {
                    // Copy A's current value, then advance only A and the result index.
                    merged[r++] = a[i++];
                } else if (
                    j < b.length &&
                    (i >= a.length || b[j] <= a[i]) &&
                    (k >= c.length || b[j] <= c[k])
                ) {
                    // A was not selected. Copy B's smallest available head and advance B.
                    merged[r++] = b[j++];
                } else {
                    // If neither A nor B is selected, C is the smallest available head.
                    // Copy it and advance only C and the result index.
                    merged[r++] = c[k++];
                }
            }

            // All input occurrences have been copied exactly once in sorted order.
            return merged;
        }
    }

    public static void main(String[] args) {
        // Run the exact example used in the approved diagram.
        int[] a = { 1, 4 };
        int[] b = { 1, 3, 5 };
        int[] c = { 2, 4 };

        Solution solution = new Solution();
        int[] result = solution.mergeThreeSortedArrays(a, b, c);

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

Let nA, nB, and nC be the lengths of the three arrays. Each value is read and copied exactly once, so the total time is O(nA + nB + nC). The algorithm keeps only i, j, k, and r as extra working state, so auxiliary space is O(1). The returned merged array needs O(nA + nB + nC) space, but that is required output space rather than extra working memory.

Where it is used

This pattern is useful when several data sources are already sorted and must be combined without sorting everything again. Examples include merging sorted database results, combining ordered event streams, and merging sorted runs inside larger sorting or data-processing systems.

Why Interviewers Ask This

This question tests whether you can use the sorted-input property instead of doing unnecessary work. The interviewer can evaluate pointer management, handling of exhausted arrays, duplicate preservation, tie behavior, loop correctness, and whether your implementation matches your reasoning. It also checks whether you can explain why choosing the smallest current head is safe and give the correct O(nA + nB + nC) time and O(1) auxiliary-space analysis.

Common interview mistakes

A common mistake is advancing more than one input pointer when two head values are equal. That can lose duplicate values. Another mistake is comparing an array after its pointer has reached the end, which can cause an index error. Candidates may also move the wrong pointer after selecting a value, which breaks the invariant. Another mistake is forgetting that the output size must be the sum of all three input lengths. Finally, do not claim the algorithm uses O(1) total space without explaining that the result array itself still requires linear output space.

Interview tip

State the invariant before coding: the output prefix is always sorted, and each input pointer marks that array's smallest unused value. Then your comparison and pointer movements are much easier to justify.

Interviewer may ask next
What happens if one or even all three input arrays are empty?

The same algorithm still works. Each comparison first checks whether that array's pointer is inside its bounds. An empty or exhausted array is simply unavailable for selection. If one array is empty, the remaining arrays are merged normally. If all three are empty, the loop never runs and the method returns an empty array. The time remains O(nA + nB + nC), and auxiliary space remains O(1) beyond the output.

How would you change this if there were k sorted arrays instead of exactly three?

For k sorted arrays, the direct extension of this idea would require checking up to k current heads for every output value. A better scalable approach is a min-heap that stores the current head from each non-empty array together with its array and position. Pop the smallest item, append it, then push the next value from the same array. For N total values, this takes O(N log k) time and O(k) auxiliary space. The tradeoff is extra heap memory and logarithmic work per output value.

6. How would you solve Binary Tree Vertical Order Traversal?CodingMediumMeta

Question Details

Traverse a binary tree column by column from left to right.

Short Interview Answer (30-60 seconds)

I would use breadth-first search with a queue. Each queue entry stores a tree node and its vertical column number. I start the root at column 0, move left children to column minus 1, and right children to column plus 1. A hash map stores the values for each column in BFS order. I also track the leftmost and rightmost columns. Then I read those columns from left to right. This takes O(n) expected time and O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The task is to place tree values into vertical groups. The root starts in the middle. Every move to a left child moves one group left. Every move to a right child moves one group right. Values in the same group must stay from top to bottom. If two values are at the same height and in the same group, the left one must appear first. I process the tree level by level, from left to right, because that naturally keeps this required order while I collect values for each group.

Useful Questions to Ask the Interviewer
  1. If two nodes have the same row and column, should their left-to-right tree order be preserved?
  2. Should an empty tree return an empty list?
How would you solve Binary Tree Vertical Order Traversal? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a binary tree. The output is a list of vertical columns from left to right. Each column contains node values from top to bottom. The example tree is [3, 9, 8, 4, 0, 1, 7], with 3 as the root, 9 and 8 as its children, 4 and 0 under 9, and 1 and 7 under 8. The required result is [[4], [9], [3, 0, 1], [8], [7]].

2. Choose BFS and store each node with its column

I use breadth-first search, or BFS. A queue performs the BFS. Each queue item stores a node reference and its column number. I also use a HashMap<Integer, List<Integer>>. Its key is a column number. Its value is the list of node values already visited in that column. The central invariant is that columnTable[c] stores the visited values for column c in BFS order.

BFS fits the ordering rule because it processes higher rows before lower rows. I enqueue the left child before the right child. Therefore, when two nodes share both a row and a column, their left-to-right tree order is preserved.

3. Initialize the state

The root starts at column 0. The initial queue is [(3, 0)]. The column map is empty. Both minCol and maxCol start at 0. These values track the leftmost and rightmost columns reached during the traversal.

4. Walk through the example

Step 1: Dequeue 3@0. Add 3 to column 0. Enqueue 9@-1 first and 8@1 second. The queue becomes [(9,-1),(8,1)]. The map is {0:[3]}. The current range is -1 through 1.

Step 2: Dequeue 9@-1. Add 9 to column -1. Enqueue 4@-2 and 0@0. The queue becomes [(8,1),(4,-2),(0,0)]. The map is {-1:[9], 0:[3]}. The leftmost column becomes -2.

Step 3: Dequeue 8@1. Add 8 to column 1. Enqueue 1@0 and 7@2. The queue becomes [(4,-2),(0,0),(1,0),(7,2)]. The map is {-1:[9], 0:[3], 1:[8]}. The rightmost column becomes 2.

Step 4: Dequeue 4@-2. Add 4 to column -2. It has no children. The queue becomes [(0,0),(1,0),(7,2)]. The map is {-2:[4], -1:[9], 0:[3], 1:[8]}.

Step 5: Dequeue 0@0. Append 0 to column 0. The queue becomes [(1,0),(7,2)]. Column 0 is now [3,0].

Step 6: Dequeue 1@0. Append 1 to column 0. The queue becomes [(7,2)]. Column 0 is now [3,0,1].

Step 7: Dequeue 7@2. Add 7 to column 2. The queue becomes empty. The final map is {-2:[4], -1:[9], 0:[3,0,1], 1:[8], 2:[7]}.

Now minCol is -2 and maxCol is 2. Reading columns -2, -1, 0, 1, and 2 gives [[4], [9], [3, 0, 1], [8], [7]].

5. Explain why the result is correct

Every queue entry carries the exact column of its node. Therefore, every value is added to the correct column. BFS processes nodes level by level, so higher nodes are appended before lower nodes. Because left children are enqueued before right children, nodes tied on both row and column keep their required left-to-right order. Every node is dequeued once, so every node appears exactly once.

6. Explain the Java implementation

The Java code uses a small NodeCol class to store a TreeNode and its column. An ArrayDeque is the BFS queue. A HashMap stores each column and its list of values. computeIfAbsent creates a list when a column is first seen. A left child receives col - 1. A right child receives col + 1. The code updates minCol and maxCol as children are added. After BFS finishes, it reads every column from minCol through maxCol.

7. Explain complexity and edge cases

Each node enters the queue once and leaves the queue once. Hash map lookup and insertion are O(1) on average, so the total expected time is O(n). The queue and column storage require O(n) auxiliary space. Important cases are an empty tree, a single-node tree, a left- or right-skewed tree that creates many columns, and several nodes that belong to the same column.

Key Insight / Why This Solution Works

The key idea is to combine breadth-first search with a column number for every node. The root is column 0. Following a left edge changes the column by -1. Following a right edge changes it by +1. A hash map stores column number -> list of visited node values. The central invariant is that columnTable[c] contains the values already visited for column c in BFS order. This works because BFS processes higher rows before lower rows, and adding the left child before the right child preserves left-to-right order for nodes tied on the same row and column. Tracking minCol and maxCol lets us build the final answer directly from the leftmost column to the rightmost column.

Code
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;

public class Main {

    // Basic binary tree node used by the traversal and the runnable example.
    static final class TreeNode {

        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int val) {
            this.val = val;
        }
    }

    // One BFS state: the current node and its exact vertical column.
    static final class NodeCol {

        TreeNode node;
        int col;

        NodeCol(TreeNode node, int col) {
            this.node = node;
            this.col = col;
        }
    }

    static List<List<Integer>> verticalOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();

        // An empty tree has no vertical columns, so return an empty result.
        if (root == null) {
            return result;
        }

        // Map each column number to values appended in BFS order.
        Map<Integer, List<Integer>> columnTable = new HashMap<>();

        // Start BFS at the root in column 0.
        Queue<NodeCol> queue = new ArrayDeque<>();
        queue.offer(new NodeCol(root, 0));

        // Track the smallest and largest column reached during traversal.
        int minCol = 0;
        int maxCol = 0;

        while (!queue.isEmpty()) {
            // Remove the next node in BFS order together with its column.
            NodeCol current = queue.poll();
            TreeNode node = current.node;
            int col = current.col;

            // Append this node value to the list for its exact column.
            columnTable.computeIfAbsent(col, key -> new ArrayList<>()).add(node.val);

            // The left child is one column to the left.
            // Queue it before the right child to preserve left-to-right tie order.
            if (node.left != null) {
                queue.offer(new NodeCol(node.left, col - 1));
                minCol = Math.min(minCol, col - 1);
            }

            // The right child is one column to the right.
            if (node.right != null) {
                queue.offer(new NodeCol(node.right, col + 1));
                maxCol = Math.max(maxCol, col + 1);
            }
        }

        // Read every column from the leftmost one to the rightmost one.
        for (int col = minCol; col <= maxCol; col++) {
            result.add(columnTable.get(col));
        }

        return result;
    }

    public static void main(String[] args) {
        // Build the exact example tree from the approved diagram:
        //         3
        //       /   \
        //      9     8
        //     / \   / \
        //    4   0 1   7
        TreeNode root = new TreeNode(3);
        root.left = new TreeNode(9);
        root.right = new TreeNode(8);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(0);
        root.right.left = new TreeNode(1);
        root.right.right = new TreeNode(7);

        // The expected output is [[4], [9], [3, 0, 1], [8], [7]].
        System.out.println(verticalOrder(root));
    }
}
Time & Space Complexity

Let n be the number of nodes. Each node is added to the queue once and removed once. Each node is also appended to exactly one column list. Java HashMap lookup and insertion are O(1) on average, so the total expected time is O(n). Building the final result visits the stored column values once more, which is still O(n) overall. Auxiliary space is O(n) because the queue and the stored column lists can contain information proportional to the number of nodes.

Where it is used

This pattern is useful when tree data must be grouped by position while keeping level order. Similar ideas can be used in tree visualization, hierarchy layout, reporting systems, and other tree problems where each node receives a coordinate or level during BFS.

Why Interviewers Ask This

This problem tests whether you can combine tree traversal with extra positional state. The interviewer can see if you recognize when BFS naturally preserves an ordering requirement, choose suitable queue and map structures, and maintain a useful invariant. It also tests careful handling of left and right column changes, correct Java collection usage, edge cases such as empty and skewed trees, and whether you describe expected HashMap complexity accurately instead of claiming a guaranteed constant-time hash operation.

Common interview mistakes

A common mistake is using plain DFS without recording enough row and ordering information, which can place nodes in the wrong order inside a column. Another mistake is putting only node values in the queue and losing the column number associated with each node. Candidates may also reverse the column direction by adding 1 for a left child or subtracting 1 for a right child. Enqueuing the right child before the left child can break the required tie order. Finally, do not claim guaranteed O(n) time when the implementation relies on average O(1) HashMap operations.

Interview tip

State the invariant early: each queue entry knows its exact column, and each map list receives values in BFS order. Then use column 0 becoming [3, 0, 1] to show why level-order traversal and left-before-right queue order matter.

Interviewer may ask next
What changes if nodes in the same row and column must be sorted by value?

BFS order alone would no longer satisfy the stronger tie rule. I would also track each node's row and collect enough information to order entries by column, then row, then value. Sorting the collected entries is a straightforward way to preserve the new rule. The time complexity becomes O(n log n), and the extra space remains O(n). The tradeoff is extra sorting work in exchange for the stronger ordering requirement.

Could you use a TreeMap instead of tracking minCol and maxCol?

Yes. A TreeMap<Integer, List<Integer>> would keep the column keys sorted automatically. The BFS logic and the left-before-right queue order would stay the same, so values inside each column would keep the same required order. After traversal, I could read the TreeMap values from the smallest key to the largest. The time complexity would become O(n log n) because each TreeMap access or insertion costs O(log n), while auxiliary space would remain O(n). The tradeoff is simpler ordered output code but slower map operations than the expected O(1) HashMap approach.

7. How would you solve a graph traversal problem to return the integer for the longest path?CodingHardMeta

Question Details

Given a graph traversal prompt, determine the length of the longest path required by the interview.

Short Interview Answer (30-60 seconds)

I would first clarify that the graph is a directed acyclic graph, because the longest-path problem changes when cycles are allowed. For a DAG, I build an adjacency list and use DFS with memoization. memo[u] stores the longest path length starting at node u. Each node computes the best value from its outgoing neighbors only once, and later calls reuse it. I take the maximum over all starting nodes. The time is O(V + E), and the auxiliary space is O(V), including memo and recursion.

Detailed Explanation

See the Code while reading this explanation.

The problem asks for one integer: the largest number of edges in any valid path. The diagram uses a directed acyclic graph, so there are no cycles. I can start from every node, follow its outgoing edges, and remember the best remaining path from each node. Reusing those saved answers avoids solving the same suffix again. For the shown graph with six nodes and seven directed edges, the longest path contains 3 edges, so the required returned integer is 3.

Useful Questions to Ask the Interviewer
  1. Can I assume the graph is directed and acyclic?
  2. Is the path length measured in edges, as shown here?
  3. If cycles are allowed, do you mean the longest simple path with no repeated nodes?
  4. Can the graph be disconnected?
How would you solve a graph traversal problem to return the integer for the longest path? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is n nodes and a list of directed edges. The output is one integer: the maximum number of edges in a path. In the diagram, n = 6 and edges = [[0,1],[0,2],[1,3],[2,3],[2,4],[3,5],[4,5]]. The expected answer is 3. Two valid longest paths are 0 → 1 → 3 → 5 and 0 → 2 → 4 → 5.

2. Choose DFS with memoization

I build an adjacency list. For each node u, memo[u] means the longest path length that starts at u. DFS explores the outgoing neighbors of u. For every neighbor v, the candidate length is 1 + dfs(v). I keep the largest candidate. If memo[u] is already known, I return it immediately. This is safe because the graph is acyclic.

3. Initialize the state

The adjacency list is 0:[1,2], 1:[3], 2:[3,4], 3:[5], 4:[5], and 5:[]. The memo array starts as [-1,-1,-1,-1,-1,-1]. A value of -1 means that node has not been solved yet. A sink node has no outgoing edges, so its longest path length is 0.

4. Walk through the verified example

Start with dfs(0). It first follows node 1, then node 3, then node 5. Node 5 is a sink, so memo[5] becomes 0. Returning to node 3 gives 1 + memo[5] = 1, so memo[3] becomes 1. Returning to node 1 gives 1 + memo[3] = 2, so memo[1] becomes 2.

Next dfs(0) explores node 2. When node 2 checks node 3, memo[3] is already 1, so that branch gives candidate 2. Node 2 then calls dfs(4). Node 4 reaches node 5, whose memo value is already 0, so memo[4] becomes 1. Node 2 now compares the two candidates, 2 through node 3 and 2 through node 4, so memo[2] becomes 2.

Finally, node 0 compares 1 + memo[1] = 3 and 1 + memo[2] = 3. Therefore memo[0] becomes 3. The outer loop still checks the remaining starting nodes, but their memo values are already solved. The global answer stays 3.

5. Explain why the result is correct

After dfs(u) returns, memo[u] is the correct longest path length starting at u. A sink returns 0. For any other node, every valid path from u must first take one outgoing edge to some neighbor v. The best path from u is therefore the maximum of 1 + dfs(v) over all outgoing neighbors. Because the graph is acyclic, recursion always reaches a sink, and memoization stores each solved suffix once.

6. Explain the Java implementation

The Java code first builds the adjacency list. It fills memo with -1. Then it calls dfs from every node and keeps the largest returned value. The helper returns a cached value when available. Otherwise, it checks every outgoing neighbor, computes 1 + dfs(next), stores the maximum in memo[node], and returns it. This is the same state and traversal used in the diagram.

7. Explain complexity and edge cases

Each node is fully solved once, and each directed edge is examined when its source node is solved, so the time is O(V + E). The memo array uses O(V) extra space. The recursion stack can also use O(V) space in the worst case. Relevant cases are an isolated node, sink nodes, disconnected DAGs, and multiple different longest paths with the same maximum length. If arbitrary cycles are allowed, this DAG memoization solution does not directly solve the longest simple path problem.

Key Insight / Why This Solution Works

The key idea is to turn repeated graph traversal into reusable subproblems. Define memo[u] as the longest path length, in edges, starting at node u. For a sink, that value is 0. For any other node, memo[u] = max(1 + memo[v]) over every outgoing neighbor v. DFS computes these values naturally from the end of the graph back toward the start. The invariant is: after dfs(u) returns, memo[u] is final and correct. Memoization avoids recomputing shared suffixes such as nodes 3 and 5.

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

public class Main {

    public static int longestPathLength(int n, int[][] edges) {
        // Build a directed adjacency list. graph[u] contains every node v
        // for which there is an edge u -> v.
        List<List<Integer>> graph = new ArrayList<>();
        for (int node = 0; node < n; node++) {
            graph.add(new ArrayList<>());
        }

        // Add every directed edge to the adjacency list.
        for (int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
        }

        // memo[u] stores the longest path length, in edges, starting at u.
        // -1 means that the value for u has not been computed yet.
        int[] memo = new int[n];
        Arrays.fill(memo, -1);

        int answer = 0;

        // Try every node as a starting node because the DAG may be disconnected.
        for (int node = 0; node < n; node++) {
            answer = Math.max(answer, dfs(node, graph, memo));
        }

        return answer;
    }

    private static int dfs(int node, List<List<Integer>> graph, int[] memo) {
        // Reuse a solved suffix so shared subproblems are not recomputed.
        if (memo[node] != -1) {
            return memo[node];
        }

        // A sink has no outgoing edges, so its longest path length is 0.
        int best = 0;

        // Every path from this node must choose one outgoing edge first.
        // Add 1 for that edge, then append the best path from the neighbor.
        for (int next : graph.get(node)) {
            int candidate = 1 + dfs(next, graph, memo);
            best = Math.max(best, candidate);
        }

        // Cache the final answer for this node before returning it.
        memo[node] = best;
        return best;
    }

    public static void main(String[] args) {
        // Exact example from the audited diagram.
        int n = 6;
        int[][] edges = { { 0, 1 }, { 0, 2 }, { 1, 3 }, { 2, 3 }, { 2, 4 }, { 3, 5 }, { 4, 5 } };

        // Expected output: 3
        System.out.println(longestPathLength(n, edges));
    }
}
Time & Space Complexity

Let V be the number of nodes and E be the number of directed edges. Each node is fully computed once because its answer is stored in memo. During that computation, its outgoing edges are checked once. So the total time is O(V + E). The memo array uses O(V) extra memory. The recursion stack can also grow to O(V) in the worst case, such as one long chain. Therefore the auxiliary space is O(V).

Where it is used

This pattern is useful when a directed acyclic graph has repeated subproblems. Examples include dependency graphs, build pipelines, course prerequisite chains, workflow stages, and scheduling graphs where you want the longest chain or critical depth. DFS with memoization works well when each node’s answer depends on answers from nodes reachable after it.

Why Interviewers Ask This

This problem checks whether you can recognize repeated subproblems inside graph traversal. The interviewer can see whether you define a clear DFS return value, build the adjacency list correctly, use memoization safely on a DAG, handle disconnected components, and reason about sinks. It also tests whether you can connect the recurrence to the code and explain why each node and edge is processed only once after memoization.

Common interview mistakes

A common mistake is applying this memoized DFS directly to a graph with cycles. The recurrence is valid here because the graph is a DAG. Another mistake is forgetting to try every node as a starting node, which can miss the answer in a disconnected graph. Candidates may also forget that a sink returns 0 because path length is counted in edges. Finally, do not recompute an already solved node. Return memo[node] immediately when it is available.

Interview tip

Define memo[u] out loud before coding: “memo[u] is the longest path length starting at u.” Then explain the recurrence as “one edge to a neighbor plus that neighbor’s best suffix.” This makes the DFS, base case, and O(V + E) reasoning much easier to follow.

Interviewer may ask next
What changes if the graph can contain cycles?

I would first clarify what “longest path” means. If repeated nodes are allowed, a reachable cycle can make the path length unbounded because you can keep going around the cycle. If the interviewer means the longest simple path, where a node cannot repeat, this DAG memoization recurrence no longer works because the answer depends on which nodes are already in the current path. In a general directed graph, longest simple path is NP-hard. A backtracking solution can track path-local visited nodes, but worst-case time is exponential and space is O(V) for the current path plus recursion.

How would you return one actual longest path instead of only its length?

Keep the same DFS and memo values, but also store nextNode[u], the neighbor that produced the best value for u. When 1 + dfs(v) improves best, set nextNode[u] = v. After all nodes are solved, remember the start node with the largest memo value and follow nextNode until there is no next node. The time remains O(V + E). The extra next-node array uses O(V) space, so total auxiliary space remains O(V).

8. How would you solve Valid Word Abbreviation?CodingEasyMeta

Question Details

Validate whether an abbreviation correctly represents a word.

Short Interview Answer (30-60 seconds)

I would use two pointers, one for the word and one for the abbreviation. I move through both from left to right. When the abbreviation has a letter, it must match the current word character. When it has digits, I parse all consecutive digits as one skip count and move the word pointer forward by that amount. A number cannot start with zero. The abbreviation is valid only if both pointers finish together. The time is O(n + m) and the auxiliary space is O(1).

Detailed Explanation

See the Code while reading this explanation.

The input has a full word and an abbreviation string. The abbreviation can contain letters and numbers. A letter must be the same as the matching letter in the word. A number tells us how many letters of the word to skip. A number cannot start with zero. I use one position for the word and one position for the abbreviation. I move both positions from left to right. The answer is true only when the abbreviation describes the complete word and both positions finish at the same time.

Useful Questions to Ask the Interviewer
  1. Can I assume the abbreviation contains only letters and digits?
  2. Should a number that starts with zero always be treated as invalid?
  3. Does a skip that goes past the end of the word make the abbreviation invalid?
How would you solve Valid Word Abbreviation? diagram
How to Explain It in an Interview
1. Understand the input and required output

We receive word and abbr. We return true when abbr correctly represents the whole word. Otherwise, we return false.

A normal letter in abbr must exactly match the current letter in word. A group of digits represents one number. That number tells us how many letters to skip in word. For example, 12 means skip twelve letters. It does not mean two separate skips of one and two.

A number cannot start with 0. The final check is also important. Both strings must be completely consumed.

2. Choose two pointers

I use i for the current position in word and j for the current position in abbr.

The main invariant is simple: before each new step, word[0..i-1] has already been correctly represented by abbr[0..j-1].

If abbr[j] is a letter, I compare it with word[i]. If the letters are different, the answer is immediately false. If they match, I move both pointers forward by one.

If abbr[j] is a digit, I first reject a leading zero. Then I read every consecutive digit and build one skipCount. I add that number to i.

3. Initialize the state

I start with i = 0 and j = 0. This means no part of either string has been processed yet.

The example is: word = "internationalization" abbr = "i12iz4n"

The word length is 20. The abbreviation length is 7.

4. Walk through the example

Step 1 starts at (i=0, j=0). The abbreviation character is i. It matches word[0], which is also i. I advance both pointers. The new state is (i=1, j=1).

Step 2 starts at (i=1, j=1). The abbreviation starts the number 12. I parse both digits together, so skipCount = 12. After reading the two digits, j becomes 3. I move i from 1 to 13. The new state is (i=13, j=3).

Step 3 reads abbr[3] = 'i'. It matches word[13] = 'i'. I advance both pointers to (i=14, j=4).

Step 4 reads abbr[4] = 'z'. It matches word[14] = 'z'. I advance both pointers to (i=15, j=5).

Step 5 reads the number 4. I parse it as skipCount = 4. I move j to 6 and move i from 15 to 19. The new state is (i=19, j=6).

Step 6 reads abbr[6] = 'n'. It matches word[19] = 'n'. I advance both pointers to (i=20, j=7).

Now i == word.length() and j == abbr.length(). Both strings finished together, so the method returns true.

5. Explain why the result is correct

The invariant is that the processed part of the abbreviation correctly represents the processed part of the word. A letter step keeps the invariant because the two characters must match exactly. A number step keeps it because the word pointer moves forward by exactly the parsed skip count.

For i12iz4n, i matches word[0], 12 skips word[1..12], i matches word[13], z matches word[14], 4 skips word[15..18], and n matches word[19]. Both pointers then reach their ends together.

6. Explain the Java implementation

The Java method keeps integer pointers i and j. Its loop runs while both pointers are still inside their strings. A letter is checked directly against word.charAt(i). A digit starts number parsing. The code rejects 0 before parsing because a number cannot have a leading zero.

The inner loop builds a complete number with skipCount = skipCount * 10 + digit. After the number is parsed, the code adds skipCount to i. The final return checks that both pointers reached their exact string lengths.

7. Explain complexity and edge cases

Let n be the length of word and m be the length of abbr. The pointers move only forward, so the running time is O(n + m). The algorithm only stores a few integer and character variables, so the auxiliary space is O(1).

Important cases are a leading zero such as w02d, a multi-digit number such as 12, a skip that moves past the end of the word, and an abbreviation that contains the exact full word with no digits.

Key Insight / Why This Solution Works

The key idea is to represent progress with two pointers. Pointer i tells us how much of word has been represented. Pointer j tells us how much of abbr has been read. The invariant is that word[0..i-1] is correctly represented by abbr[0..j-1]. A letter keeps this invariant only when it matches the current word character. A number keeps it by moving i forward by exactly the parsed skip count. Parsing all consecutive digits together is important because a value such as 12 is one skip. Rejecting a leading zero prevents invalid forms such as 02. Finally, checking both pointer positions prevents accepting an abbreviation that covers too little or too much of the word.

Code
public class Main {

    static class Solution {

        public boolean validWordAbbreviation(String word, String abbr) {
            // i tracks the next character in the original word.
            int i = 0;

            // j tracks the next character in the abbreviation.
            int j = 0;

            // Continue while both strings still have characters to process.
            // A mismatch can return false before the full input is read.
            while (i < word.length() && j < abbr.length()) {
                char current = abbr.charAt(j);

                if (Character.isLetter(current)) {
                    // A literal letter in the abbreviation must match the
                    // current character in the word exactly.
                    if (word.charAt(i) != current) {
                        return false;
                    }

                    // The matching characters are now represented, so move
                    // both pointers to their next positions.
                    i++;
                    j++;
                } else {
                    // A number cannot start with zero, so forms such as 02
                    // are invalid abbreviations.
                    if (current == '0') {
                        return false;
                    }

                    // Parse all consecutive digits as one skip count.
                    // For example, '1' followed by '2' becomes 12, not two
                    // independent skips.
                    int skipCount = 0;
                    while (j < abbr.length() && Character.isDigit(abbr.charAt(j))) {
                        skipCount = skipCount * 10 + (abbr.charAt(j) - '0');
                        j++;
                    }

                    // Move the word pointer past exactly the abbreviated
                    // number of characters.
                    i += skipCount;
                }
            }

            // The abbreviation is valid only when both strings are consumed
            // exactly. This also rejects a skip that goes past the word end.
            return i == word.length() && j == abbr.length();
        }
    }

    public static void main(String[] args) {
        Solution solution = new Solution();

        // Run the same verified example used in the diagram.
        String word = "internationalization";
        String abbr = "i12iz4n";

        // Expected output: true.
        System.out.println(solution.validWordAbbreviation(word, abbr));
    }
}
Time & Space Complexity

Let n be word.length() and m be abbr.length(). The time complexity is O(n + m), matching the algorithm in the diagram. Both pointers only move forward. The abbreviation pointer advances through at most m positions, and the word pointer never moves backward. Each abbreviation position is advanced past once, although a character can be inspected a constant number of times while the code decides whether it is a letter or part of a number. The algorithm may also stop early when it finds a mismatch. The auxiliary space is O(1) because it uses only a few variables such as i, j, current, and skipCount. The extra memory does not grow with the input size.

Where it is used

This two-pointer parsing pattern is useful when one string is a compact description of another string. Similar logic appears in parsers, compact text formats, encoded ranges, and validation code where numbers describe how far to move through another sequence. The important pattern is to keep synchronized positions while interpreting each token from left to right.

Why Interviewers Ask This

This problem tests whether a candidate can manage two related positions correctly while parsing a string. The interviewer can see whether the candidate handles multi-digit numbers, leading zeros, exact character matching, and boundary conditions without losing track of state. It also tests whether the candidate can state and maintain a useful invariant, stop correctly on a mismatch, write clear Java pointer logic, and explain why the final end-position check is necessary.

Common interview mistakes

A common mistake is reading each digit separately. For example, 12 must be parsed as one number, not as skips of 1 and 2. Another mistake is allowing a number to start with 0, such as 02. Candidates also sometimes move the wrong pointer after parsing a number. The abbreviation pointer moves while the digits are parsed, and then the word pointer moves by the completed skip count. Finally, returning true without checking both final pointer positions can incorrectly accept an abbreviation that leaves characters unprocessed or skips past the word.

Interview tip

While coding, say what each pointer means and trace the multi-digit 12 case out loud. Showing that i moves from 1 to 13 after parsing 12 makes the main idea and the pointer updates easy for the interviewer to verify.

Interviewer may ask next
What happens if a numeric skip moves past the end of the word?

The abbreviation is invalid. With this implementation, i becomes greater than word.length(). The main loop then stops, and the final condition i == word.length() is false. No different algorithm is needed. The time remains O(n + m), and the auxiliary space remains O(1).

Why do we parse all consecutive digits before moving the word pointer?

Consecutive digits represent one number. For example, 12 means skip twelve word characters. Treating it as separate values would change the meaning of the abbreviation and move the pointer incorrectly. Parsing the complete number first preserves the invariant that the processed abbreviation exactly represents the processed word prefix. The time remains O(n + m), and the auxiliary space remains O(1).

9. How would you find the number of connections needed to connect all computers?CodingEasyMeta

Question Details

Given pairs of connected computers, determine how many additional connections are needed to connect the whole network.

Short Interview Answer (30-60 seconds)

I would model the computers and their existing connections as an undirected graph. I would build an adjacency list, then run DFS from each computer that has not been visited yet. Each new DFS finds one connected component. If there are C components, I need C - 1 additional connections to join them into one network. The full algorithm takes O(n + e) time and uses O(n + e) auxiliary space for the graph, visited array, and recursion stack.

Detailed Explanation

See the Code while reading this explanation.

We are given computers and pairs that show which computers already have a direct connection. The goal is to find how many new connections are needed so every computer belongs to one connected network. I first find groups of computers that are already connected, including connections through other computers. Then I count those separate groups. If there are three groups, two new links can join them into one network. DFS fits this problem because one DFS can visit every computer in one connected group.

Useful Questions to Ask the Interviewer
  1. Should I treat each connection as two-way, so the network is undirected?
  2. Are the computers numbered from 0 to n - 1?
  3. Do you only need the number of additional connections, not the actual new connection pairs?
How would you find the number of connections needed to connect all computers? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is the number of computers n and a list of existing connection pairs. Each pair [u, v] means computer u is connected to computer v. The diagram treats these connections as undirected, so the relationship works both ways. The output is one integer: the number of additional connections required to make the whole network connected.

For the example, n = 6 and connections = [[0,1], [0,2], [3,4]]. Computers 0, 1, and 2 form one group. Computers 3 and 4 form another group. Computer 5 is alone. There are 3 connected components, so the answer is 3 - 1 = 2.

2. Choose the algorithm and data structure

I use an adjacency list to represent the graph. For each connection [u, v], I add v to u's neighbor list and u to v's neighbor list because the graph is undirected.

I also use a boolean visited array. visited[i] tells me whether computer i has already been reached by DFS.

The important idea is that one DFS starting from an unvisited computer visits every computer in that connected component. Therefore, every time the outer loop finds an unvisited computer and starts DFS, I have found exactly one new component.

3. Initialize the state

For n = 6, the adjacency list is 0:[1,2], 1:[0], 2:[0], 3:[4], 4:[3], and 5:[].

The visited array starts as [false, false, false, false, false, false]. The component count starts at 0. The outer loop starts at computer 0.

The invariant is: after DFS starts from an unvisited computer, every computer in that same connected component becomes visited.

4. Walk through the example

At node 0, no computer has been visited. Node 0 is unvisited, so I increase components from 0 to 1 and start DFS from 0. DFS reaches 0, 1, and 2. The visited computers are now {0,1,2}.

At node 1, it is already visited, so I skip it. The visited state stays {0,1,2}, and components stays 1.

At node 2, it is already visited, so I skip it. Components stays 1.

At node 3, it is unvisited. I increase components to 2 and start DFS from

  1. DFS reaches 3 and
  2. The visited computers become {0,1,2,3,4}.

At node 4, it is already visited, so I skip it. Components stays 2.

At node 5, it is unvisited. I increase components to 3 and start DFS from 5. Computer 5 has no neighbors, so it forms a one-computer component. The visited computers become {0,1,2,3,4,5}.

The scan is complete. I return components - 1, which is 3 - 1 = 2.

5. Explain why the result is correct

DFS marks every computer reachable from its starting computer. Because the outer loop starts DFS only from an unvisited computer, each DFS launch represents a different connected component. A component cannot be counted twice because all of its computers become visited during its first DFS.

If there are C disconnected components, one new connection can join two components and reduce the number of separate groups by one. Repeating this C - 1 times joins all components. Therefore, the required number of new connections is C - 1.

6. Explain the Java implementation

The code first creates one adjacency list entry for each computer. It adds every existing connection in both directions. It then creates the visited array and sets components to 0.

The outer loop checks every computer from 0 to n - 1. When it finds an unvisited computer, it increases components and calls DFS. DFS marks the current computer visited and recursively visits each neighbor that is still unvisited.

After the loop finishes, the method returns Math.max(0, components - 1). For the diagram's example, components is 3, so the method returns 2.

7. Explain complexity and edge cases

Let n be the number of computers and e be the number of existing connections. Creating the adjacency list and traversing the graph take O(n + e) time. DFS visits each computer once and examines the stored connections.

The adjacency list uses O(n + e) memory. The visited array uses O(n), and the recursive DFS can use O(n) call-stack space in the worst case. Therefore, the total auxiliary space is O(n + e).

Important cases from the diagram are an already connected network, which returns 0, one computer, which returns 0, no existing connections among n computers, which requires n - 1 connections, and isolated computers, which each count as separate components.

Key Insight / Why This Solution Works

The key insight is to count connected components instead of choosing the new connections directly. I represent the undirected network with an adjacency list and use DFS to discover each component. The outer loop checks every computer. If a computer is still unvisited, it belongs to a component that has not been counted yet, so I increment the component count and run DFS from it. The central invariant is that after a DFS finishes, every computer in that connected component is marked visited. If there are C components, exactly C - 1 new connections are needed to join them into one connected network.

Code
import java.util.ArrayList;
import java.util.List;

public class Main {

    // Keep the solution method and DFS structure shown in the diagram.
    static class Solution {

        public int additionalConnections(int n, int[][] connections) {
            // Create one adjacency-list entry for every computer.
            List<List<Integer>> graph = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                graph.add(new ArrayList<>());
            }

            // Each connection is undirected, so store it in both directions.
            for (int[] edge : connections) {
                int u = edge[0];
                int v = edge[1];
                graph.get(u).add(v);
                graph.get(v).add(u);
            }

            // visited[node] records whether DFS has already reached that computer.
            boolean[] visited = new boolean[n];
            int components = 0;

            // Every DFS launched from an unvisited node discovers one new component.
            for (int node = 0; node < n; node++) {
                if (!visited[node]) {
                    components++;
                    dfs(node, graph, visited);
                }
            }

            // C connected components need C - 1 new links to become one component.
            return Math.max(0, components - 1);
        }

        private void dfs(int node, List<List<Integer>> graph, boolean[] visited) {
            // Mark the node before exploring neighbors so cycles do not revisit it.
            visited[node] = true;

            // Continue DFS through every still-unvisited neighbor in this component.
            for (int next : graph.get(node)) {
                if (!visited[next]) {
                    dfs(next, graph, visited);
                }
            }
        }
    }

    public static void main(String[] args) {
        // Run the exact verified example from the diagram.
        int n = 6;
        int[][] connections = { { 0, 1 }, { 0, 2 }, { 3, 4 } };

        Solution solution = new Solution();

        // Components are {0,1,2}, {3,4}, and {5}, so the expected result is 2.
        System.out.println(solution.additionalConnections(n, connections));
    }
}
Time & Space Complexity

Let n be the number of computers and e be the number of existing connections. The time complexity is O(n + e). We create n adjacency lists, add the e undirected connections, and DFS visits every computer while examining the stored edges. The auxiliary space complexity is O(n + e). The adjacency list stores the graph, the visited array stores one boolean for each computer, and recursive DFS may use up to O(n) call-stack space in the worst case.

Where it is used

This pattern is useful when software needs to find separate groups in an undirected network. Examples include disconnected computers in a network, groups of connected users, clusters of devices, and separate regions in graph-based systems. Counting connected components is also useful when deciding how many new links are needed to join separate parts of a system.

Why Interviewers Ask This

This question checks whether you can recognize a graph problem from connection pairs. The interviewer can evaluate whether you know how to build an undirected adjacency list, use DFS correctly, maintain visited state, and count connected components without double-counting. It also tests whether you can connect the component count to the C - 1 result, write correct Java, explain the invariant, discuss recursion, handle relevant edge cases, and state O(n + e) time and auxiliary space accurately.

Common interview mistakes

A common mistake is treating the connections as directed and storing only u -> v instead of both directions. Another mistake is starting DFS from every computer without first checking visited, which counts the same connected component more than once. Candidates may also mark a node visited too late, causing repeated recursive visits when the graph has cycles. Another mistake is returning the number of components instead of components - 1. It is also incorrect to claim O(n) auxiliary space while ignoring the O(n + e) adjacency list.

Interview tip

Explain the invariant before coding: whenever the outer loop finds an unvisited computer, that computer starts one new connected component, and DFS marks that entire component visited. Once that is clear, the final components - 1 calculation is easy to justify.

Interviewer may ask next
How would the solution change if you had to return the actual new connections instead of only their count?

I would keep the same DFS component search. When each new component is discovered, I would save one representative computer from that component. If the representatives are r0, r1, r2, and so on, I can connect r0 to r1, r1 to r2, and continue until all components are joined. With C components, this creates C - 1 new edges. Correctness is preserved because every new edge joins two previously separate component groups. The traversal remains O(n + e) time. The graph and traversal use O(n + e) auxiliary space, with up to O(C) additional space for the representatives.

How would you handle a very large network where recursive DFS could overflow the Java call stack?

I would keep the same connected-component approach but replace recursive DFS with iterative DFS using an explicit ArrayDeque<Integer> stack. When the outer loop finds an unvisited computer, I increment components, mark that computer visited, push it, and repeatedly pop a node and add its unvisited neighbors. Each traversal still visits exactly one connected component, so correctness does not change. Time remains O(n + e), and auxiliary space remains O(n + e) including the adjacency list, visited array, and explicit stack. The tradeoff is a little more code, but it avoids relying on deep recursion.

10. How would you return the exclusive running time for each function?CodingMediumMeta

Question Details

Given begin and end logs for nested function calls, compute the active runtime for each function.

Short Interview Answer (30-60 seconds)

I would use a stack to track the currently running functions and a prevTime variable to track the first uncounted time unit. I process the logs in order. When a new function starts, I charge the current top function for the time since prevTime, then push the new function. When a function ends, I include the end timestamp, pop it, and move prevTime forward. This takes O(m) time and O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input contains start and end records for functions that can call other functions. We need to calculate how long each function itself runs. Time spent inside a child call must not also count toward its parent. Calls can be nested, so the most recently started function is the one currently running. We therefore keep the active calls in their nesting order and remember the first time unit that has not been counted yet. This lets us give each unit of execution time to exactly one function.

Useful Questions to Ask the Interviewer
  1. Are the logs already ordered by timestamp?
  2. Are end timestamps inclusive, so an end at time 5 includes time 5?
  3. Can the same function id appear in more than one call?
How would you return the exclusive running time for each function? diagram
How to Explain It in an Interview
1. Understand the input and required output

We have n functions. Each log has the form id:start|end:timestamp. The result is an int array. result[i] stores the exclusive running time of function i. Exclusive means we do not count time that the function spends waiting while a nested child function runs.

For the example, n = 2 and logs = ["0:start:0", "1:start:2", "1:end:5", "0:end:6"]. The required result is [3, 4]. Function 0 runs at times 0, 1, and 6. Function 1 runs at times 2, 3, 4, and 5.

2. Choose the stack and prevTime

I use a stack of function ids. The top of the stack is the function that is running now. The stack also keeps the nested call order. I use prevTime to mean the first time unit that has not yet been assigned to a function.

The main invariant is that the stack stores the current active call path, and every time before prevTime has already been counted exactly once.

3. Initialize the state

Start with answer = [0, 0], stack = [], and prevTime = 0. No function has received any running time yet. The stack is empty because no function has started.

4. Walk through the example

Step 1 processes 0:start:0. The stack is empty, so there is no active function to charge. Push function 0. The stack becomes [0]. answer stays [0, 0]. Set prevTime to 0.

Step 2 processes 1:start:2. Function 0 is currently on top. It ran from time 0 until just before time 2. Add 2 - 0 = 2 to answer[0]. answer becomes [2, 0]. Push function 1, so the active call path is [0, 1]. Set prevTime to 2.

Step 3 processes 1:end:5. Function 1 is on top. End timestamps are inclusive, so add 5 - 2 + 1 = 4 to answer[1]. answer becomes [2, 4]. Pop function 1. Function 0 is active again. Set prevTime to 5 + 1 = 6 because time 5 has already been counted.

Step 4 processes 0:end:6. Function 0 is on top again. Add 6 - 6 + 1 = 1 to answer[0]. answer becomes [3, 4]. Pop function 0 and set prevTime to 7. All four logs are now processed, so return [3, 4].

5. Explain why the result is correct

Before a child function starts, we give the parent exactly the elapsed time since prevTime. When a function ends, we give it time through the inclusive end timestamp. Then we move prevTime past that timestamp. Because only the function at the top of the stack receives time, nested time is never counted for both parent and child. Every time unit is assigned exactly once.

6. Explain the Java implementation

The Java code keeps the answer array, a Deque<Integer> used as a stack, and prevTime. It parses every log into id, type, and time. A start event may first charge the current top function, then pushes the new function. An end event charges the top function including the end timestamp, pops it, and moves prevTime to time + 1. After every log has been processed, the code returns the answer array.

7. Explain complexity and edge cases

If m is the number of log entries, the loop processes each log once, so the time complexity is O(m). The result array and call stack use O(n) space in the worst case shown in the diagram. Important cases include one function, deeply nested calls, repeated calls using the same function id, and a function that starts and ends at the same timestamp. Empty or null logs can also be handled defensively by returning a zero-filled result array if that behavior is desired.

Key Insight / Why This Solution Works

The key idea is to always give elapsed execution time to the function at the top of the stack. That function is the one currently running. When a child starts, the parent must stop receiving time, so we first add time - prevTime to the parent and then push the child. When the top function ends, we add time - prevTime + 1 because the end timestamp is inclusive. We then pop it and move prevTime to time + 1. The invariant is that the stack contains the active call path and prevTime is the first uncounted time unit.

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

public class Main {

    public static int[] exclusiveTime(int n, List<String> logs) {
        // answer[i] stores the exclusive running time for function i.
        int[] answer = new int[n];

        // The stack stores active function ids. The top id is running now.
        Deque<Integer> stack = new ArrayDeque<>();

        // prevTime is the first time unit that has not been counted yet.
        int prevTime = 0;

        // Process every log in the given execution order.
        for (String log : logs) {
            // Parse the function id, event type, and timestamp.
            String[] parts = log.split(":");
            int id = Integer.parseInt(parts[0]);
            String type = parts[1];
            int time = Integer.parseInt(parts[2]);

            if ("start".equals(type)) {
                // If another function is active, charge it only for the time
                // before this new function starts.
                if (!stack.isEmpty()) {
                    answer[stack.peek()] += time - prevTime;
                }

                // The new function becomes the currently running function.
                stack.push(id);

                // Its execution starts at this timestamp.
                prevTime = time;
            } else {
                // End timestamps are inclusive, so count the end time unit too.
                answer[stack.peek()] += time - prevTime + 1;

                // The current function is finished, so remove it from the stack.
                stack.pop();

                // A parent function, if present, resumes after this end timestamp.
                prevTime = time + 1;
            }
        }

        // All log entries have been processed.
        return answer;
    }

    public static void main(String[] args) {
        // Use the exact example shown in the approved diagram.
        int n = 2;
        List<String> logs = List.of("0:start:0", "1:start:2", "1:end:5", "0:end:6");

        // Run the solution and print the expected result: [3, 4].
        int[] result = exclusiveTime(n, logs);
        System.out.println(Arrays.toString(result));
    }
}
Time & Space Complexity

Let m be the number of log entries. We process each log once, so the time complexity is O(m). The diagram's solution uses an answer array and a stack for active calls. It gives O(n) auxiliary space in the worst case shown in the diagram.

Where it is used

This stack pattern is useful when software records nested start and end events. Examples include profiling function execution, tracing nested method calls, measuring exclusive CPU time, and processing structured event logs where the most recently started operation must finish before its parent continues.

Why Interviewers Ask This

This problem checks whether you can recognize nested execution as a stack problem and maintain a correct time-accounting invariant. The interviewer can see whether you understand which function is active, how parent execution pauses during a child call, and why inclusive end timestamps need special handling. It also tests careful state updates, correct Java stack usage with Deque, accurate complexity analysis, and the ability to trace a nontrivial example without double-counting time.

Common interview mistakes

A common mistake is forgetting that an end timestamp is inclusive. The end calculation needs +1. Another mistake is setting prevTime to time instead of time + 1 after an end event, which double-counts the end unit. Candidates may also forget to charge the current parent before pushing a child. Another error is adding elapsed time to the new function instead of the current stack top. Finally, the stack must follow the exact push and pop order of the nested calls.

Interview tip

Explain the meaning of prevTime before writing code: it is the first time unit that has not been counted yet. Then show why a start uses time - prevTime but an end uses time - prevTime + 1. That makes the inclusive timestamp rule and the stack logic much easier to follow.

Interviewer may ask next
What changes if the logs contain much deeper nesting?

The algorithm does not change. The stack naturally stores each active nested call. We still process each log once, so the time complexity remains O(m). The stack grows with the active nesting depth, while the diagram expresses the overall auxiliary-space bound as O(n). The tradeoff is that deeper nesting requires more stack memory.

What changes if the same function id is called multiple times?

No algorithm change is needed. Each call is pushed and popped separately on the stack, even when two calls have the same id. Every completed time interval is added to answer[id], so all calls for that function accumulate into the same result entry. The processing order, O(m) time, and the diagram's O(n) auxiliary-space bound remain unchanged.

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.