Skip to content

Commit cf93ab4

Browse files
authored
Merge branch 'TheAlgorithms:master' into master
2 parents 5edd47b + b76115e commit cf93ab4

File tree

5 files changed

+263
-43
lines changed

5 files changed

+263
-43
lines changed

data_structures/queue/double_ended_queue.py

+45-17
Original file line numberDiff line numberDiff line change
@@ -242,31 +242,45 @@ def pop(self) -> Any:
242242
Removes the last element of the deque and returns it.
243243
Time complexity: O(1)
244244
@returns topop.val: the value of the node to pop.
245-
>>> our_deque = Deque([1, 2, 3, 15182])
246-
>>> our_popped = our_deque.pop()
247-
>>> our_popped
245+
>>> our_deque1 = Deque([1])
246+
>>> our_popped1 = our_deque1.pop()
247+
>>> our_popped1
248+
1
249+
>>> our_deque1
250+
[]
251+
252+
>>> our_deque2 = Deque([1, 2, 3, 15182])
253+
>>> our_popped2 = our_deque2.pop()
254+
>>> our_popped2
248255
15182
249-
>>> our_deque
256+
>>> our_deque2
250257
[1, 2, 3]
258+
251259
>>> from collections import deque
252260
>>> deque_collections = deque([1, 2, 3, 15182])
253261
>>> collections_popped = deque_collections.pop()
254262
>>> collections_popped
255263
15182
256264
>>> deque_collections
257265
deque([1, 2, 3])
258-
>>> list(our_deque) == list(deque_collections)
266+
>>> list(our_deque2) == list(deque_collections)
259267
True
260-
>>> our_popped == collections_popped
268+
>>> our_popped2 == collections_popped
261269
True
262270
"""
263271
# make sure the deque has elements to pop
264272
assert not self.is_empty(), "Deque is empty."
265273

266274
topop = self._back
267-
self._back = self._back.prev_node # set new back
268-
# drop the last node - python will deallocate memory automatically
269-
self._back.next_node = None
275+
# if only one element in the queue: point the front and back to None
276+
# else remove one element from back
277+
if self._front == self._back:
278+
self._front = None
279+
self._back = None
280+
else:
281+
self._back = self._back.prev_node # set new back
282+
# drop the last node, python will deallocate memory automatically
283+
self._back.next_node = None
270284

271285
self._len -= 1
272286

@@ -277,11 +291,17 @@ def popleft(self) -> Any:
277291
Removes the first element of the deque and returns it.
278292
Time complexity: O(1)
279293
@returns topop.val: the value of the node to pop.
280-
>>> our_deque = Deque([15182, 1, 2, 3])
281-
>>> our_popped = our_deque.popleft()
282-
>>> our_popped
294+
>>> our_deque1 = Deque([1])
295+
>>> our_popped1 = our_deque1.pop()
296+
>>> our_popped1
297+
1
298+
>>> our_deque1
299+
[]
300+
>>> our_deque2 = Deque([15182, 1, 2, 3])
301+
>>> our_popped2 = our_deque2.popleft()
302+
>>> our_popped2
283303
15182
284-
>>> our_deque
304+
>>> our_deque2
285305
[1, 2, 3]
286306
>>> from collections import deque
287307
>>> deque_collections = deque([15182, 1, 2, 3])
@@ -290,17 +310,23 @@ def popleft(self) -> Any:
290310
15182
291311
>>> deque_collections
292312
deque([1, 2, 3])
293-
>>> list(our_deque) == list(deque_collections)
313+
>>> list(our_deque2) == list(deque_collections)
294314
True
295-
>>> our_popped == collections_popped
315+
>>> our_popped2 == collections_popped
296316
True
297317
"""
298318
# make sure the deque has elements to pop
299319
assert not self.is_empty(), "Deque is empty."
300320

301321
topop = self._front
302-
self._front = self._front.next_node # set new front and drop the first node
303-
self._front.prev_node = None
322+
# if only one element in the queue: point the front and back to None
323+
# else remove one element from front
324+
if self._front == self._back:
325+
self._front = None
326+
self._back = None
327+
else:
328+
self._front = self._front.next_node # set new front and drop the first node
329+
self._front.prev_node = None
304330

305331
self._len -= 1
306332

@@ -432,3 +458,5 @@ def __repr__(self) -> str:
432458
import doctest
433459

