Java solution to subsets/power-set FAANG interview questions

Key takeaways:

  • A subset is any combination of elements from a set, and the power set is the collection of all subsets. For a set with nn elements, the power set contains 2n2^n subsets.

  • Subsets are crucial in combinatorics and are used in problems like decision-making, finding combinations, and organizing data.

  • Bitwise operations are used to generate the power set, determining element inclusion/exclusion through binary representation.

  • Duplicates are avoided by sorting the array and skipping subsets with repeated elements.

  • Time complexity is O(2nn)O(2^n * n) and space complexity is O(2nn)O(2^n * n) due to subset generation and storage.

A subset is any combination of elements from a given set, including the empty set and the set itself. The power set is the set of all possible subsets of a given set. For a set with nn elements, the power set contains 2n2^n subsets. Subsets/power sets are fundamental in combinatorics and are widely applicable in solving problems like:

  • Decision-making processes (e.g., subset sums, knapsack problems).

  • Finding combinations and arrangements.

  • Data organization tasks (e.g., forming groups, partitions).

Subsets-related questions test your problem-solving ability and understanding of recursion, backtracking, and iterative approaches.

Problem statement of subsets/power-set

Write a program to find all possible subsets (the power set) of a given input array nums. The solution should ensure no duplicate subsets, even if the input array contains duplicates.

Example:

  • Input: nums = [1, 2, 2, 2]

  • Output: [[], [1], [1, 2], [1, 2, 2], [1, 2, 2, 2], [2], [2, 2], [2, 2, 2]]

  • Explanation: According to the formula mentioned earlier, the total count of subsets is calculated as24=162^4 = 16However, the output shows only 88 subsets because duplicate subsets are ignored.

Java solution to subsets/power-set

This program generates the power set of a given input array nums using bitwise operations. For a set with nn elements, there are 2n2^n possible subsets. Each subset can be formed by considering whether each element is included or excluded. Using bitwise operations, we can efficiently determine the inclusion or exclusion of elements for each subset in an iterative manner.

The process involves an outer loop controlled by a variable called counter, which iterates through all integers from 0 to 2n12^{n}-1. The binary representation of the counter determines which elements are included in the current subset. The table below demonstrates how subsets are generated based on the binary values of counter.

Counter (in decimal)

Counter (in binary)

Subset

0

000

[]

1

001

[1]

2

010

[2]

3

011

[1, 2]

4

100

[3]

5

101

[1, 3]

6

110

[2, 3]

7

111

[1, 2, 3]

Let’s look at the following illustration to get a better understanding of the solution:

canvasAnimation-image
1 of 3

Step-by-step algorithm:

Here is the step-by-step approach to generate all unique subsets.

  1. Sort the input array:

    1. Sort nums to handle duplicates effectively.

    2. Sorting ensures that duplicates are adjacent, allowing us to skip them easily.

  2. Initialize variables:

    1. Create an empty list result to store all subsets.

  3. Iterate through all possible combinations:

    1. Compute the total number of subsets: subsetCount = 2^n or 1 << n (bitwise left shiftThe bitwise left shift operation (<<) shifts the bits of a number to the left by a specified number of positions. Each left shift multiplies the number by 2, effectively doubling its value.).

    2. For each integer counter from 0 to subsetCount - 1:

      1. Initialize an empty list currentSubset to build the current subset.

      2. Set a boolean isValid to true to track if this subset is valid (used for skipping duplicates).

      3. Check each bit:

        1. For each bit position i (from 0 to n−1):

          1. If the i-th bit of counter is set (counter & (1 << i) != 0):

            1. Check for duplicates:

              1. If nums[i] == nums[i-1] and the i−1-th bit of counter is not set, mark isValid as false and break the loop.

            2. If no duplicates, add nums[i] to currentSubset.

  4. Add valid subsets:

    1. If isValid is still true after the loop, add currentSubset to result.

  5. Return the result:

    1. After processing all counter values, return the result list containing all subsets.

