Split the Array solution and explanation
9 more videos on this problem
3046. Split the Array - LeetCode Weekly Contest 386 | Python, JavaScript, Java, C++CodingNinja · 3:57 · 1K views
3046. Split the Array | LeetCode Weekly Contest 386 | C++Priyanshi Porwal · 5:41 · 972 views
Silly Visa Interview Question | Leetcode 3046 Split the Array | Telugu | DSADSA Bro · 4:23 · 855 views
LeetCode 3046 Split the Array | Hindi | Competitive Programming | JavaCode with Satish · 7:12 · 695 views
3046. Split the Array (Leetcode Easy)Programming Live with Larry · 2:20 · 467 views
3046. Split the ArrayoneArena · 5:06 · 232 views
Leetcode 3046. Split the ArrayElite Code · 12:57 · 183 views
my attempt at solving leetcode 3046: split the array [SOLVED]Cameron Late · 14:52 · 47 views
LeetCode 3046: Split the Array Explained in C++ | Step-by-StepJonty Patel · 7:48 · 8 views
TitleLeetCode #3046 | Split the Array | Frequency Count Approach in C++Code with Vishv Patel · 4:29 · 6 views
The problem
You are given an integer array nums of even length. You have to split the array into two parts nums1 and nums2 such that:
nums1.length == nums2.length == nums.length / 2.nums1should contain distinct elements.nums2should also contain distinct elements.
Return true if it is possible to split the array, and false otherwise.
Example 1:
Input: nums = [1,1,2,2,3,4] Output: true Explanation: One of the possible ways to split nums is nums1 = [1,2,3] and nums2 = [1,2,4].
Example 2:
Input: nums = [1,1,1,1] Output: false Explanation: The only possible way to split nums is nums1 = [1,1] and nums2 = [1,1]. Both nums1 and nums2 do not contain distinct elements. Therefore, we return false.
Constraints:
1 <= nums.length <= 100nums.length % 2 == 01 <= nums[i] <= 100
Approach overview
Problem Overview: You receive an integer array nums. The task is to determine whether it can be split into two arrays such that each resulting array contains only unique elements. An element may appear in both arrays, but duplicates inside the same array are not allowed.
Approach 1: Depth-First Search (Backtracking) (Time: O(2^n), Space: O(n))
Model the problem as assigning each number to one of two arrays. During recursion, try placing the current element into either array while ensuring no duplicate exists in that array. Use two sets to track elements already placed. If both placements violate the uniqueness rule, backtrack. This approach demonstrates the constraints clearly but becomes exponential because every element has two placement choices.
Approach 2: Breadth-First Search State Exploration (Time: O(2^n), Space: O(2^n))
Treat each partial assignment as a state in a queue. A state stores the current index and the elements placed in each array. For every number, generate new states by adding it to array A or B when the uniqueness constraint holds. BFS guarantees you explore all valid configurations level by level. This method is conceptually useful for understanding the assignment process but is impractical for large inputs due to the exponential state space.
Approach 3: Hash Counting (Optimal) (Time: O(n), Space: O(n))
The key observation: since there are only two arrays, any number can appear at most twice in the original array. If a value appears three or more times, at least one resulting array must contain a duplicate. Iterate through nums, maintain a frequency map using a hash table, and immediately return false if any count exceeds two. This turns the problem into a simple frequency validation using concepts from array traversal and counting.
Recommended for interviews: The hash counting approach is what interviewers expect. It reduces the problem to a simple invariant: each value can appear at most twice. Showing a brute-force DFS assignment first demonstrates reasoning about constraints, but recognizing the counting shortcut shows strong pattern recognition and leads to the optimal O(n) solution.
Approach 1: Breadth-First Search (BFS) Approach
The Breadth-First Search (BFS) approach involves exploring the neighbors of a node prior to moving on to the next level neighbors. This technique is typically implemented using a queue data structure to keep track of nodes being visited. BFS is particularly useful for finding the shortest path on unweighted graphs or levels of nodes.
Consider applying this approach when the question involves discovering nodes or levels layer by layer.
from collections import deque
def bfs(graph, start):
visited = [False] * len(graph)
queue = deque([start])
visited[start] = True
while queue:
node = queue.popleft()
print(node, end=' ')
for neighbour in graph[node]:
if not visited[neighbour]:
queue.append(neighbour)
visited[neighbour] = True
graph = {
0: [1],
1: [0, 2, 3],
2: [1, 3],
3: [1, 2]
}
bfs(graph, 0)import java.util.*;
class BFS {
public static void bfs(List<List<Integer>> graph, int start) {
boolean[] visited = new boolean[graph.size()];
Queue<Integer> queue = new LinkedList<>();
queue.add(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.poll();
System.out.print(node + " ");
for (int neighbour : graph.get(node)) {
if (!visited[neighbour]) {
queue.add(neighbour);
visited[neighbour] = true;
}
}
}
}
public static void main(String[] args) {
List<List<Integer>> graph = new ArrayList<>();
graph.add(Arrays.asList(1)); // Node 0
graph.add(Arrays.asList(0, 2, 3)); // Node 1
graph.add(Arrays.asList(1, 3)); // Node 2
graph.add(Arrays.asList(1, 2)); // Node 3
bfs(graph, 0);
}
}#include <iostream>
#include <vector>
#include <queue>
void bfs(const std::vector<std::vector<int>> &graph, int start) {
std::vector<bool> visited(graph.size(), false);
std::queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int node = q.front();
q.pop();
std::cout << node << " ";
for (int neighbour : graph[node]) {
if (!visited[neighbour]) {
q.push(neighbour);
visited[neighbour] = true;
}
}
}
}
int main() {
std::vector<std::vector<int>> graph = {
{1}, // Node 0
{0, 2, 3}, // Node 1
{1, 3}, // Node 2
{1, 2} // Node 3
};
bfs(graph, 0);
return 0;
}function bfs(graph, start) {
let visited = new Array(graph.length).fill(false);
let queue = [];
queue.push(start);
visited[start] = true;
while (queue.length != 0) {
let node = queue.shift();
console.log(node + " ");
for (let neighbour of graph[node]) {
if (!visited[neighbour]) {
queue.push(neighbour);
visited[neighbour] = true;
}
}
}
}
let graph = [
[1], // Node 0
[0, 2, 3], // Node 1
[1, 3], // Node 2
[1, 2] // Node 3
];
bfs(graph, 0);#include <stdio.h>
#include <stdlib.h>
void bfs(int **graph, int nodes, int start) {
int *visited = (int *)calloc(nodes, sizeof(int));
int queue[nodes];
int front = 0, rear = 0;
queue[rear++] = start;
visited[start] = 1;
while (front != rear) {
int current = queue[front++];
printf("%d ", current);
for (int i = 0; i < nodes; i++) {
if (graph[current][i] == 1 && !visited[i]) {
queue[rear++] = i;
visited[i] = 1;
}
}
}
free(visited);
}
int main() {
int nodes = 4;
int **graph = (int **)malloc(nodes * sizeof(int *));
for (int i = 0; i < nodes; i++) graph[i] = (int *)malloc(nodes * sizeof(int));
// Example graph
graph[0][1] = graph[1][0] = 1;
graph[1][2] = graph[2][1] = 1;
graph[1][3] = graph[3][1] = 1;
graph[2][3] = graph[3][2] = 1;
bfs(graph, nodes, 0);
for (int i = 0; i < nodes; i++) free(graph[i]);
free(graph);
return 0;
}using System;
using System.Collections.Generic;
class BFS {
public static void bfs(List<List<int>> graph, int start) {
bool[] visited = new bool[graph.Count];
Queue<int> queue = new Queue<int>();
queue.Enqueue(start);
visited[start] = true;
while (queue.Count != 0) {
int node = queue.Dequeue();
Console.Write(node + " ");
foreach (int neighbour in graph[node]) {
if (!visited[neighbour]) {
queue.Enqueue(neighbour);
visited[neighbour] = true;
}
}
}
}
static void Main(string[] args) {
List<List<int>> graph = new List<List<int>> {
new List<int> { 1 },
new List<int> { 0, 2, 3 },
new List<int> { 1, 3 },
new List<int> { 1, 2 }
};
bfs(graph, 0);
}
}The Python implementation uses a deque from the collections module to efficiently pop elements from the left. Each node is marked as visited and then expanded.
The time complexity is O(V + E) and the space complexity is O(V) for the queue and visited dictionary.
Approach 2: Depth-First Search (DFS) Approach
The Depth-First Search (DFS) approach explores as far along a branch as possible before backtracking. It is typically implemented using a stack, either explicitly or through recursion which utilizes the call stack. DFS is effective for path-finding and solving puzzles like mazes.
Consider using this approach when the problem can benefit from going deep into a particular path before considering alternatives.
def dfs(graph, node, visited):
if node not in visited:
print(node, end=' ')
visited.add(node)
for neighbour in graph[node]:
dfs(graph, neighbour, visited)
graph = {
0: [1],
1: [0, 2, 3],
2: [1, 3],
3: [1, 2]
}
visited = set()
dfs(graph, 0, visited)import java.util.*;
class DFS {
private static void dfsUtil(List<List<Integer>> graph, int node, boolean[] visited) {
visited[node] = true;
System.out.print(node + " ");
for (int neighbour : graph.get(node)) {
if (!visited[neighbour]) {
dfsUtil(graph, neighbour, visited);
}
}
}
public static void dfs(List<List<Integer>> graph, int start) {
boolean[] visited = new boolean[graph.size()];
dfsUtil(graph, start, visited);
}
public static void main(String[] args) {
List<List<Integer>> graph = new ArrayList<>();
graph.add(Arrays.asList(1)); // Node 0
graph.add(Arrays.asList(0, 2, 3)); // Node 1
graph.add(Arrays.asList(1, 3)); // Node 2
graph.add(Arrays.asList(1, 2)); // Node 3
dfs(graph, 0);
}
}#include <iostream>
#include <vector>
void dfsUtil(const std::vector<std::vector<int>> &graph, int node, std::vector<bool> &visited) {
visited[node] = true;
std::cout << node << " ";
for (int neighbour : graph[node]) {
if (!visited[neighbour]) {
dfsUtil(graph, neighbour, visited);
}
}
}
void dfs(const std::vector<std::vector<int>> &graph, int start) {
std::vector<bool> visited(graph.size(), false);
dfsUtil(graph, start, visited);
}
int main() {
std::vector<std::vector<int>> graph = {
{1}, // Node 0
{0, 2, 3}, // Node 1
{1, 3}, // Node 2
{1, 2} // Node 3
};
dfs(graph, 0);
return 0;
}function dfs(graph, node, visited) {
if (!visited[node]) {
console.log(node + " ");
visited[node] = true;
for (let neighbour of graph[node]) {
dfs(graph, neighbour, visited);
}
}
}
let graph = [
[1], // Node 0
[0, 2, 3], // Node 1
[1, 3], // Node 2
[1, 2] // Node 3
];
let visited = new Array(graph.length).fill(false);
dfs(graph, 0, visited);#include <stdio.h>
#include <stdlib.h>
void dfsHelper(int **graph, int nodes, int current, int *visited) {
visited[current] = 1;
printf("%d ", current);
for (int i = 0; i < nodes; i++) {
if (graph[current][i] && !visited[i]) {
dfsHelper(graph, nodes, i, visited);
}
}
}
void dfs(int **graph, int nodes, int start) {
int *visited = (int *)calloc(nodes, sizeof(int));
dfsHelper(graph, nodes, start, visited);
free(visited);
}
int main() {
int nodes = 4;
int **graph = (int **)malloc(nodes * sizeof(int *));
for (int i = 0; i < nodes; i++) graph[i] = (int *)malloc(nodes * sizeof(int));
// Example graph
graph[0][1] = graph[1][0] = 1;
graph[1][2] = graph[2][1] = 1;
graph[1][3] = graph[3][1] = 1;
graph[2][3] = graph[3][2] = 1;
dfs(graph, nodes, 0);
for (int i = 0; i < nodes; i++) free(graph[i]);
free(graph);
return 0;
}using System;
using System.Collections.Generic;
class DFS {
private static void dfsUtil(List<List<int>> graph, int node, bool[] visited) {
visited[node] = true;
Console.Write(node + " ");
foreach (var neighbour in graph[node]) {
if (!visited[neighbour]) {
dfsUtil(graph, neighbour, visited);
}
}
}
public static void dfs(List<List<int>> graph, int start) {
bool[] visited = new bool[graph.Count];
dfsUtil(graph, start, visited);
}
static void Main(string[] args) {
List<List<int>> graph = new List<List<int>> {
new List<int> { 1 },
new List<int> { 0, 2, 3 },
new List<int> { 1, 3 },
new List<int> { 1, 2 }
};
dfs(graph, 0);
}
}The Python implementation uses recursive calls to perform DFS from the starting node. A set keeps track of visited nodes, ensuring no duplicates in paths.
The time complexity is O(V + E) and the space used is O(V), taking recursion stack into account.
Approach 3: Counting
According to the problem, we need to divide the array into two parts, and the elements in each part are all distinct. Therefore, we can count the occurrence of each element in the array. If an element appears three or more times, it cannot satisfy the problem's requirements. Otherwise, we can divide the array into two parts.
The time complexity is O(n), and the space complexity is O(n). Where n is the length of the array.
function isPossibleToSplit(nums: number[]): boolean {
const cnt: number[] = Array(101).fill(0);
for (const x of nums) {
if (++cnt[x] >= 3) {
return false;
}
}
return true;
}func isPossibleToSplit(nums []int) bool {
cnt := [101]int{}
for _, x := range nums {
cnt[x]++
if cnt[x] >= 3 {
return false
}
}
return true
}use std::collections::HashMap;
impl Solution {
pub fn is_possible_to_split(nums: Vec<i32>) -> bool {
let mut cnt = HashMap::new();
for &x in &nums {
*cnt.entry(x).or_insert(0) += 1;
}
*cnt.values().max().unwrap_or(&0) < 3
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| Breadth-First Search (BFS) Approach | The time complexity is O(V + E) and the space complexity is O(V) for the queue and visited dictionary. |
| Depth-First Search (DFS) Approach | The time complexity is O(V + E) and the space used is O(V), taking recursion stack into account. |
| Counting | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Depth-First Search (Backtracking) | O(2^n) | O(n) | When exploring all possible assignments or explaining the constraint logically |
| Breadth-First Search (State Exploration) | O(2^n) | O(2^n) | Useful for conceptual state modeling or teaching assignment problems |
| Hash Counting (Optimal) | O(n) | O(n) | Best for interviews and production; quickly validates the frequency constraint |
Frequently asked questions
Is Split the Array easy or hard?
Split the Array is classified as an Easy problem. The key insight is recognizing that each value can appear at most twice since the array is split into two arrays with unique elements.
Split the Array Python/Java solution
In Python, use a dictionary or collections.Counter to track frequencies and verify no element appears more than twice. In Java, use a HashMap<Integer, Integer> or an integer frequency array if the value range is small.
How to solve Split the Array in O(n)?
Traverse the array and maintain a frequency map using a hash table. For each number, increment its count and check whether the count exceeds two. If any value appears more than twice, return false immediately; otherwise return true after processing the array.
What is the best approach for Split the Array?
The optimal approach uses a hash map to count the frequency of each number. Since the array must be split into two arrays with unique elements, any value appearing more than twice makes the split impossible. A single pass counting solution runs in O(n) time with O(n) space.
Is Split the Array asked at Google/Amazon/Meta?
Variants of frequency counting and duplicate constraints appear frequently in interviews at companies like Amazon, Google, and Meta. Problems that reduce to hash counting and constraint validation are common in early interview rounds.
What data structure is used in Split the Array?
A hash table (hash map) is the primary data structure. It stores the frequency of each value while iterating through the array, allowing constant-time updates and checks.
What is the time complexity of Split the Array?
The optimal hash counting solution runs in O(n) time because you iterate through the array once and update a frequency map. Space complexity is O(n) in the worst case when all elements are distinct.
