Problem

You have access to ranked lists of songs for various users. Each song is represented as an integer, and more preferred songs appear earlier in each list. For example, the list [4, 1, 7] indicates that a user likes song 4 the best, followed by songs 1 and 7.

Given a set of these ranked lists, interleave them to create a playlist that satisfies everyone’s priorities.

For example, suppose your input is {[1, 7, 3], [2, 1, 6, 7, 9], [3, 9, 5]}. In this case a satisfactory playlist could be [2, 1, 6, 7, 3, 9, 5].

Examples

Example 1

1
2
3
Input: [[1, 7, 3], [2, 1, 6, 7, 9], [3, 9, 5]]
Output: [2, 1, 6, 7, 3, 9, 5]
Explanation: This playlist respects all user preferences - each user's songs appear in their preferred relative order.

Example 2

1
2
3
Input: [[1, 2, 3], [3, 2, 1]]
Output: [1, 3, 2]
Explanation: Song 1 must come before 2 and 3 (from first list), song 3 must come before 2 and 1 (from second list). One valid ordering is [1, 3, 2].

Example 3

1
2
3
Input: [[1, 2], [2, 3], [1, 3]]
Output: [1, 2, 3]
Explanation: All lists agree that 1 comes before 3, and the transitivity gives us 1  2  3.

Example 4

1
2
3
Input: [[1], [2], [3]]
Output: [1, 2, 3]
Explanation: No relative ordering constraints, so any order of unique songs works.

Solution

Method 1 - Topological Sort with Kahn’s Algorithm

Intuition

This problem is essentially asking for a topological ordering that respects multiple partial orderings. Each user’s ranked list defines a partial order (song A must come before song B if A appears before B in their list). We can model this as a directed acyclic graph (DAG) where edges represent “comes before” relationships, then find a topological ordering.

