diff --git a/README.md b/README.md index b777198..d056e01 100644 --- a/README.md +++ b/README.md @@ -11,57 +11,57 @@ In this exercise you should be able to: ## Description -Given a set of N puppies (numbered 0, 1, 2, ..., N - 1), we would like to split them into two groups of any size to use two play areas. +Given a set of N puppies, we would like to split them into two groups of any size to use two play areas. Some dogs have a history of fighting with specific other dogs and shouldn't be put into the same play area. -Formally, if dislikes[i] = [a, b], it means dog i is not allowed to put in the same group as dog a or dog b. +Formally, if `dislikes[i] = [a, b]`, it means dog `i` is not allowed to put in the same group as dog `a` or dog `b`. -Return true if and only if it is possible to split the dogs into two groups where no fighting will occur. +Dislike is mutual. If dog `a` that dislikes dog `b`, dog `b` also dislikes dog `a`. -### Example 1 +Return `True` if and only if it is possible to split the dogs into two groups where no fighting will occur. Otherwise, return `False`. +### Example 1 +*Input*: +``` python +dislikes = { + "Fido": [], + "Nala": ["Cooper", "Spot"], + "Cooper": ["Nala", "Bruno"], + "Spot": ["Nala"], + "Bruno": ["Cooper"] + } ``` -Input: dislikes = [ [], - [2, 3], - [1, 4], - [1], - [2] - ] -Output: true -``` - -Explanation: group1 [0, 1, 4], group2 [2, 3] +*Output*: `True` +Explanation: group1: `["Fido", "Nala", "Bruno"]`, group2: `["Cooper", "Spot"]` ### Example 2 - -``` -Input: dislikes = [ [], - [2, 3], - [1, 3], - [1, 2] - ] -Output: false +*Input*: +```python +dislikes = { + "Fido": [], + "Nala": ["Cooper", "Spot"], + "Coooper": ["Nala", "Spot"], + "Spot": ["Nala", "Cooper"] + } ``` - -Explanation: All the nodes 1-3 are interconnected and so there is no way to split them up. +*Output*: `False` +Explanation: The nodes `Nala`, `Cooper`, and `Spot` are interconnected and so there is no way to split them up. ### Example 3 - -``` -Input: dislikes = [ [], - [2, 5], - [1, 3], - [2, 4], - [3, 5], - [1, 4] - ] -Output: false +*Input*: +```Python +dislikes = { + "Fido": [], + "Nala": ["Cooper", "Cali"], + "Cooper": ["Nala", "Spot"], + "Spot": ["Cooper", "Bruno"], + "Bruno": ["Spot", "Cali"], + "Cali": ["Nala", "Bruno"] + } ``` - -### Note - -The graph is undirected, so if dog 1 dislikes dog 2, then dog 2 also dislikes dog 1. +*Output*: `False` +Explanation: There is no way to split `Nala`, `Cooper`, `Spot`, `Bruno`, and `Cali` up into two groups such that they are all separated from the dogs they dislike. ## Source diff --git a/graphs/possible_bipartition.py b/graphs/possible_bipartition.py index ca55677..4d96804 100644 --- a/graphs/possible_bipartition.py +++ b/graphs/possible_bipartition.py @@ -5,8 +5,35 @@ def possible_bipartition(dislikes): """ Will return True or False if the given graph can be bipartitioned without neighboring nodes put into the same partition. - Time Complexity: ? - Space Complexity: ? + Time Complexity: O(N + E) where N is the number of nodes & E is the number of edges + Space Complexity: O(N) where N is the number of nodes """ - pass + if len(dislikes) == 0: + return True + + groups = {} + for key in dislikes.keys(): + groups[key] = 0 + + first_pup = list(dislikes.keys())[0] + groups[first_pup] = 1 + queue = [first_pup] + visited = set() + + for pup in dislikes: + while queue: + current = queue.pop(0) + visited.add(current) + + if dislikes[current]: + for dog in dislikes[current]: + if groups[dog] == 0: + groups[dog] = groups[current] + 1 + queue.append(dog) + elif groups[dog] == groups[current]: + return False + if pup not in visited: + queue.append(pup) + return True + \ No newline at end of file diff --git a/tests/test_possible_bipartition.py b/tests/test_possible_bipartition.py index 88ba735..7dfbdd8 100644 --- a/tests/test_possible_bipartition.py +++ b/tests/test_possible_bipartition.py @@ -3,12 +3,13 @@ def test_example_1(): # Arrange - dislikes = [ [], - [2, 3], - [1, 4], - [1], - [2] - ] + dislikes = { + "Fido": [], + "Rufus": ["James", "Alfie"], + "James": ["Rufus", "T-Bone"], + "Alfie": ["Rufus"], + "T-Bone": ["James"] + } # Act answer = possible_bipartition(dislikes) @@ -17,12 +18,12 @@ def test_example_1(): assert answer def test_example_2(): - # Arrange - dislikes = [ [], - [2, 3], - [1, 3], - [1, 2] - ] + dislikes = { + "Fido": [], + "Rufus": ["James", "Alfie"], + "James": ["Rufus", "Alfie"], + "Alfie": ["Rufus", "James"] + } # Act answer = possible_bipartition(dislikes) @@ -32,13 +33,14 @@ def test_example_2(): def test_example_r(): # Arrange - dislikes = [ [], - [2, 5], - [1, 3], - [2, 4], - [3, 5], - [1, 4] - ] + dislikes = { + "Fido": [], + "Rufus": ["James", "Scruffy"], + "James": ["Rufus", "Alfie"], + "Alfie": ["Rufus", "T-Bone"], + "T-Bone": ["Alfie", "Scruffy"], + "Scruffy": ["Rufus", "T-Bone"] + } # Act answer = possible_bipartition(dislikes) @@ -48,14 +50,15 @@ def test_example_r(): def test_will_return_true_for_a_graph_which_can_be_bipartitioned(): # Arrange - dislikes = [ [3, 6], - [2, 5], - [1, 3], - [0, 2], - [5], - [1, 4], - [0] - ] + dislikes = { + "Fido": ["Alfie", "Bruno"], + "Rufus": ["James", "Scruffy"], + "James": ["Rufus", "Alfie"], + "Alfie": ["Fido", "James"], + "T-Bone": ["Scruffy"], + "Scruffy": ["Rufus", "T-Bone"], + "Bruno": ["Fido"] + } # Act answer = possible_bipartition(dislikes) @@ -65,14 +68,15 @@ def test_will_return_true_for_a_graph_which_can_be_bipartitioned(): def test_will_return_false_for_graph_which_cannot_be_bipartitioned(): # Arrange - dislikes = [ [3, 6], - [2, 5], - [1, 3], - [0, 2, 4], - [3, 5], - [1, 4], - [0] - ] + dislikes = { + "Fido": ["Alfie", "Bruno"], + "Rufus": ["James", "Scruffy"], + "James": ["Rufus", "Alfie"], + "Alfie": ["Fido", "James", "T-Bone"], + "T-Bone": ["Alfie", "Scruffy"], + "Scruffy": ["Rufus", "T-Bone"], + "Bruno": ["Fido"] + } # Act answer = possible_bipartition(dislikes) @@ -82,23 +86,85 @@ def test_will_return_false_for_graph_which_cannot_be_bipartitioned(): def test_will_return_true_for_empty_graph(): - assert possible_bipartition([]) + assert possible_bipartition({}) def test_will_return_false_for_another_graph_which_cannot_be_bipartitioned(): # Arrange - dislikes = [ [3, 6], - [2, 5], - [1, 3], - [0, 2, 4], - [3, 5], - [1, 4], - [0], - [8], - [7] - ] + dislikes = { + "Fido": ["Alfie", "Bruno"], + "Rufus": ["James", "Scruffy"], + "James": ["Rufus", "Alfie"], + "Alfie": ["Fido", "James", "T-Bone"], + "T-Bone": ["Alfie", "Scruffy"], + "Scruffy": ["Rufus", "T-Bone"], + "Bruno": ["Fido"], + "Spot": ["Nala"], + "Nala": ["Spot"] + } # Act answer = possible_bipartition(dislikes) # Assert assert not answer + +def test_multiple_dogs_at_beginning_dont_dislike_any_others(): + # Arrange + dislikes = { + "Fido": [], + "Rufus": [], + "James": [], + "Alfie": ["T-Bone"], + "T-Bone": ["Alfie", "Scruffy"], + "Scruffy": ["T-Bone"], + "Bruno": ["Nala"], + "Spot": ["Nala"], + "Nala": ["Bruno", "Spot"] + } + + # Act + answer = possible_bipartition(dislikes) + + # Assert + assert answer + + +def test_multiple_dogs_in_middle_dont_dislike_any_others(): + # Arrange + dislikes = { + "Fido": ["Alfie"], + "Rufus": ["James", "Scruffy"], + "James": ["Rufus", "Alfie"], + "Alfie": ["Fido", "James"], + "T-Bone": [], + "Scruffy": ["Rufus"], + "Bruno": [], + "Spot": ["Nala"], + "Nala": ["Spot"] + } + + # Act + answer = possible_bipartition(dislikes) + + # Assert + assert answer + +def test_will_return_false_for_disconnected_graph_which_cannot_be_bipartitioned(): + # Arrange + dislikes = { + "Ralph": ["Tony"], + "Tony": ["Ralph"], + "Fido": ["Alfie", "Bruno"], + "Rufus": ["James", "Scruffy"], + "James": ["Rufus", "Alfie"], + "Alfie": ["Fido", "James", "T-Bone"], + "T-Bone": ["Alfie", "Scruffy"], + "Scruffy": ["Rufus", "T-Bone"], + "Bruno": ["Fido"] + } + + # Act + answer = possible_bipartition(dislikes) + + # Assert + assert not answer \ No newline at end of file