Check out this course on how to solve problems using bit manipulation: Master Bit Manipulation for Coding Interviews.

Let’s look at the code for the solution we just discussed.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PowerSetBitwise {
// Function to generate subsets using bitwise operations
public static List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums); // Sort the array to handle duplicates
int subsetCount = 1 << nums.length; // Total subsets: 2^n
for (int counter = 0; counter < subsetCount; counter++) {
List<Integer> currentSubset = new ArrayList<>();
boolean isValid = true;
for (int i = 0; i < nums.length; i++) {
if ((counter & (1 << i)) != 0) { // Check if i-th bit is set
// Skip duplicates
if (i > 0 && nums[i] == nums[i - 1] && (counter & (1 << (i - 1))) == 0) {
isValid = false;
break;
}
currentSubset.add(nums[i]);
}
}
if (isValid) {
result.add(new ArrayList<>(currentSubset));
}
}
return result;
}
// Main driver function with test cases
public static void main(String[] args) {
// Test cases
int[][] testCases = {
{1, 2, 1},
{0},
{1, 1, 1},
{1, 2, 3, 4, 5},
{}
};
// Execute each test case
for (int t = 0; t < testCases.length; t++) {
System.out.println("Test Case " + (t + 1) + ":");
List<List<Integer>> result = subsetsWithDup(testCases[t]);
System.out.println("Input: " + Arrays.toString(testCases[t]));
System.out.println("Output: " + result);
System.out.println("--------------------------------------------------------------------------------------");
}
}
}

Complexity analysis

The time complexity of the given solution is O(2nn)O(2^n \cdot n). This is because there are 2n2^n possible subsets for an input array of size nn, as each element can either be included or excluded. For each subset, the algorithm iterates through nn elements to check if a bit is set, resulting in a total time complexity of O(2nn)O(2^n \cdot n). Additionally, the array is sorted initially to handle duplicates, which takes O(nlogn)O(n \log n). However, for large nn, the 2n2^n term dominates, making the overall time complexity approximately O(2nn)O(2^n \cdot n).

The space complexity of the solution is O(2nn)O(2^n \cdot n). This accounts for the storage of all subsets in the result list, where each subset can have up to nn elements, and there are 2n2^n subsets. Temporary variables like currentSubset and the counter use negligible additional space. Thus, the dominant factor in space usage is the storage of subsets, resulting in a total space complexity of O(2nn)O(2^n \cdot n).

Dreaming of acing your FAANG interview? Success requires more than just practice—it demands mastering proven coding patterns and problem-solving strategies. Check this highly recommended course, “Grokking the Coding Interview Patterns,” designed to turbocharge your preparation and set you on the path to success!

Frequently asked questions

Haven’t found what you were looking for? Contact Us


Why are power set problems considered a good test of algorithmic skills?

Power set problems require a combination of skills like recursion, iteration, and understanding of combinatorics. They test a programmer’s ability to explore all possible combinations of a dataset, manage edge cases like duplicates, and optimize the solution for performance, making them an excellent choice for technical interviews.


How does sorting the input array impact the subsets generation process?

Sorting the input array simplifies the process of handling duplicate elements. By placing duplicate values next to each other, it becomes easier to skip over them during subset generation. This ensures that only unique subsets are included in the final result, regardless of the approach used.


What role does bitwise manipulation play in solving subsets problems?

Bitwise manipulation provides an efficient and elegant way to generate subsets. By iterating over all integers from 0 to 2^𝑛 −1, each integer’s binary representation can act as a mask to determine which elements of the input array are included in the subset. This eliminates the need for recursion and makes the solution iterative and scalable.


Why is the subsets problem a common question in FAANG interviews?

The subsets problem is frequently asked in FAANG interviews because it demonstrates a candidate’s understanding of fundamental concepts like recursion, backtracking, and bitwise operations. Additionally, it reveals the candidate’s ability to handle edge cases, optimize solutions, and think creatively about problem-solving strategies.


Free Resources

Attributions:
  1. undefined by undefined
  2. undefined by undefined