434460
doctest.testmod()
461+
dq = Deque([3])
462+
dq.pop()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from __future__ import annotations
2+
3+
4+
def largest_divisible_subset(items: list[int]) -> list[int]:
5+
"""
6+
Algorithm to find the biggest subset in the given array such that for any 2 elements
7+
x and y in the subset, either x divides y or y divides x.
8+
>>> largest_divisible_subset([1, 16, 7, 8, 4])
9+
[16, 8, 4, 1]
10+
>>> largest_divisible_subset([1, 2, 3])
11+
[2, 1]
12+
>>> largest_divisible_subset([-1, -2, -3])
13+
[-3]
14+
>>> largest_divisible_subset([1, 2, 4, 8])
15+
[8, 4, 2, 1]
16+
>>> largest_divisible_subset((1, 2, 4, 8))
17+
[8, 4, 2, 1]
18+
>>> largest_divisible_subset([1, 1, 1])
19+
[1, 1, 1]
20+
>>> largest_divisible_subset([0, 0, 0])
21+
[0, 0, 0]
22+
>>> largest_divisible_subset([-1, -1, -1])
23+
[-1, -1, -1]
24+
>>> largest_divisible_subset([])
25+
[]
26+
"""
27+
# Sort the array in ascending order as the sequence does not matter we only have to
28+
# pick up a subset.
29+
items = sorted(items)
30+
31+
number_of_items = len(items)
32+
33+
# Initialize memo with 1s and hash with increasing numbers
34+
memo = [1] * number_of_items
35+
hash_array = list(range(number_of_items))
36+
37+
# Iterate through the array
38+
for i, item in enumerate(items):
39+
for prev_index in range(i):
40+
if ((items[prev_index] != 0 and item % items[prev_index]) == 0) and (
41+
(1 + memo[prev_index]) > memo[i]
42+
):
43+
memo[i] = 1 + memo[prev_index]
44+
hash_array[i] = prev_index
45+
46+
ans = -1
47+
last_index = -1
48+
49+
# Find the maximum length and its corresponding index
50+
for i, memo_item in enumerate(memo):
51+
if memo_item > ans:
52+
ans = memo_item
53+
last_index = i
54+
55+
# Reconstruct the divisible subset
56+
if last_index == -1:
57+
return []
58+
result = [items[last_index]]
59+
while hash_array[last_index] != last_index:
60+
last_index = hash_array[last_index]
61+
result.append(items[last_index])
62+
63+
return result
64+
65+
66+
if __name__ == "__main__":
67+
from doctest import testmod
68+
69+
testmod()
70+
71+
items = [1, 16, 7, 8, 4]
72+
print(
73+
f"The longest divisible subset of {items} is {largest_divisible_subset(items)}."
74+
)

graphs/check_bipartite_graph_dfs.py

+47-26
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,55 @@
1-
# Check whether Graph is Bipartite or Not using DFS
1+
from collections import defaultdict
22

33

4-
# A Bipartite Graph is a graph whose vertices can be divided into two independent sets,
5-
# U and V such that every edge (u, v) either connects a vertex from U to V or a vertex
6-
# from V to U. In other words, for every edge (u, v), either u belongs to U and v to V,
7-
# or u belongs to V and v to U. We can also say that there is no edge that connects
8-
# vertices of same set.
9-
def check_bipartite_dfs(graph):
10-
visited = [False] * len(graph)
11-
color = [-1] * len(graph)
4+
def is_bipartite(graph: defaultdict[int, list[int]]) -> bool:
5+
"""
6+
Check whether a graph is Bipartite or not using Depth-First Search (DFS).
127
13-
def dfs(v, c):
14-
visited[v] = True
15-
color[v] = c
16-
for u in graph[v]:
17-
if not visited[u]:
18-
dfs(u, 1 - c)
8+
A Bipartite Graph is a graph whose vertices can be divided into two independent
9+
sets, U and V such that every edge (u, v) either connects a vertex from
10+
U to V or a vertex from V to U. In other words, for every edge (u, v),
11+
either u belongs to U and v to V, or u belongs to V and v to U. There is
12+
no edge that connects vertices of the same set.
1913
20-
for i in range(len(graph)):
21-
if not visited[i]:
22-
dfs(i, 0)
14+
Args:
15+
graph: An adjacency list representing the graph.
2316
24-
for i in range(len(graph)):
25-
for j in graph[i]:
26-
if color[i] == color[j]:
27-
return False
17+
Returns:
18+
True if there's no edge that connects vertices of the same set, False otherwise.
2819
29-
return True
20+
Examples:
21+
>>> is_bipartite(
22+
... defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4], 3: [1], 4: [2]})
23+
... )
24+
False
25+
>>> is_bipartite(defaultdict(list, {0: [1, 2], 1: [0, 2], 2: [0, 1]}))
26+
True
27+
"""
3028