Approach

  1. Build directed graph: For each user’s list, create edges from each song to all songs that come after it
  2. Calculate in-degrees: Count incoming edges for each song
  3. Apply Kahn’s algorithm:
    • Start with songs that have no prerequisites (in-degree = 0)
    • Process songs in order, removing them and updating in-degrees
    • Add songs to result as their in-degree becomes 0
  4. Handle conflicts: If multiple songs have in-degree 0, we can choose any (different orderings possible)

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Solution {
public:
    vector<int> createPlaylist(vector<vector<int>>& lists) {
        unordered_set<int> allSongs;
        unordered_map<int, unordered_set<int>> graph;
        unordered_map<int, int> inDegree;
        
        // Collect all songs and initialize in-degree
        for (auto& list : lists) {
            for (int song : list) {
                allSongs.insert(song);
                if (inDegree.find(song) == inDegree.end()) {
                    inDegree[song] = 0;
                }
            }
        }
        
        // Build graph and calculate in-degrees
        for (auto& list : lists) {
            for (int i = 0; i < list.size(); i++) {
                for (int j = i + 1; j < list.size(); j++) {
                    int from = list[i], to = list[j];
                    if (graph[from].find(to) == graph[from].end()) {
                        graph[from].insert(to);
                        inDegree[to]++;
                    }
                }
            }
        }
        
        // Kahn's algorithm
        queue<int> q;
        for (auto& pair : inDegree) {
            if (pair.second == 0) {
                q.push(pair.first);
            }
        }
        
        vector<int> ans;
        while (!q.empty()) {
            int curr = q.front();
            q.pop();
            ans.push_back(curr);
            
            for (int next : graph[curr]) {
                inDegree[next]--;
                if (inDegree[next] == 0) {
                    q.push(next);
                }
            }
        }
        
        return ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func createPlaylist(lists [][]int) []int {
    allSongs := make(map[int]bool)
    graph := make(map[int]map[int]bool)
    inDegree := make(map[int]int)
    
    // Collect all songs and initialize in-degree
    for _, list := range lists {
        for _, song := range list {
            allSongs[song] = true
            if _, exists := inDegree[song]; !exists {
                inDegree[song] = 0
                graph[song] = make(map[int]bool)
            }
        }
    }
    
    // Build graph and calculate in-degrees
    for _, list := range lists {
        for i := 0; i < len(list); i++ {
            for j := i + 1; j < len(list); j++ {
                from, to := list[i], list[j]
                if !graph[from][to] {
                    graph[from][to] = true
                    inDegree[to]++
                }
            }
        }
    }
    
    // Kahn's algorithm
    queue := []int{}
    for song, degree := range inDegree {
        if degree == 0 {
            queue = append(queue, song)
        }
    }
    
    ans := []int{}
    for len(queue) > 0 {
        curr := queue[0]
        queue = queue[1:]
        ans = append(ans, curr)
        
        for next := range graph[curr] {
            inDegree[next]--
            if inDegree[next] == 0 {
                queue = append(queue, next)
            }
        }
    }
    
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
    public List<Integer> createPlaylist(List<List<Integer>> lists) {
        Set<Integer> allSongs = new HashSet<>();
        Map<Integer, Set<Integer>> graph = new HashMap<>();
        Map<Integer, Integer> inDegree = new HashMap<>();
        
        // Collect all songs and initialize in-degree
        for (List<Integer> list : lists) {
            for (int song : list) {
                allSongs.add(song);
                inDegree.putIfAbsent(song, 0);
                graph.putIfAbsent(song, new HashSet<>());
            }
        }
        
        // Build graph and calculate in-degrees
        for (List<Integer> list : lists) {
            for (int i = 0; i < list.size(); i++) {
                for (int j = i + 1; j < list.size(); j++) {
                    int from = list.get(i), to = list.get(j);
                    if (!graph.get(from).contains(to)) {
                        graph.get(from).add(to);
                        inDegree.put(to, inDegree.get(to) + 1);
                    }
                }
            }
        }
        
        // Kahn's algorithm
        Queue<Integer> q = new LinkedList<>();
        for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
            if (entry.getValue() == 0) {
                q.offer(entry.getKey());
            }
        }
        
        List<Integer> ans = new ArrayList<>();
        while (!q.isEmpty()) {
            int curr = q.poll();
            ans.add(curr);
            
            for (int next : graph.get(curr)) {
                inDegree.put(next, inDegree.get(next) - 1);
                if (inDegree.get(next) == 0) {
                    q.offer(next);
                }
            }
        }
        
        return ans;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution:
    def createPlaylist(self, lists: List[List[int]]) -> List[int]:
        allSongs = set()
        graph = defaultdict(set)
        inDegree = defaultdict(int)
        
        # Collect all songs and initialize in-degree
        for lst in lists:
            for song in lst:
                allSongs.add(song)
                inDegree[song] = inDegree.get(song, 0)
        
        # Build graph and calculate in-degrees
        for lst in lists:
            for i in range(len(lst)):
                for j in range(i + 1, len(lst)):
                    from_song, to_song = lst[i], lst[j]
                    if to_song not in graph[from_song]:
                        graph[from_song].add(to_song)
                        inDegree[to_song] += 1
        
        # Kahn's algorithm
        queue = deque([song for song in inDegree if inDegree[song] == 0])
        ans = []
        
        while queue:
            curr = queue.popleft()
            ans.append(curr)
            
            for next_song in graph[curr]:
                inDegree[next_song] -= 1
                if inDegree[next_song] == 0:
                    queue.append(next_song)
        
        return ans

Complexity

  • ⏰ Time complexity: O(N + E), where N is the total number of unique songs and E is the total number of ordering relationships. Building the graph takes O(S²L) where S is average songs per list and L is number of lists, and topological sort takes O(N + E).
  • 🧺 Space complexity: O(N + E), for storing the graph, in-degree map, and queue.

Method 2 - Priority-Based Merge

Intuition

Instead of building a full graph, we can think of this as merging multiple sorted lists while maintaining the relative order within each list. We use a priority-based approach where we track the position of each song in each user’s list and always pick the song that appears earliest in any remaining list.

Approach

  1. Initialize pointers: Keep track of current position in each user’s list
  2. Calculate priorities: For each available song, find its earliest position across all lists
  3. Select next song: Choose the song with the highest priority (earliest average position)
  4. Update state: Move pointers forward and remove selected song from consideration
  5. Repeat: Continue until all songs are added to playlist

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
public:
    vector<int> createPlaylist(vector<vector<int>>& lists) {
        set<int> allSongs;
        for (auto& list : lists) {
            for (int song : list) {
                allSongs.insert(song);
            }
        }
        
        vector<int> ans;
        vector<int> pointers(lists.size(), 0);
        
        while (ans.size() < allSongs.size()) {
            map<int, double> priorities;
            
            // Calculate priority for each available song
            for (int i = 0; i < lists.size(); i++) {
                for (int j = pointers[i]; j < lists[i].size(); j++) {
                    int song = lists[i][j];
                    if (priorities.find(song) == priorities.end()) {
                        priorities[song] = j;
                    } else {
                        priorities[song] = min(priorities[song], (double)j);
                    }
                }
            }
            
            // Find song with highest priority (lowest position)
            int nextSong = -1;
            double bestPriority = 1e9;
            for (auto& pair : priorities) {
                if (pair.second < bestPriority) {
                    bestPriority = pair.second;
                    nextSong = pair.first;
                }
            }
            
            ans.push_back(nextSong);
            
            // Update pointers
            for (int i = 0; i < lists.size(); i++) {
                while (pointers[i] < lists[i].size() && 
                       find(ans.begin(), ans.end(), lists[i][pointers[i]]) != ans.end()) {
                    pointers[i]++;
                }
            }
        }
        
        return ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
func createPlaylist(lists [][]int) []int {
    allSongs := make(map[int]bool)
    for _, list := range lists {
        for _, song := range list {
            allSongs[song] = true
        }
    }
    
    ans := []int{}
    pointers := make([]int, len(lists))
    used := make(map[int]bool)
    
    for len(ans) < len(allSongs) {
        priorities := make(map[int]float64)
        
        // Calculate priority for each available song
        for i, list := range lists {
            for j := pointers[i]; j < len(list); j++ {
                song := list[j]
                if !used[song] {
                    if _, exists := priorities[song]; !exists {
                        priorities[song] = float64(j)
                    } else {
                        if float64(j) < priorities[song] {
                            priorities[song] = float64(j)
                        }
                    }
                }
            }
        }
        
        // Find song with highest priority (lowest position)
        nextSong := -1
        bestPriority := 1e9
        for song, priority := range priorities {
            if priority < bestPriority {
                bestPriority = priority
                nextSong = song
            }
        }
        
        ans = append(ans, nextSong)
        used[nextSong] = true
        
        // Update pointers
        for i := range lists {
            for pointers[i] < len(lists[i]) && used[lists[i][pointers[i]]] {
                pointers[i]++
            }
        }
    }
    
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
    public List<Integer> createPlaylist(List<List<Integer>> lists) {
        Set<Integer> allSongs = new HashSet<>();
        for (List<Integer> list : lists) {
            allSongs.addAll(list);
        }
        
        List<Integer> ans = new ArrayList<>();
        int[] pointers = new int[lists.size()];
        Set<Integer> used = new HashSet<>();
        
        while (ans.size() < allSongs.size()) {
            Map<Integer, Double> priorities = new HashMap<>();
            
            // Calculate priority for each available song
            for (int i = 0; i < lists.size(); i++) {
                List<Integer> list = lists.get(i);
                for (int j = pointers[i]; j < list.size(); j++) {
                    int song = list.get(j);
                    if (!used.contains(song)) {
                        priorities.put(song, Math.min(priorities.getOrDefault(song, Double.MAX_VALUE), (double)j));
                    }
                }
            }
            
            // Find song with highest priority (lowest position)
            int nextSong = -1;
            double bestPriority = Double.MAX_VALUE;
            for (Map.Entry<Integer, Double> entry : priorities.entrySet()) {
                if (entry.getValue() < bestPriority) {
                    bestPriority = entry.getValue();
                    nextSong = entry.getKey();
                }
            }
            
            ans.add(nextSong);
            used.add(nextSong);
            
            // Update pointers
            for (int i = 0; i < lists.size(); i++) {
                while (pointers[i] < lists.get(i).size() && 
                       used.contains(lists.get(i).get(pointers[i]))) {
                    pointers[i]++;
                }
            }
        }
        
        return ans;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution:
    def createPlaylist(self, lists: List[List[int]]) -> List[int]:
        allSongs = set()
        for lst in lists:
            allSongs.update(lst)
        
        ans = []
        pointers = [0] * len(lists)
        used = set()
        
        while len(ans) < len(allSongs):
            priorities = {}
            
            # Calculate priority for each available song
            for i, lst in enumerate(lists):
                for j in range(pointers[i], len(lst)):
                    song = lst[j]
                    if song not in used:
                        priorities[song] = min(priorities.get(song, float('inf')), j)
            
            # Find song with highest priority (lowest position)
            nextSong = min(priorities.keys(), key=lambda x: priorities[x])
            
            ans.append(nextSong)
            used.add(nextSong)
            
            # Update pointers
            for i in range(len(lists)):
                while pointers[i] < len(lists[i]) and lists[i][pointers[i]] in used:
                    pointers[i] += 1
        
        return ans

Complexity

  • ⏰ Time complexity: O(N² × L × S), where N is number of unique songs, L is number of lists, and S is average songs per list. For each of N iterations, we scan all remaining songs across all lists.
  • 🧺 Space complexity: O(N), for storing the result, used set, and priority map.