29+
def depth_first_search(node: int, color: int) -> bool:
30+
visited[node] = color
31+
return any(
32+
visited[neighbour] == color
33+
or (
34+
visited[neighbour] == -1
35+
and not depth_first_search(neighbour, 1 - color)
36+
)
37+
for neighbour in graph[node]
38+
)
3139

32-
# Adjacency list of graph
33-
graph = {0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: []}
34-
print(check_bipartite_dfs(graph))
40+
visited: defaultdict[int, int] = defaultdict(lambda: -1)
41+
42+
return all(
43+
not (visited[node] == -1 and not depth_first_search(node, 0)) for node in graph
44+
)
45+
46+
47+
if __name__ == "__main__":
48+
import doctest
49+
50+
result = doctest.testmod()
51+
52+
if result.failed:
53+
print(f"{result.failed} test(s) failed.")
54+
else:
55+
print("All tests passed!")

greedy_methods/gas_station.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
Task:
3+
There are n gas stations along a circular route, where the amount of gas
4+
at the ith station is gas_quantities[i].
5+
6+
You have a car with an unlimited gas tank and it costs costs[i] of gas
7+
to travel from the ith station to its next (i + 1)th station.
8+
You begin the journey with an empty tank at one of the gas stations.
9+
10+
Given two integer arrays gas_quantities and costs, return the starting
11+
gas station's index if you can travel around the circuit once
12+
in the clockwise direction otherwise, return -1.
13+
If there exists a solution, it is guaranteed to be unique
14+
15+
Reference: https://leetcode.com/problems/gas-station/description
16+
17+
Implementation notes:
18+
First, check whether the total gas is enough to complete the journey. If not, return -1.
19+
However, if there is enough gas, it is guaranteed that there is a valid
20+
starting index to reach the end of the journey.
21+
Greedily calculate the net gain (gas_quantity - cost) at each station.
22+
If the net gain ever goes below 0 while iterating through the stations,
23+
start checking from the next station.
24+
25+
"""
26+
from dataclasses import dataclass
27+
28+
29+
@dataclass
30+
class GasStation:
31+
gas_quantity: int
32+
cost: int
33+
34+
35+
def get_gas_stations(
36+
gas_quantities: list[int], costs: list[int]
37+
) -> tuple[GasStation, ...]:
38+
"""
39+
This function returns a tuple of gas stations.
40+
41+
Args:
42+
gas_quantities: Amount of gas available at each station
43+
costs: The cost of gas required to move from one station to the next
44+
45+
Returns:
46+
A tuple of gas stations
47+
48+
>>> gas_stations = get_gas_stations([1, 2, 3, 4, 5], [3, 4, 5, 1, 2])
49+
>>> len(gas_stations)
50+
5
51+
>>> gas_stations[0]
52+
GasStation(gas_quantity=1, cost=3)
53+
>>> gas_stations[-1]
54+
GasStation(gas_quantity=5, cost=2)
55+
"""
56+
return tuple(
57+
GasStation(quantity, cost) for quantity, cost in zip(gas_quantities, costs)
58+
)
59+
60+
61+
def can_complete_journey(gas_stations: tuple[GasStation, ...]) -> int:
62+
"""
63+
This function returns the index from which to start the journey
64+
in order to reach the end.
65+
66+
Args:
67+
gas_quantities [list]: Amount of gas available at each station
68+
cost [list]: The cost of gas required to move from one station to the next
69+
70+
Returns:
71+
start [int]: start index needed to complete the journey
72+
73+
Examples:
74+
>>> can_complete_journey(get_gas_stations([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]))
75+
3
76+
>>> can_complete_journey(get_gas_stations([2, 3, 4], [3, 4, 3]))
77+
-1
78+
"""
79+
total_gas = sum(gas_station.gas_quantity for gas_station in gas_stations)
80+
total_cost = sum(gas_station.cost for gas_station in gas_stations)
81+
if total_gas < total_cost:
82+
return -1
83+
84+
start = 0
85+
net = 0
86+
for i, gas_station in enumerate(gas_stations):
87+
net += gas_station.gas_quantity - gas_station.cost
88+
if net < 0:
89+
start = i + 1
90+
net = 0
91+
return start
92+
93+
94+
if __name__ == "__main__":
95+
import doctest
96+
97+
doctest.testmod()

0 commit comments

Comments
 (0)