Hello everyone 👋, In today's post I wanted to talk about the famous Dijkstra algorithm, we are going to see what it is, what it's used for, how it works, and the implementation of the algorithm in c++ altho the idea can be applied in any language.

Before we continue I highly recommend you read the following resources:

The Dijkstra algorithm is a graph search algorithm that solves the single-source shortest path problem for a graph with non-negative edge weights, producing a shortest path tree, this means that we can know the **shortest** distance from the origin node to the rest of the nodes in a graph. It is very similar to the BFS algorithm, however, the BFS algorithm does not give the shortest distance in a **weighted** graph.

Dijkstra's algorithm is a popular choice for finding the shortest path in a graph, and it has many practical applications. Some examples of where Dijkstra's algorithm is used include:

Navigation: Dijkstra's algorithm can be used to find the shortest route between two locations on a map, taking into account factors such as distance, time, and traffic.

Network routing: In computer networking, Dijkstra's algorithm can be used to find the shortest path between two nodes in a network, such as routers or servers.

Traffic management: Dijkstra's algorithm can be used to optimize traffic flow by finding the shortest routes for vehicles to take, based on factors such as distance, time, and traffic.

Resource allocation: Dijkstra's algorithm can be used to find the most efficient allocation of resources, such as in a supply chain or manufacturing process.

Artificial intelligence: In artificial intelligence and machine learning, Dijkstra's algorithm can be used to find the shortest path in a graph representing a problem space, such as in pathfinding or planning.

These are just some examples, in my case I use Dijkstra for competitive programming problems, we'll look at an example later in this post.

The Dijkstra algorithm works by starting from a specified node and repeatedly expanding to the closest adjacent node until it reaches the destination node, very similar to the BFS algorithm however the Dijkstra algorithm uses a priority queue instead of a queue, this is necessary since the adjacent node with the less cost has the highest priority. At each step, the algorithm removes the node at the front of the priority queue and updates the distances of its adjacent nodes based on the distance from the starting node to the current node.

To implement Dijkstra's algorithm, we need to keep track of the following information for each node:

The distance from the starting node to the current node.

The previous node in the path from the starting node to the current node.

Initially, the distance of the starting node is set to 0, and the distances of all other nodes are set to infinity. As the algorithm progresses, it updates the distances of the neighboring nodes based on the distance from the starting node to the current node.

Here is an outline of the steps:

Set the distance of the starting node to 0 and the distance of all other nodes to infinity.

Add the starting node to the priority queue

While the priority queue is not empty:

Remove the node with the highest priority (lowest distance) from the priority queue, (The Top).

Check if the current node has a cost greater than what it already has, if this is true, it means that the cost is not being improved and we can

`continue`

.Update the distances of its neighboring nodes based on the distance from the current node to the neighboring node.

Save that the neighboring node comes from the current node in the

`BACK`

array, this is going to help us to recreate the path.Add the neighboring nodes to the priority queue.

Once the priority queue is empty, we can reconstruct the shortest path from the starting node to the destination node by following the previous nodes back from the destination node to the starting node using the

`BACK`

.

This algorithm is more efficient and can handle larger graphs in comparison to other algorithms like the "Bellman-Ford". However, the Dijkstra algorithm requires that there are no negative weight edges.

In order to better understand the algorithm I made the following animation:

the input for the example graph shown above would be:

```
8 11
0 3 3
0 2 8
1 3 1
1 6 1
2 5 8
2 4 7
3 7 4
4 7 2
4 6 3
5 7 8
6 7 7
1
```

The last number is the starting node.

Below you can find the implementation of the algorithm in c++, remember that there are several ways to code this algorithm and I'm just showing the version I use. you can find the explanation below the code cell.

```
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
struct node{
int node;
ll cost;
bool operator < (const struct node &b) const{
if(cost != b.cost) return cost > b.cost;
if(node != b.node) return node > b.node;
return 0;
}
};
const ll INF = 1E18;
int main(){
int n, m; cin>>n>>m;
vector<vector<node>> adj(n);
vector<int> BACK(n, -1);
for(int i = 0; i < m; i++){
int u, v; ll w;
cin>>u>>v>>w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
vector<ll> dist(n, INF);
int source; cin >> source;
dist[source] = 0;
priority_queue<node> dijkstra;
dijkstra.push({source, 0});
while(!dijkstra.empty()){
node c = dijkstra.top();
dijkstra.pop();
if(c.cost > dist[c.node]) continue;
for(node adj: adj[c.node]){
if(c.cost + adj.cost >= dist[adj.node])continue;
dist[adj.node] = c.cost + adj.cost;
BACK[adj.node] = c.node;
dijkstra.push({adj.node,c.cost + adj.cost});
}
}
for(int i = 0; i < n; i++){
cout<<i<<": cost:"<<dist[i]<<" path:";
vector<int> path;
int current = i;
while (current != -1) {
path.push_back(current);
current = BACK[current];
}
reverse(path.begin(), path.end());
for(auto e:path)cout << e << " ==> ";
cout << endl;
}
return 0;
}
```

First, we define our node struct, which is going to have two properties, the name or in this case number of the node, and the cost or weight. Notice that we also define the "<" operator, this is necessary and very important for the priority queue to know how to order the nodes by cost. Then we input the graph itself in the exact same way as we do with a BFS algorithm and we set the source distance to 0. Notice how we are going to keep track of the distances in the `dist`

vector. Additionally, we also define the Back vector, with -1 as the default value.

Now we define our Priority Queue which I like to call "Dijkstra" and we push the origin node while it's not empty, we are going to perform the following actions:

remove the top of the priority queue. Check if the node has a cost greater than the already saved distance for that node in the `dist`

vector, in which case you continue.

```
if(c.cost > dist[c.node]) continue;
```

This is going to help to not come back from where you came from. Else, we are going to iterate through the adjacent nodes to that node and once again check if the distance from the current node plus the neighboring node improves the cost. If it is greater, that means worse so continue.

```
if(c.cost + adj.cost >= dist[adj.node])continue;
```

else we update the distance to that adjacent node, the back, and push that adjacent node with the updated cost.

```
dist[adj.node] = c.cost + adj.cost;
BACK[adj.node] = c.node;
dijkstra.push({adj.node,c.cost + adj.cost});
```

Once the Dijkstra is empty, we can prove that it worked by showing the minimum cost from the origin to the rest of the nodes, AND the path that it took.

```
for(int i = 0; i < n; i++){
cout<<i<<": cost:"<<dist[i]<<" path:";
vector<int> path;
int current = i;
while (current != -1) {
path.push_back(current);
current = BACK[current];
}
reverse(path.begin(), path.end());
for(auto e:path)cout << e << " ==> ";
cout << endl;
}
```

The process to recover the path is exactly the same as with the BFS.

Now you can try to run the full code with the example input and you should get the following output:

```
0: cost:4 path:1 ==> 3 ==> 0 ==>
1: cost:0 path:1 ==>
2: cost:11 path:1 ==> 6 ==> 4 ==> 2 ==>
3: cost:1 path:1 ==> 3 ==>
4: cost:4 path:1 ==> 6 ==> 4 ==>
5: cost:13 path:1 ==> 3 ==> 7 ==> 5 ==>
6: cost:1 path:1 ==> 6 ==>
7: cost:5 path:1 ==> 3 ==> 7 ==>
```

In conclusion, the Dijkstra algorithm is a powerful tool for finding the shortest path between two nodes in a graph. Whether you are a beginner or an experienced programmer, understanding how the Dijkstra algorithm works is an important skill to have in your toolkit. Whether you are navigating a map, routing network traffic, or optimizing the allocation of resources, the Dijkstra algorithm can help you find the most efficient solution to a wide range of problems. So if you want to learn more about the Dijkstra algorithm and how to implement it in your own projects, be sure to check out the resources and code samples provided in this post.

remember that this is a skill that takes a lifetime to master so don't feel frustrated if you don't get it right away because this isn't easy, but I hope some of the guidelines we saw today were helpful and the basic foundations on this topic were well understood, remember, there is still a lot to learn and we definitively didn't cover everything on Dijkstra, but I hope this was a good beginner overview and that you managed to grasp the basic ideas.

Let me know in the comments what you thought about this post and let me know what you will like to see next. Stay tuned!

]]>Hello Everyone! 👋, welcome back, in this post I want to show you one of my favorite trivial stack problems and some variations, I will explain the problem, go through the most simple but inefficient answer, and then the correct and brilliant solution using stacks.

Given an Array of positive integers, for each array element output the nearest smaller element, i.e., the first smaller element that comes after the element in the array, if no element exists, output -1.

N (the size of the array) 1 <= N <= 10^6

Ai (Element in the array) 1 <= Ai <= 10^6

```
INPUT:
6
3 2 4 5 1 6
OUTPUT:
2 1 1 1 -1 -1
```

```
INPUT:
3
3 1 2
OUTPUT:
1 -1 -1
```

If you understood the problem then it's very likely that you already thought of this solution, the algorithm is very simple, for each element simply iterate from that position to the end of the array in the search for a smallest element, and if found, immediately output that element, finish the search, and continue to the next item. If not found output -1.

Based on the explanation above, I highly recommend that you try to code the solution for yourself, and then compare it to the code implementation below.

```
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n; cin >> n;
vector<int>arr(n); for(auto &e:arr)cin >> e; // getting input
for(int i=0; i<n; i++){
int current = arr[i];
bool found = false;
for(int j=i; j<n; j++){
if(arr[j] < current){cout << arr[j] << " "; found=true; break;}
}
if(!found)cout << -1 << " ";
}
return 0;
}
```

When I first made this solution, I thought: "Hey! this is a great simple solution let's submit this code!", but that's because I didn't had a great understanding of complexity and efficiency at the time, and I got a TLE verdict 😔.

If you have never heard of time and space complexity and Big-O notation, I highly suggest that you read my post: Efficiency and Big-O Notation Overview

This code has a time complexity of ** O(n^2)** and a space complexity of

`n`

. The space complexity is determined by the use of the `vector`

container, which has a size of `n`

.in the worst case, ** n** can be 1,000,000, with this algorithm the amount of operations is going to be approximately 1,000,000,000,000 in a worst-case scenario which is why we get a Time Limit Exceeded (TLE) verdict.

Now that we understand why the iterative solution is not the right choice, let's think of a different solution, this one involves a stack. If you have never heard about a "stack" I highly recommend you to read my "Stacks and Queues a Beginners Overview" post and come back here.

We can iterate the array backward from right to left, and for each element perform the following algorithm:

If our helper stack is empty, output -1, add the element to the stack, and continue to the next element.

else, check the element that is on the top of the stack, and if it's smaller than the current element, output the top of the stack and add the current element to the stack, don't delete the top!

if the top of the stack is not smaller, remove the top of the stack until either steps 1 or 2 are fulfilled.

If you are struggling to understand this solution, check out the animation below of the algorithm in action with the first input example.

I highly encourage you to code the algorithm as described above in the language of your choice. here is my implementation in c++:

```
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
int main(){
int n; cin >> n;
vector<int>arr(n); for(auto &e:arr)cin >> e;
stack<int>S;
vector<int>result;
for(int i=n-1; i>=0; i--){
while(true){
if(S.size() == 0){result.push_back(-1); S.push(arr[i]); break;}
if(S.top() < arr[i]){
result.push_back(S.top());
S.push(arr[i]);
break;
}else{
S.pop();
}
}
}
reverse(result.begin(), result.end());
for(auto &e:result)cout << e << " ";
return 0;
}
```

The efficiency of this algorithm depends on the total number of stack operations. if the current item is larger than the top element of the stack it gets directly added to the stack in constant time ** O(1)**. However, if it's not, we might have to remove several elements of the stack which has a worst-case of

Up until this moment, the problem consists of finding the minimum nearest element to the right, however, you can also find the Maximum item by just changing a single character. before: `if(S.top() < arr[i])`

After: `if(S.top() > arr[i])`

. Another common variation is to find the minimum nearest element to the **left**. To do that simply iterate the array normally from left to right and don't reverse the `result`

array.

```
int main(){
int n; cin >> n;
vector<int>arr(n); for(auto &e:arr)cin >> e;
stack<int>S;
vector<int>result;
for(int i=0; i<n; i++){
while(true){
if(S.size() == 0){result.push_back(-1); S.push(arr[i]); break;}
if(S.top() < arr[i]){
result.push_back(S.top());
S.push(arr[i]);
break;
}else{
S.pop();
}
}
}
for(auto &e:result)cout << e << " ";
return 0;
}
```

You've reached the end of this post, hope you enjoyed the solution for this classical competitive programming problem and learned something new. Here are some related posts:

https://blog.garybricks.com/stacks-and-queues-a-beginners-overview

https://blog.garybricks.com/efficiency-and-big-o-notation-overview-with-python-examples

https://blog.garybricks.com/programmers-guide-to-solving-computational-problems

Let me know in the comments what you thought about this post and let me know what you will like to see next. See you in the next post, stay tuned!

]]>Hello everyone! 👋, In this Post, we'll dive deeper into Graphs in programming, we'll take a look at the most common algorithms for solving competitive programming problems using Graphs, DFS, and BFS, and how to implement them in c++ while we solve some interesting problems.

If you have no idea what a graph is and how to represent one check out my previous post: Graphs Introduction For Beginners

Depth-first search is a straightforward graph traversal technique. The idea is that the algorithm begins at a specified node and from there proceeds to visit all nodes that are reachable from the current node. This algorithm always follows a single path in the graph as long as it finds new **unvisited** nodes. If the algorithm has no new unvisited nodes it returns to previous nodes and begins to explore in other directions. With DFS, each node is visited only once.

Let's see how a DFS algorithm would process the following graph:

In this case, we are starting from node 1, and the algorithm proceeds to visit the neighbor node 2, the process repeats for nodes 3 and 4 until node 5 where there are no longer new **unvisited** nodes, that's the moment our algorithm returns to node 4 and chooses another route, in this case, node 6. Once the algorithm reaches node 6 there are no longer new unvisited nodes so the search terminates. The time complexity of DFS is `O(n+m)`

where **n** is the number of nodes and **m** is the number of edges.

DFS can be very easy and convenient to implement using recursion, the main idea is that you define a function that receives the current node and marks it as visited, then checks for all of the adjacent nodes to that node and sends the same function with those nodes as long as they are not visited.

```
#include <iostream>
#include <vector>
using namespace std;
int n, k;
vector<vector<int>> GRAPH;
vector<bool> visited;
void DFS(int current) {
visited[current] = 1; // marking the active node as visited
cout << "currently on:" << current << endl;
for (auto node : GRAPH[current]) // iterate through all of it's neighboors
if (!visited[node]) DFS(node); // send DFS if it's a new node
}
int main() {
cin >> n >> k;
GRAPH.resize(n + 1);
visited.resize(n + 1);
for (int i = 0; i < k; i++) {
int a, b;
cin >> a >> b;
GRAPH[a].push_back(b);
GRAPH[b].push_back(a);
}
DFS(1); // starting algorithm from node 1
return 0;
}
```

In the code cell above you can see the implementation using an adjacency list to represent the graph and a simple vector of booleans to keep track of the visited nodes. In this case, we are sending the DFS algorithm starting from node 1 but you can try and make the algorithm start from a different node to see what the output is.

Breadth-first search is also a traversal algorithm that is very commonly used for searching a node, finding the shortest path in a graph, and for simulations. BFS visits all the nodes but in increasing order based on their distance from the starting node, because of this, BFS can help us calculate the distance from the starting node to all other nodes.

Let's see what a BFS algorithm would look like on our example graph.

Just like a DFS, BFS starts from a specified node, and from there visits **ALL** of the nodes that are exactly 1 node away, once all nodes of that level are visited, the algorithm continues with the second level and this process continues until all levels are visited.

The time complexity of BFS is `O(n+m)`

exactly the same as DFS.

The implementation of a BFS algorithm is not as simple as with the DFS but in this section, we'll see the most typical method that is based on a queue of nodes. The algorithm works as follows: First, we define a queue of nodes, I normally like to name it "BFS", and we push the origin (starting node). Here is the interesting part: while the queue is not empty, we are going to save the front of the queue and remove it no matter what, then we check all of the adjacent nodes to that saved node and push them to the queue but **only if they are not visited**, this process is going to repeat until the queue is empty.

If you don't know what a queue is, check out this section on my post about stacks and queues: https://blog.garybricks.com/stacks-and-queues-a-beginners-overview#heading-what-is-a-queue just understand the main idea and come back here since we are going to use c++ handy implementation.

```
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
vector<vector<int>> GRAPH(n + 1);
vector<int> distance(n + 1, -1);
for (int i = 0; i < k; i++) {
int a, b;
cin >> a >> b;
GRAPH[a].push_back(b);
GRAPH[b].push_back(a);
}
queue<int> BFS;
BFS.push(1);
distance[1] = 0;
while (!BFS.empty()) {
int current = BFS.front();
BFS.pop();
cout << "currently on:" << current << endl;
for (auto node : GRAPH[current])
if (distance[node] == -1) {
distance[node] = distance[current] + 1;
BFS.push(node);
}
}
for (int i = 1; i <= n; i++)
cout << "Node:" << i << " is " << distance[i]
<< " nodes away from node 1\n";
return 0;
}
```

In the code cell above you can see my implementation in c++ using an adjacency list to represent the graph, notice how instead of using a vector of bools to see if a node has been visited we use a vector of ints to save the "distance" to node 1. For this case, I decided to initialize that distance vector with -1, "-1" is going to represent a node that has not been visited. Just like DFS, try to change the origin to see what the output is.

In this section, we'll see some competitive programming problems I've faced and how they were solved using a DFS or BFS algorithm, at the same time we dive a little deeper into the topic and learn new things, read the problems and try to solve them by yourself, don't worry, the solutions are at the end of this section.

Given a Matrix of characters representing a maze, where "*" represents a wall, "E" represents the entry to the maze, and "X" the exit, print whether is possible or not to reach "X" starting from "E" through the empty spaces. Examples:

Given a Matrix of characters of size **n * m** in which **"w"** represents water and **"L"** represents land, output how many puddles of water are on the map and the size of the biggest one.

A puddle is considered valid if it's **completely** surrounded by land, this means that any body of water touching the edges of the map does not count as a puddle.

Any body of water is considered connected to another if it's orthogonally or diagonally adjacent to it. See the examples below:

Given a Matrix of characters of size n*m in which "." represents water, "#" land, "*" contaminated water, and "$" represents an oil plant that has a spill that every day spreads to an orthogonally adjacent square of water. Output what the Matrix will look like after k days. Example:

It's 2020 and covid is all over the place, as a responsible human you are trying to stay as furthest away from other people.

Given a Matrix of characters of size n*m in which "#" represents a wall, "." an empty space, and "G" a person. find the empty square in which you can be the furthest from everyone. Example:

Output the coordinates of the safest place, for the examples above this would be the answer:

```
3 2
4 4
```

You are given a chess board of size n*n, the coordinates of an enemy queen, the coordinates of your king, and an exit coordinate, your job is to output whether or not the king can reach the exit coordinate without getting in check and following the king movement rules. If it's possible, output the coordinates the king took to reach the exit, and if it's not possible, output -1;

IMPORTANT:

For this problem, the enemy queen will never move.

If it's possible to reach the exit coordinate you have to output the

**shortest**path the king took, if there are several answers output any of them.

This is a very classical problem, however, the tricky part of this problem was to figure out how to represent the graph in order to apply the DFS algorithm.

```
#include <iostream>
#include <vector>
using namespace std;
struct node {
int i, j;
};
int n, m;
vector<string> MAZE;
vector<vector<bool>> visited;
node E, X;
void getInput() {
string row;
cin >> n >> m;
getline(cin, row);
for (int i = 0; i < n; i++) {
getline(cin, row);
MAZE.push_back(row);
}
}
void findStartEnd() {
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) {
if (MAZE[i][j] == 'e') E = {i, j};
if (MAZE[i][j] == 'x') X = {i, j};
}
}
bool valid(int i, int j) {
if (i >= 0 && j >= 0 && i < n && j < m && MAZE[i][j] != '*' && !visited[i][j])
return true;
return false;
}
void DFS(node c) {
visited[c.i][c.j] = true;
if (valid(c.i + 1, c.j)) DFS({c.i + 1, c.j});
if (valid(c.i - 1, c.j)) DFS({c.i - 1, c.j});
if (valid(c.i, c.j + 1)) DFS({c.i, c.j + 1});
if (valid(c.i, c.j - 1)) DFS({c.i, c.j - 1});
}
int main() {
getInput();
findStartEnd();
visited.resize(n, vector<bool>(m, 0));
DFS(E);
if (visited[X.i][X.j])
cout << "There is a solution!";
else
cout << "IT'S IMPOSSIBLE TO ESCAPE";
return 0;
}
```

This is the solution I came up with, it's a bit longer than what we are used to, but don't worry, I carefully separated every part in order to make it as readable as possible. Notice how when working with this type of problem is very comfortable to have your graph and visited matrix global. You might be wondering where our Graph is, we'll get to that in a second, first, let's get the input using our `getInput()`

function. You might already have noticed the problem does not give you a graph as we have seen above, this is where you must think and realize that you can represent the nodes as the coordinates in the `MAZE`

matrix, that's why I defined a node structure that simply saves an **x** and **y** coordinates. Then, we call our `findStartEnd()`

function, this simply iterates through the `MAZE`

and finds the starting and ending nodes. Now we initialize our visited matrix, and finally, we send our DFS starting from the start node. The DFS works as follows: first, we mark the current node as visited as we normally do, here's the interesting part: notice how we don't have our adjacency matrix, that is because it's not necessary, we literally check the adjacent positions in the Matrix! to do that I defined a `valid()`

function that simply checks if it's a valid node for the DFS to go, that function takes care of the walls, if it's visited, and for out-of-bounds cases, this is a perfect example of an implicit graph.

The tricky part of this problem was to figure out if a puddle is valid, and its size. Below you can find the code with the explanation.

```
#include <iostream>
#include <vector>
using namespace std;
int n, m, numPuddles = 0, BiggestPuddle = 0;
vector<vector<char>> MAP;
vector<vector<bool>> visited;
vector<int> X = {1, -1, 0, 0, 1, -1, 1, -1};
vector<int> Y = {0, 0, 1, -1, 1, -1, -1, 1};
void init() {
cin >> n >> m;
MAP.resize(n, vector<char>(m));
visited.resize(n, vector<bool>(m));
for (auto &row : MAP)
for (auto &e : row) cin >> e;
}
bool valid(int i, int j) {
if (i >= 0 && j >= 0 && i < n && j < m && !visited[i][j] && MAP[i][j] == 'W')
return true;
return false;
}
void DFS(int i, int j, int &size, bool &puddle) {
visited[i][j] = true;
size++;
if (i == 0 || j == 0 || i == n - 1 || j == m - 1) puddle = false;
for (int d = 0; d < 8; d++) {
if (valid(i + X[d], j + Y[d])) DFS(i + X[d], j + Y[d], size, puddle);
}
}
int main() {
init();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++)
if (MAP[i][j] == 'W' && !visited[i][j]) {
int size = 0;
bool isPuddle = true;
DFS(i, j, size, isPuddle);
if (isPuddle) {
BiggestPuddle = max(BiggestPuddle, size);
numPuddles++;
}
}
}
cout << "There are: " << numPuddles
<< " puddles in the map, the largest one has a size of: "
<< BiggestPuddle;
return 0;
}
```

To solve this problem, we iterate over the map of chars and if we find water we are going to assume it's a valid puddle and send the `dfs`

on that specific coordinate, notice how we have two more parameters: `size`

and `isPuddle`

which are always passed by reference: `&`

, this is important since we want to be able to modify the variables. `DFS`

is not very different from the others, we mark the current position as visited and send the `DFS`

to the eight possible adjacent coordinates, the only difference is that we always increment the `size`

variable and if we reach an edge of the Map, we are going to say that the puddle is not valid. Once the `DFS`

is done, we check if it's a valid puddle, if true, we increment the number of puddles found, and we check if it's bigger than the biggest puddle.

Just by looking at the example, you can tell that a BFS algorithm behaves exactly the same, the tricky part was how to stop the BFS at the **k** day. Below you can find my code with the explanation.

```
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
struct node { int i, j, level; };
int N, M, days;
vector<vector<char>> MAP;
int X[4] = {0,1,0,-1};
int Y[4] = {1,0,-1,0};
node source;
void get_input() {
cin >> N >> M >> days;
MAP.resize(N, vector<char>(M));
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
cin >> MAP[i][j];
if (MAP[i][j] == '$') source = {i, j, 0};
}
}
}
void print() {
for (auto e : MAP) {
for (auto c : e) cout << c;
cout << "\n";
}
}
bool valid(int i, int j){ return i >= 0 && j >= 0 && i < N && j < M && MAP[i][j] == '.';}
int main() {
get_input();
queue<node> BFS;
BFS.push(source);
while (!BFS.empty()) {
node e = BFS.front();
BFS.pop();
if (e.level >= days) break;
for(int i=0; i<4; i++){
if(valid(e.i + X[i], e.j + Y[i])){
MAP[e.i + X[i]][e.j + Y[i]] = '*';
BFS.push({e.i+X[i], e.j+Y[i], e.level+1});
}
}
}
print();
return 0;
}
```

Just like the previous problems, we represented a node as the coordinates on the matrix, and notice how this node has an extra **level** variable, this is going to help us know how far we are from the origin which is going to help us stop the BFS.

The algorithm works as follows:

while the BFS is not empty get the current node and remove it

check if the node has a distance greater or equal to k. if so, end the algorithm.

for each valid adjacent node, modify that node to become contaminated and add it to the BFS with a level increment.

Once the BFS finishes, print the final Map. This problem cannot be solved with a DFS due to the land that can ruin the simulation and give an incorrect result.

One of the first ideas most people have is to send a BFS from every single empty space and get the distance to the **closest** person and by doing that, find the best square, however, this idea is extremely inefficient. Another idea can be to send a BFS from every single person and get the distance to every single empty square and then fuse them together on a final matrix that contains all of the minimum distances, and lastly just search for the biggest value.

This is the correct idea, however, creating a distance matrix for each person can bring a lot of implementation and memory problems, the best and easiest solution involves using just a single BFS and distance matrix, it turns out that is totally possible and easy to send a BFS algorithm from several origins. Below you can see my solution.

```
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
struct node {int i, j;};
int N, M;
vector<vector<char>> MAP;
vector<vector<int>> V; // Distance Matrix
int X[4] = {0,1,0,-1};
int Y[4] = {1,0,-1,0};
vector<node> people;
void get_input() { // get Map, initialize the visited matrix, find the people
cin >> N >> M;
MAP.resize(N, vector<char>(M));
V.resize(N, vector<int>(M, -1)); // -1 represents not visited
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
cin >> MAP[i][j];
if (MAP[i][j] == 'G') people.push_back({i, j});
}
}
}
// checks if the node is inside bounds, is an empty square, and is not visited
bool valid(int i, int j){ return i >= 0 && j >= 0 && i < N && j < M && MAP[i][j] == '.' && V[i][j]==-1;}
int main() {
get_input();
// instead of adding a single source, we add all people.
queue<node> BFS;
for(auto e:people){BFS.push(e); V[e.i][e.j] = 0;}
while (!BFS.empty()) {
node e = BFS.front();
BFS.pop();
for(int i=0; i<4; i++){
if(valid(e.i + X[i], e.j + Y[i])){
V[e.i + X[i]][e.j + Y[i]] = V[e.i][e.j] + 1;
BFS.push({e.i+X[i], e.j+Y[i]});
}
}
}
node result; // final search for the maximum value
int maxi = -1;
for(int i=0; i<N; i++){
for(int j=0; j<M; j++)if(V[i][j] > maxi){
maxi = V[i][j];
result = {i, j};
}
}
cout << result.i << " " << result.j;
return 0;
}
```

This is a very standard BFS the big difference is how we define our visited matrix, for a DFS algorithm we normally use booleans but for a BFS we can actually save ints that represent the distance, and by doing that we can know the minimum distance from the origin to ANY other square which is going to be really helpful. And lastly, instead of sending the BFS from a single origin, we initialize the BFS with all of the nodes representing people, after that the algorithm remains the same.

Once the BFS is done, we are going to have our perfect visited matrix with all of the correct distances and we just need to find the maximum value.

This problem was very similar to the escape maze problem, however, the problem was that if there was a solution, you had to output the shortest route the king took, because of this, a **BFS** algorithm was the right choice. Below you can see my implementation with the explanation.

```
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
struct node {
int x, y;
};
vector<int> X = {1, -1, 0, 0, 1, -1, 1, -1}; // directions
vector<int> Y = {0, 0, 1, -1, 1, -1, -1, 1}; // directions
int n, qx, qy, kx, ky, ex, ey;
vector<vector<bool>> M; // 0 = empty, 1 = check
vector<vector<int>> V; // distance MAP
vector<vector<node>> BACK; // Helper for recreating path
vector<node> path; // final path
void initBoard() {
M.resize(n, vector<bool>(n, 0));
V.resize(n, vector<int>(n, -1));
BACK.resize(n, vector<node>(n, {-1, -1}));
for (int i = 0; i < n; i++) {
M[qx][i] = 1;
M[i][qy] = 1;
}
int x = qx, y = qy;
while (x >= 0 && y >= 0) {
M[x][y] = 1;
x--, y--;
}
x = qx, y = qy;
while (x >= 0 && y < n) {
M[x][y] = 1;
x--, y++;
}
x = qx, y = qy;
while (x < n && y >= 0) {
M[x][y] = 1;
x++, y--;
}
x = qx, y = qy;
while (x < n && y < n) {
M[x][y] = 1;
x++, y++;
}
}
// checks if it's not visited, not in check and inside the board.
bool valid(int x, int y) {
return x >= 0 && y >= 0 && x < n && y < n && !M[x][y] && V[x][y] == -1;
}
int main() {
cin >> n >> qx >> qy >> kx >> ky >> ex >> ey;
qx--, qy--, kx--, ky--, ex--, ey--;
initBoard();
queue<node> BFS;
BFS.push({kx, ky});
V[kx][ky] = 0;
while (BFS.size()) {
node current = BFS.front();
BFS.pop();
for (int i = 0; i < 8; i++) {
if (valid(current.x + (X[i]), current.y + (Y[i]))) {
V[current.x + (X[i])][current.y + (Y[i])] = V[current.x][current.y] + 1;
BACK[current.x + (X[i])][current.y + (Y[i])] = current;
BFS.push({current.x + (X[i]), current.y + (Y[i])});
}
}
}
if (V[ex][ey] == -1) { // checking if the escape square was not visited
cout << -1;
return 0;
}
node current = {ex, ey};
while (!(current.x == -1 && current.y == -1)) {
path.push_back(current);
current = BACK[current.x][current.y];
}
reverse(path.begin(), path.end());
cout << "-------------\n";
for (int i = 1; i < path.size(); i++)
cout << path[i].x + 1 << " " << path[i].y + 1 << " => ";
return 0;
}
```

Just like every problem until now, we need to represent our graph, in this case, we can create a Matrix as the chessboard, and the values of the matrix can be either 0 or 1 where 1 is going to represent a square under check. We also create our distance matrix which is going to be helpful.

In order to "reconstruct" the path we are going to need an additional BACK matrix this is a brilliant way to know the square a square comes from. And lastly, we have our directions array that is going to help later on in pointing to the adjacent squares.

First, we initialize our Graph "M" with the `initBoard()`

function which means marking every queen attacking square as "check".

Then we send our BFS on the king square and for each valid adjacent square we update the distance on the Visited Matrix "V", we also update our BACK matrix and send the BFS.

```
if (valid(current.x + (X[i]), current.y + (Y[i]))) {
// the adjacent node is going to have a distance of the current node +1
V[current.x + (X[i])][current.y + (Y[i])] = V[current.x][current.y] + 1;
// the adjacent node is going to come from the current node
BACK[current.x + (X[i])][current.y + (Y[i])] = current;
BFS.push({current.x + (X[i]), current.y + (Y[i])});}
```

Once the BFS finishes. we check if the escape square was not visited, if this is the case, we output -1 else we can reconstruct the path by starting from the escape square and going back until it's no longer possible.

```
node current = {ex, ey};
while (!(current.x == -1 && current.y == -1)) {
path.push_back(current);
current = BACK[current.x][current.y];
}
reverse(path.begin(), path.end());
```

Notice how we created a final path vector of nodes, and we push_back the current position, this is going to give us the final route backward, that's why we have to use the reverse() method.

You've reached the end of this lesson on BFS and DFS, remember that this is a skill that takes a lifetime to master so don't feel frustrated if you don't get it right away because this isn't easy, but I hope some of the guidelines we saw today were helpful and the basic foundations on this topic were well understood, remember, there is still a lot to learn and we definitively didn't cover everything on Graphs, but I hope this was a good beginner overview and that you managed to grasp the basic ideas.

Let me know in the comments what you thought about this post and let me know what you will like to see next. Stay tuned!

]]>Hello everyone! 👋, In this Post, we will take a beginner's dive into Graphs data structure in programming, we'll take a look at what are they, what are they for, the different types of graphs, the differences between graphs and trees, and how to implement a Graph structure using c++,

In Computer Science a graph is a non-linear data structure that can be used to represent complex relationships between objects. Graphs are made up of a finite number of nodes or vertices and the edges that connect them. The vertices are sometimes also referred to as nodes and the edges are lines or arcs that connect any two nodes in the graph. In this post, we will follow the node and edge convention.

A graph can be a possible choice every time there are relations between objects for example in social networking sites where a user can be represented with a node and connections between them are represented with edges, another good example can be found on google maps where a location can be represented as the node, and the path can be represented with the edges, then we can implement an algorithm to find the shortest route between two nodes. Graphs can also be used for solving puzzles with only one solution like mazes.

Null Graph - There are no edges connecting the Nodes.

Trivial Graph - only has one single node and it's the smallest graph possible.

Undirected Graph - Edges don't have any direction, if a node

**A**connects a node**B**the opposite is true.Directed Graph - All edges have a specified direction, the normal convention is that node

**A**connects the node**B**in that order.Connected Graph - Each one of the nodes can visit any other node in the Graph.

Disconnected Graph - If at least one node is not reachable from another node is considered a disconnected Graph.

Complete Graph - Every single node is connected to each other node.

Cycle Graph - A graph where all of the nodes form one perfect cycle.

Cyclic Graph - at least 1 cycle is formed within the Graph.

Bipartite Graph - A graph in which the nodes can be divided into two sets such that the nodes in each set do not contain any edge between them.

Weighted Graph - A graph where all of its edges have a specified value, for example, if each node represents a city, the edges can represent roads with the time it takes to reach another city.

Here are some of the most common and basic operations for Graphs:

- Insertion of Nodes/Edges
- Deletion of Nodes/Edges
- Searching - Uses Algorithms like BFS
- Traversal - Visit all the nodes in the graph using algorithms like DFS

If you have worked with trees before, you might get a little confused about the differences, but in simple terms, just know that trees are just more restricted types of graphs. Every tree is always a graph, but not every graph is a tree. The same is true with Linked Lists and Heaps.

You might be wondering how can a graph be represented with code. Well, there are 2 simple ways to store a graph, using an Adjacency Matrix or an Adjacency List, we'll see both of them in action.

In this method, the graph is stored in the form of a 2D matrix where rows and columns denote the nodes and each entry in the matrix represents either 1(connected) or 0(disconnected), if the graph you are trying to represent is a weighted graph, then you can put the weight instead.

In the image above, you can see how that specific graph would be represented using an adjacency matrix, the node 1 is connected to nodes 2 and 5, that's why on row 1 columns 2 and 5 are 1 which means connected, this process repeats for every single node.

This method is very similar to the 2D matrix because we are going to create a collection of N empty lists, and for each node, we push_back() the adjacent nodes like this:

If you want to use this method to represent a weighted graph then you would have to save pairs in each element of the array, the node, and the weight.

there are several factors to consider when choosing what method to use. If for example, the amount of nodes **N** is very large, then you only have the adjacency list method which in most cases has way less memory usage, however, the Matrix method can perform `O(1)`

constant operation for adding a node as well as for removing a connection which the adjacency list does not. However, if we try to consult the adjacent nodes to a specific node the List method is better since it doesn't have unnecessary nodes stored in it, and on top of that, you can be more creative by ordering the list or changing the List for a priority queue, set, and more.

The problem normally specifies how the input is given, but the standard is as follows:
In the first line **N** and **K** where **N** is the number of nodes in the graph and **K** is the number of edges, then we are given **K** lines, and for each line **A** and **B** representing that the node **A** connects node **B**, the problem normally specifies if it's directed or undirected and if it's a weighted graph, if it is, the third number for each line is added representing the weight of the edge.

The code below uses a 2D vector of booleans because, for this particular example, the graph is Undirected and has no weight.

```
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
// +1 because the graph nodes start from 1 not 0
vector<vector<bool>> GRAPH(n + 1, vector<bool>(n + 1));
for (int i = 0; i < k; i++) {
int nodeA, nodeB;
cin >> nodeA >> nodeB;
GRAPH[nodeA][nodeB] = 1;
GRAPH[nodeB][nodeA] = 1;
}
for (auto row : GRAPH) {
for (auto col : row) cout << col << " ";
cout << endl;
}
return 0;
}
```

Remember that if we wanted to represent a weighted graph, we receive an extra parameter for every edge, normally at the end, with the weight of that connection, and the only thing we would have to do is change the **bool** for **int** on our matrix.

If the problem specifies that the graph is directed, we would have to simply remove: `GRAPH[nodeB][nodeA] = 1;`

and that's it!

In the code cell below you can see the implementation. Notice how it's very similar to the Matrix method.

```
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
// +1 because the graph nodes start from 1 not 0
vector<vector<int>> GRAPH(n + 1);
for (int i = 0; i < k; i++) {
int nodeA, nodeB;
cin >> nodeA >> nodeB;
GRAPH[nodeA].push_back(nodeB);
GRAPH[nodeB].push_back(nodeA);
}
for (int node = 1; node <= n; node++) {
cout << node << " : ";
for (auto adjacent : GRAPH[node]) cout << adjacent << ", ";
cout << endl;
}
return 0;
}
```

I personally like this method more because we are saving a lot of memory and it's more readable and easy to understand in my opinion. Remember that if we wanted to add weight to this graph, instead of doing: `.push_back(nodeB);`

we can: `.push_back({nodeB, weight});`

and change the vector type from int, to pair.

You've reached the end of this lesson on Graphs, remember that this is a skill that takes a lifetime to master so don't feel frustrated if you don't get it right away because this isn't easy, but I really hope some of the guidelines we saw today were helpful and the basic foundations on this topic were well understood, remember, there is still a lot to learn and we definitively didn't cover everything on Graphs, but I hope this was a good beginner overview and that you managed to grasp the basic ideas.

Let me know in the comments what you thought about this post and let me know what you will like to see next. In the next posts, we are going to dive a little bit more into this topic and we will see the common algorithms that are applied to Graphs, like DFS and BFS, and how we can use them to solve some basic competitive programming problems. Stay tuned!

]]>Welcome Back! In this post, we will take a beginner's dive into segment trees in c++, we will look at what are they. what are they for? and we will solve some basic competitive programming problems together.

Before we begin, I highly suggest that you check out my post about Efficiency And Big O Notation altho it is not required 😉.

Segment Tree is one of the most used data structures in competitive programming, to understand why they are such a big deal, let's think of the following problem:

Let's say we have an array of N elements, like the one shown below:

And we need to perform two types of operations. The first operation will be an `update(i, v)`

, this function will take the index of the element we want to change, and the new value, and replace it.

The second operation is going to calculate to sum in a segment a.k.a range in the array from [ L to R) Note that in the request for the sum we take the left border [ L inclusive, and the right border R ) exclusive. In this post, we will follow this inclusive exclusive standard for all segments. Here are some examples of the `sum(l, r)`

in action:

A Segment tree is a data structure that will allow us to perform both of the operations in **log(n)** time complexity, we will look more in detail at how this works, but before we continue, let's see how we could solve this problem only using iteration.

Let's think of the most basic solution that does not involve a segment tree first.
In the code cell below, we define a vector globally for commodity reasons, and we receive the input **N**, then we resize the vector to the given size and store the data from the terminal.

```
#include <bits/stdc++.h>
#include <vector>
using namespace std;
int n;
vector<int> arr;
int main() {
cin >> n;
arr.resize(n);
for (auto &e : arr) cin >> e;
return 0;
}
```

`sum()`

ProcedureAs you can see from the code cell below, this method is very simple, we simply iterate the array using a for loop, starting from **a** to **b-1**, notice how we don't have to subtract 1 to **b** because we use "<" instead of "<=", and sum all the values to the `result`

variable.

```
int sum(int a, int b) {
int result = 0;
for (int i = a; i < b; i++) result += arr[i];
return result;
}
```

`update()`

ProcedureThis function is the easiest to implement because we only need the following line of code:

```
void update(int index, int new_value) { arr[index] = new_value; }
```

The `Update`

method does not return anything, that's why we use `void()`

, and we don't need to pass the array as a parameter because it's defined globally.

Feel free to copy the code locally on your machine and test it out!

```
int main() {
cin >> n;
arr.resize(n);
for (auto &e : arr) cin >> e;
cout << sum(0, 7) << endl;
cout << sum(0, 1) << endl;
cout << sum(1, 6) << endl;
update(0, 10);
cout << sum(0, 7) << endl;
cout << sum(0, 1) << endl;
cout << sum(1, 6) << endl;
return 0;
}
```

Expected output:

```
29 5 20 34 10 20
```

After coding the iterative solution, you must be thinking: "Hey!, this was easy to code, why don't we just use this approach?", and yes! that solution is simple and easy, but is it efficient? for example what happens if you have the following limits?:

- (1N10^5, 1M10^5) where N is the number of elements, and M is the number of operations that are going to be performed.

`Update()`

Time ComplexitySurprisingly, the `update`

procedure takes `O(1)`

constant time, which means it's super efficient!

`Sum()`

Time ComplexityHowever, this procedure has a worst-case scenario of `O(N)`

this is the case if we ask for a range from 0 to N, the problem is that there can be up to 10^5 of this type of function calls

```
sum(0, 1E5)
sum(0, 1E5)
sum(0, 1E5)
sum(0, 1E5)
sum(0, 1E5)
... 10^5 more times
```

and for every call, we have to iterate the entire array even tho the array stays the same. Because of this, this procedure has a complexity of `O(N*M)`

and because in this case N and M can be the same worst-case value, we say that this solution has a complexity of `O(N)`

which is not efficient at all!

Starting from the previous example array, let's see what a segment tree would look like for this particular array.

This is a binary tree, in the leaves of which there are elements of the original array, and each internal node contains the sum of the numbers in its children.

notice how we added a 0 at the end of the original array because we need to create a Binary Tree, and for the tree to be created perfectly we need the length of the array to be a power of two. If the length of the array is not a power of two, you can extend the array with a neutral value, in this case, a 0, notice how the length of the array will increase no more than twice, so the asymptotic time complexity of the operations will not change.

Now let's see how the operations will look on this tree.

`Update()`

OperationWhen an element of the array changes, what we need to do is traverse the tree until we reach the corresponding leave of the tree, then update the value, and recalculate all the values higher up the tree from the modified leaf. When performing such an operation, we need to recalculate one node on each layer of the tree.

In the animation shown above, you can see the `Update(i, v)`

in action.

`Sum()`

OperationNow let's see what the `Sum()`

operation would look like on our segment tree. Notice how we already have all the values that we need to be stored in the nodes of our tree. In this case, the values are the sum of the segments in the original array.
Observe how the root already has the answer for a query from 0 to 8 which would be a perfect query, however, what happens if we have a nonperfect query like [2, 7)?

The algorithm will be a recursive traversal of the tree that will be interrupted by two cases.

- The segment corresponding to the current node is outside of the query, if this happens it means that all the children are outside of the desired query and we can stop the recursion.
- The segment corresponding to the current node is completely inside of the query, this means that all the children are inside of the range and we need to sum the value of the current node to our result and stop the recursion.

If the current node segment is partially inside the range query, then we simply continue traversing its children until one of both break cases happens.

Altho we haven't touched any of the code for this solution, you might already be thinking about if this solution is actually better than the iterative one, after all, it seems like there are a lot of more elements in our structure, and in the sum operation example, it might seem like there was more traversing and was slower than the other one, but is this really the case?

if the array size **N** is a power of 2, then we have exactly **n-1** internal nodes, summing up to **2n-1** total nodes. But not always do we have n as the power of 2, so we basically need the smallest power of 2 which is greater than n. For good measure, it's normally said that a Segment Tree has a space complexity of `O(4n)`

which is manageable. If you want to dig a bit more about this topic I'll recommend you to check out this link

`Update()`

Time ComplexityWhen performing the update operation, we need to recalculate one node on each layer of the tree. We have only `logn`

layers, so the operation time will be `O(logn)`

.

`Sum()`

Time ComplexityWhen performing the `Sum()`

operation, we don't need to visit all the elements of the tree, thus the general asymptotic time of this procedure will be `O(logn)`

, way more efficient compared to the iterative solution.

Segment Trees are useful whenever you're frequently working with ranges of numerical data. We can use a segment tree if the function *f* is associative and the answer of an interval [l,r] can be derived from the data array.
Here are some common examples:

- Find the sum of all values in a range
- Find the smallest value in a range
- Find the Max value in a rage
- Find the Product of all values in a Range (Multiplication)
- Bitwise operations
`|`

`&`

`^`

In this section, we will look at how to implement the solution for the original sum problem using a genius Segment tree Array representation starting from the code template below.

```
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> arr(n);
for (auto &e : arr) cin >> e;
return 0;
}
```

As you can see from the code cell below, we initialize two global variables, the segment tree size, and the base size.

```
int seg_tree_size, base_size;
vector<long long> segment_tree;
```

We also create our segment_tree that, as we mentioned earlier, it's going to be represented with an array, in this case, **long long** is the data type we use because we are going to be managing sums.

```
void init(const vector<int>& a) {
int arr_size = a.size();
base_size = 1;
while (base_size < arr_size) base_size *= 2;
seg_tree_size = base_size * 2 - 1;
segment_tree.resize(seg_tree_size, 0); // neutral value;
```

In the code cell above, you can see how we use a while loop, to find the smallest power of 2 which is greater than n in order to have a perfect binary tree. Now it's time to fill the tree leaves with the values of the original array.

```
for (int i = seg_tree_size / 2, j = 0; i < seg_tree_size && j < arr_size;
i++, j++) {
segment_tree[i] = a[j];
}
```

Notice how we are representing the Tree as an array where each node has its index, and the furthest left leaf is always going to be the segment tree size divided by two.

Now that we filled all the leaves it's time to fill the rest of the tree

```
for (int i = seg_tree_size / 2 - 1; i >= 0; i--) {
segment_tree[i] = segment_tree[i * 2 + 1] + segment_tree[i * 2 + 2];
}
}
```

Notice how we start from the element in the `(seg_tree_size / 2 - 1)`

position in this case the node at index 6, and thanks to the perfect binary tree that we have, we can easily check for both of its children with `i * 2 + 1`

and `i * 2 + 2`

, In this particular case, we are making the node value to be the sum of both its children, this is normally what is adjusted for other associative properties.

`update()`

ProcedureIn case you forgot, this function is going to receive an index and a value, and update our array in the index to the new value. Thanks to our Array representation method we don't have to traverse the tree until we reach the desired node, instead, we just directly access the element with `i += st_size / 2`

```
void update(int i, int v) {
i += seg_tree_size / 2;
segment_tree[i] = v;
while (i > 0) {
i = (i - 1) / 2;
segment_tree[i] = segment_tree[i * 2 + 1] + segment_tree[i * 2 + 2];
}
}
```

Remember that once the value is updated, all the parent nodes to that leave have to be updated.

`sum()`

ProcedureThis recursive function shall take the query that is, [L and R), a helper left and right "searching" range and the index to the current node in the tree.

```
long long sum(int L, int R, int sl = 0, int sr = base_size, int i = 0) {
if (sl >= R || sr <= L) return 0; // Outside of range
if (sl >= L && sr <= R) return segment_tree[i]; // Inside of range
int mid = (sl + sr) / 2;
return sum(L, R, sl, mid, i * 2 + 1) + sum(L, R, mid, sr, i * 2 + 2);
}
```

Let's see what is happening here, in the first iteration we try to "search" the entire array, that's why `sl`

is set to 0, and `sr`

is set to the `base_size`

, and we start on the root node, a.k.a the node at index 0.

The function will check if the searching range is outside of the query, if it is, we return 0, if instead, the searching range is completely inside of the query, we return the value of the segment tree node, else, it means that we are partially inside of the query, and because this is a binary tree, we can simply trim the search range in 2 with `int mid = (sl + sr) / 2;`

and send the recursive function again to BOTH children.

```
#include <iostream>
#include <vector>
using namespace std;
int seg_tree_size, base_size;
vector<long long> segment_tree;
void init(const vector<int>& a) {
int arr_size = a.size();
base_size = 1;
while (base_size < arr_size) base_size *= 2;
seg_tree_size = base_size * 2 - 1;
segment_tree.resize(seg_tree_size, 0); // valor neutro;
for (int i = seg_tree_size / 2, j = 0; i < seg_tree_size && j < arr_size;
i++, j++) {
segment_tree[i] = a[j];
}
for (int i = seg_tree_size / 2 - 1; i >= 0; i--) {
segment_tree[i] = segment_tree[i * 2 + 1] + segment_tree[i * 2 + 2];
}
}
void update(int i, int v) {
i += seg_tree_size / 2;
segment_tree[i] = v;
while (i > 0) {
i = (i - 1) / 2;
segment_tree[i] = segment_tree[i * 2 + 1] + segment_tree[i * 2 + 2];
}
}
long long sum(int L, int R, int sl = 0, int sr = base_size, int i = 0) {
if (sl >= R || sr <= L) return 0; // Outside of range
if (sl >= L && sr <= R) return segment_tree[i]; // Inside of range
int mid = (sl + sr) / 2;
return sum(L, R, sl, mid, i * 2 + 1) + sum(L, R, mid, sr, i * 2 + 2);
}
int main() {
int n;
cin >> n;
vector<int> arr(n);
for (auto& e : arr) cin >> e;
init(arr); // <- DON'T FORGET TO CREATE THE SEGMENT TREE!
cout << sum(0, 7) << endl;
cout << sum(0, 1) << endl;
cout << sum(1, 6) << endl;
update(0, 10);
cout << sum(0, 7) << endl;
cout << sum(0, 1) << endl;
cout << sum(1, 6) << endl;
return 0;
}
```

Expected output:

```
29 5 20 34 10 20
```

You've reached the end of this lesson on the Segment tree data structure, remember that this is a skill that takes a lifetime to master so don't feel frustrated if you don't get it right away because this isn't easy, but I really hope some of the guidelines we saw today were helpful and the basic foundations on this topic were well understood, remember, there is still a lot to learn and we definitively didn't cover everything on Segment Trees, but I hope this was a good beginner overview and that you grasped the concepts and feel more confident on your programming journey.

Let me know in the comments what you thought about this post and let me know what you will like to see next. See you in the next post, stay tuned!

]]>Hello everyone, welcome back!, today I wanted to give a beginners overview of stacks and queues in Python, we will take a good look at what are they? what are they for? and we will look through some examples together.

The first time I ever heard about stacks, I couldn't help but imagine a stack of Legos or a stack of boxes, and in programming, a stack is a linear list-based data structure that behaves very similar to a stack of boxes in real life.

The main idea is that you can keep putting elements on top, and you have easy quick access to remove or look at the top element. And just like a stack of boxes in real life, if you want access to the bottom element you can't get to it easily, you will need to take the top box over and over again until you reach the box that you want.

In computer science, a stack is an abstract data type that serves as a collection of elements, with two main principal operations:

- Push, which adds an element to the collection, and
Pop, which removes the most recently added element that was not yet removed.

https://en.wikipedia.org/wiki/Stack_(abstract_data_type)

Stacks can be extremely useful and efficient when you only care about the most recent elements or the order in which you see and save elements matters.

For example, if you made a news page, you'll need to access the more recent elements first and more quickly but you may want to show all of the elements when the user scrolls down.

When talking about stacks, it's important to know some specific terminology. When you add an element to a stack, the operation is called "Push" instead of "Insert", and when you take out an element off the stack the operation is called "Pop" instead of "remove", and remember that we always push and pop at the top of the stack. L.I.F.O(Last In, First Out) as the image below illustrates.

Hopefully, you can see that because all you need to do is work with the top of the stack, both operations should take constant time O(1) which can be a really good reason to implement a stack.

Because a stack it's a pretty abstract concept you can actually implement it in two different ways, with an array, or with a Linked list.

A stack has some things in common with an array in the sense that we have a collection of elements and an order to them. One difference with an array is that if we wanted, we could actually access an element in the middle of the array or at the beginning of the array which wouldn't actually be a Stack. Remember that with a stack, we can only access one end, the top.

So how do we implement a stack using an array? well, one way we could look at it is if we rotate an array sideways, we could use it as the container for the stack, then, we could restrict the ways we can interact with this container so that we get the behavior we expect from a Stack, in practical terms, we are going to create a Stack class that has a `push`

and `pop`

methods.

```
class Stack: # defining stack
def __init__(self): # initializing list
self.items = []
myStack = Stack() # creating stack
```

From the code above you can see our `Stack`

class and in the `__init__`

method we initialize our list that will contain all the items of our stack.

`push`

methodThe behavior of this method is very straightforward, simply push a new element into the top of the stack. Python makes this task especially easy thanks to the built-in `append`

method that adds a new element to the end of a list which in this case it's going to be considered the head of our stack. The `append`

method also takes care of the size of the array, so we won't ever have a stack overflow problem.

```
class Stack: # defining stack
def __init__(self): # initializing list
self.items = []
def push(self, element): # push method
self.items.append(element) # append method to add an element to the end of the list.
myStack = Stack() # creating stack
print(myStack.items) # [ ]
myStack.push("element1")
print(myStack.items) # ["element1"]
myStack.push("element 2")
print(myStack.items) # ["element1", "element2"] # the head will be considered the end of the list
```

If you wish to print the stack to get a better understanding of what the stack looks like, you can use `print(myStack.items)`

.

`pop`

methodAs we mentioned earlier, the purpose of this method is to remove the element at the top of the stack, in this case, the last element of the list. Just as we used the `append`

method for adding elements to our stack, python provides us with a built-in `pop`

method that removes the last element of an array, precisely what we need.

```
class Stack:
def __init__(self):
self.items = []
def push(self, element):
self.items.append(element)
def pop(self):
self.items.pop()
myStack = Stack() # creating stack
print(myStack.items) # [ ]
myStack.push("element1")
print(myStack.items) # ["element1"]
myStack.push("element2")
print(myStack.items) # ["element1", "element2"]
myStack.pop()
print(myStack.items) # ["element1"]
myStack.pop()
print(myStack.items) # []
myStack.pop() # IndexError: pop from empty list
print(myStack.items)
```

The pop method takes no arguments, we only pass the `self`

attribute and using `self.items.pop()`

we can easily achieve the desired behavior, but there's a problem, If we call the pop method on an empty array, we are going to get the following error:

IndexError: pop from empty list.

To prevent this error from happening, we are going to check the size of the array first, and if it's not empty, run the `pop`

method like we already have. To check the size of the array we can use the built-in `len()`

method.

```
class Stack:
def __init__(self):
self.items = []
def push(self, element):
self.items.append(element)
def pop(self):
if len(self.items) > 0: # checking if not empty
self.items.pop()
myStack = Stack() # creating stack
print(myStack.items) # [ ]
myStack.push("element1")
print(myStack.items) # ["element1"]
myStack.push("element2")
print(myStack.items) # ["element1", "element2"]
myStack.pop()
print(myStack.items) # ["element1"]
myStack.pop()
print(myStack.items) # []
myStack.pop()
print(myStack.items) # []
```

Lastly, we want our pop() method to return the "popped" element and we are done!

```
class Stack:
def __init__(self): # initializing stack
self.items = []
def push(self, element):
self.items.append(element) # adding new element
def pop(self):
if len(self.items) > 0: # checking if not empty
return self.items.pop() # removing and returning the head
myStack = Stack() # creating stack
print(myStack.items) # []
myStack.push("element1")
print(myStack.items) # ["element1"]
myStack.push("element2")
print(myStack.items) # ["element1", "element2"]
print(myStack.pop()) # element2
print(myStack.pop()) # element1
print(myStack.pop()) # None
```

Previously, we looked at how to implement a stack using an array. While that approach works great in python because the append and pop method takes O(1) constant time, in other languages where there is no such efficiency when working with arrays it might be a good idea to implement a stack using a linked list.

If this is the first time you've heard about a linked list, check out my post titled A Beginners Overview of Linked Lists in Python where I explain what they are, what they are used for, and how to implement one using python. After you've read that article come back and continue from here.

As you can see from the code below, we initialize our Stack class with the `__init__`

method, and we add two attributes: the `head`

which by default it's going to be None since there are no elements in our linked list yet, and we set the `num_elements`

variable to zero for the same reason.

```
class Stack:
def __init__(self):
self.head = None
self.num_elements = 0
```

Because we are going to be implementing a linked list we are going to need a `Node`

class that it's going to be an "element" with a value property and a pointer to the next node in the linked list.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
```

`Push`

MethodNext, we will be adding the `push`

method to the Stack class. As we mentioned earlier, the purpose of this method is to remove the element at the top of the stack, in this case, the top of the stack is going to be the **head **of the linked list.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Stack:
def __init__(self):
self.head = None # No items in the stack, so head should be None
self.num_elements = 0
def push(self, value):
new_node = Node(value)
if self.head is None:
self.head = new_node
else:
new_node.next = self.head
self.head = new_node
self.num_elements += 1
```

From the code above, you can see that once the push method is called, we create a new element "Node" with the value passed and we check if the linked list is empty with `if self.head is None:`

if that is the case, we make the head equal to the newly created node. If not, that means that the linked list is not empty, and the `next`

node is now the old head, and the new head will be the `new_node`

, lastly, we increment the number of elements by 1.

`Pop`

MethodFirst, this method needs to check if the stack is empty, then get the value of the head, which is the top of the stack, and store it in a local variable, then change the head to the next node, and in doing so, removing the top of the stack, finally subtract 1 to the number of elements variable in the stack and return the "popped" value.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Stack:
def __init__(self):
self.head = None
self.num_elements = 0
def push(self, value):
new_node = Node(value)
if self.head is None:
self.head = new_node
else:
new_node.next = self.head
self.head = new_node
self.num_elements += 1
def pop(self):
if self.num_elements == 0:
return None
else:
value = self.head.value
self.head = self.head.next
self.num_elements -= 1
return value
```

We are done! now it's time to test it out and see if it works as expected

```
myStack = Stack()
myStack.push("e1")
myStack.push("e2")
myStack.push("e3")
myStack.push("e4")
print(myStack.num_elements) # 4
print(myStack.pop()) # e4
print(myStack.num_elements) # 3
print(myStack.pop()) # e3
```

In this section, you are going to apply what you learned about stacks with a real-world problem. We will be using stacks to make sure the parentheses are balanced in mathematical expressions such as: ((32+8)(5/2))/(2+6).

Take a string as an input and return True if its parentheses are balanced or False if it is not.

For this problem, you can choose to implement the stack with an array or a Linked list, it's completely up to you.

```
myStack = Stack()
equation = input()
```

```
def solve(equation):
for char in equation:
```

```
def solve(equation):
for char in equation:
if char == "(":
myStack.push(char)
```

As you can see from the code above, if there is an opening parenthesis, we can add that character to the stack.

If we detect a closing character, we must check if the stack is empty, if it is, we will immediately know that the equation is unbalanced and if is not empty then we remove an element from the stack with the pop method.

```
def solve(equation):
for char in equation:
if char == "(":
myStack.push(char)
elif char == ")":
if myStack.num_elements > 0:
myStack.pop()
else:
return False
```

At this point, if the program finishes iterating over the equation it means that the equation is balanced or that there are leftover elements in the stack if this is the case the equation is unbalanced.

```
def solve(equation):
for char in equation:
if char == "(":
myStack.push(char)
elif char == ")":
if myStack.num_elements > 0:
myStack.pop()
else:
return False
if myStack.num_elements > 0:
return False
else:
return True
```

We are done! now it's time to test it out and see if it works as expected.

```
((3^2 + 8)*(5/2))/(2+6)) False
((3^2 + 8)*(5/2))/(2+6) True
```

Just like with Stacks, queues have a very descriptive name and behave very similar to a queue in real life, you can imagine a queue of people that are waiting to get their hands on the best ice cream in town. The way this works is that the person who is at the front of the queue is the first one to receive the ice cream and leave. People can always join at the back of the queue but can only receive the ice cream and leave at the front of the queue. This is called a First In, First out structure, remember that a Stack is a Last In, First out structure very similar but kind of the opposite.

When working with queues it's important to know some queue-related terminology like:

- Head - First (oldest) element added to the queue
- Tail - Last (Newest) element added to the queue
- Enqueue - add an element to the back/tail of the queue
- Deque - A double-ended queue and pronounced "Deck"
- Dequeue - Remove the element at the front of the queue

In this section, we'll look at one way to implement a `Queue`

with an array by creating a Stack class that has the following methods:

- Push() - inserts an element at the back/tail of the queue.
- Pop() - removes an element from the front of the queue.
- Front() - returns the first element/Head of the queue.
- Back() - returns the tail of the queue.
- Size() - returns the number of elements in the queue
- Empty() - returns boolean
`True`

if the queue is empty.

`Queue`

class```
class Queue:
def __init__(self):
self.items = []
self.size = 0
```

As you can see from the code above, the initialization of our `Queue`

class is basically the same as we did with the `Stack`

, we have an items array and a variable to keep track of the size of the `Queue`

.

`push()`

method```
class Queue:
def __init__(self):
self.items = []
self.size = 0
def push(self, value):
self.items.append(value)
self.size += 1
```

The `push`

method it's the same as the one with the Stack, we are going to handle the end of the array as the tail of the `Queue`

, and with just 2 lines of code, we already added the Enqueue behavior.

`pop()`

method```
class Queue:
def __init__(self):
self.arr = []
self.size = 0
def push(self, value):
self.arr.append(value)
self.size += 1
def pop(self):
if self.size > 0:
self.items.pop(0)
self.size -= 1
```

The pop method may look very similar to the Stack method, BUT remember that the stack took out the last element of the array, and with a queue, we must take the first element of the array aka the front/head, that's why instead of doing `pop()`

we do `pop(0)`

where 0 it's the index of the array.

`front()`

methodThis method simply returns the element that's at the Front/Head of the Queue, this method should not remove the element.

```
class Queue:
def __init__(self):
self.items = []
self.size = 0
def push(self, value):
self.items.append(value)
self.size += 1
def pop(self):
if self.size > 0:
self.items.pop(0)
self.size -= 1
def front(self):
return self.items[0]
```

`back()`

methodThis method behaves very similarly to the `front()`

method but instead of returning the front, this method should return the last element.

```
class Queue:
def __init__(self):
self.items = []
self.size = 0
def push(self, value):
self.items.append(value)
self.size += 1
def pop(self):
if self.size > 0:
self.items.pop(0)
self.size -= 1
def front(self):
return self.items[0]
def back(self):
if self.size > 0:
return self.items[self.size - 1] # -1 because indexing starts at 0
```

As you can see from the code above, the back method first checks that the Queue is not empty, this is important because if we don't check, we are going to get an index out of range error.

`empty()`

method```
class Queue:
def __init__(self):
self.items = []
self.size = 0
def push(self, value):
self.items.append(value)
self.size += 1
def pop(self):
if self.size > 0:
self.items.pop(0)
self.size -= 1
def front(self):
return self.items[0]
def back(self):
if self.size > 0:
return self.items[self.size - 1]
def empty(self):
if self.size > 0:
return False
else:
return True
```

```
myQueue = Queue()
myQueue.push(1)
myQueue.push(2)
myQueue.push(3)
myQueue.push(4)
print(myQueue.items)
print(myQueue.size)
print(myQueue.front())
print(myQueue.back())
myQueue.pop()
myQueue.pop()
print(myQueue.items)
print(myQueue.size)
print(myQueue.front())
print(myQueue.back())
```

By now, you may be noticing a pattern. Earlier, we implemented a stack using an array and a linked list. Here, we're doing the same thing with queues.

`Queue`

classin the cell below, you can see the Queue class initialization with the head and tail set to None and the num_elements variable set to cero.

```
class Queue:
def __init__(self):
self.head = None
self.tail = None
self.num_elements = 0
```

`Node`

classThis is exactly the same code we did previously.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
```

`push()`

methodIn the method from the cell below, we create a new node with the passed value, and we check if the linked list is empty with `if self.head == None:`

, if it is, we make the head of our linked list to be the newly created node, as well as the tail. if the linked list is not empty, we simply set the next node of the tail to the new node and we shift the tail to make sure it's always at the end. Lastly, we increment the number of elements in the linked list.

```
class Queue:
def __init__(self):
self.head = None
self.tail = None
self.num_elements = 0
def push(self, value):
new_node = Node(value)
if self.head == None:
self.head = new_node
self.tail = self.head
else:
self.tail.next = new_node
self.tail = self.tail.next
self.num_elements += 1
```

`pop()`

methodThe first step is to check if the linked list is not empty with `if self.num_elements > 0:`

and if so, we shift the head to the next node removing it from the list, lastly, we subtract 1 to the `num_elements`

counter.

```
def pop(self):
if self.num_elements > 0:
self.head = self.head.next
self.num_elements -= 1
```

`front()`

method.This method is very easy thanks to the way we keep track of the head on the linked list.

```
def front(self):
if self.num_elements > 0:
return self.head.value
```

`back()`

method.Likewise, creating this method is a breeze thanks to the tail attribute of the linked list.

```
def back(self):
if self.num_elements > 0:
return self.tail.value
```

`size()`

method```
def size(self):
return self.num_elements
```

`print()`

methodIn the first method, we could simply print the array, but here, we need to traverse the linked list and print each of the elements' values.

```
def print_items(self):
current = self.head
while current:
print(current.value, "=>", end=" ") # prints in a single line
current = current.next
print("") # just to add the end line at the end
```

```
myQueue = Queue()
myQueue.push(1)
myQueue.push(2)
myQueue.push(3)
myQueue.push(4)
myQueue.print_items()
print(myQueue.size())
print(myQueue.front())
print(myQueue.back())
myQueue.pop()
myQueue.pop()
myQueue.print_items()
print(myQueue.size())
print(myQueue.front())
print(myQueue.back())
```

Output:

```
1 => 2 => 3 => 4 =>
4
1
4
3 => 4 =>
2
3
4
```

You've reached the end of this lesson on Stacks and Queues, I really hope you liked it and learned something new today, if you have any questions or suggestions, feel free to comment in the section below, remember, there is still a lot to learn and we definitively didn't cover everything on this topic, especially on the applications of the queue like in a BFS algorithm for example, but that's a topic for another day. See you in the next post, stay tuned! 👋

]]>Hello everyone, welcome back! today, I wanted to talk about the importance of efficiency with data structure and algorithms and how to use Big-O notation to measure the Time and Space complexity of an algorithm.

In my last post, we talked about how to solve computational problems with python but we didn't dive into whether our solution was efficient or not. That's what we will be looking at in this section.

When we refer to the efficiency of a program, we don't only look at the time it takes to run, but at the space required in the computer's memory as well. Often, there will be a trade-off between the two, where you can design a program that runs faster by selecting a data structure that takes up more spaceor vice versa.

In order to quantify the time and space an algorithm takes, let's first understand what an algorithm is.

## Algorithm

An algorithm is a series of well-defined steps for solving a problem. Usually, an algorithm takes some kind of input (such as a list) and then produces the desired output (such as a reversed list).

For any problem, there is likely more than one algorithm that can solve the problem, but there are some algorithms that are more efficient than others. However, computers are so fast! that in some problems, we can't tell the difference, so how can we know what algorithm is more efficient than another? and if we can't tell the difference, why try to make our code efficient?

Well, those are some great questions, and in some cases it's true, one version of a program may take 5 times longer than another, but they both still run so quickly that it has no real impact. However, in other cases, a very small change can make the difference between a program that takes milliseconds to run and a program that takes hours!.

Sometimes, you will hear programmers say:

"This algorithm is better than that algorithm"

But how can we be more specific than that? How do we quantify efficiency?.

Let's take a look at some code examples.

```
def add200(n):
for i in range(2):
n += 100
return n
```

```
def add200_2(n):
for i in range(100):
n += 2
return n
```

Both of the functions above have no real-world use, they are just dummy functions that will help us understand efficiency. Both of them add 200 to whatever the input(n) is. Take a good look at them and tell me, which one is more efficient?

The answer is the `add200`

function. Why?

Although both functions output the exact same result, the `add200_2`

function makes too many iterations, while the `add200()`

function only iterates twice.

With the example above, what we basically did was to estimate which code had more lines to run, let's take a look again at both functions

```
def add200(n):
for i in range(2):
n += 100
return n
def add200_2(n):
for i in range(100):
n += 2
return n
```

The first function has a total of 4 lines but because of the for loop that gets called twice, there is a total of 5 lines (the for loop line doesn't get counted).

Now, let's take a look at the second function. the total of lines is 4 but the for loop gets called 100 times! so running this code will involve running 103 lines of code!.

Counting lines it's not a perfect way of quantifying the efficiency of a program but it's an easy way for us to **approximate** the difference in efficiency between two solutions.

In both examples above, no matter what number we passed as an argument, the number of lines executed will remain the same.

Here's a new code example:

```
def print_to_num(n):
for i in range(n+1): # +1 because python counts from 0
print(i)
print_to_num(10)
```

The code above prints all numbers from 0 to N, meaning the bigger the input N is, the number of lines executed will increase, and that means a longer time to run.

The highlighted idea it's that:

As the input to an algorithm increases, the time required to run the algorithm

mayalso increase.

This **may** happen in some cases, in the last example it does increase the time, but in the first two, it does not.

Let's keep working with the last function and let's try several function calls examples to see the number of lines each one gets to run.

```
print_to_num(2) # 4 lines
print_to_num(3) # 5 lines
print_to_num(4) # 6 lines
print_to_num(5) # 7 lines
```

As you can see, when N goes up by 1 the number of lines will also go up by 1. We can also say that the number of lines executed increases by a **proportional** amount. This type of relationship is called a **Linear relationship** if we graph the relationship we can see why it's called that.

The x-axis represents the input size, in this case, a number, and the y-axis represents the number of operations that will be performed in this case we're thinking of an "operation" as a line of Python code which is not the most accurate but will do for now.

Let's take a look at another function example where the operations increase at a **none** constant rate.

```
def print_square(n):
for i in range(1, n+1):
for j in range(1, n+1):
print("*", end=" ") # end=" " for printing all "*" in a single line separated by two white spaces
print("") # this is used to mimic an "enter"
print_square(5)
```

This code prints a square made out of "*" that it's exactly N x N in size

```
output:
* * * * *
* * * * *
* * * * *
* * * * *
* * * * *
```

Notice that this function has a **nested** loop, that means, a loop inside a loop, and take a good look at the fact that both loops have a linear rate of increase but they are **nested** that makes the rate of increase **quadratic** that means that when the input goes up by a certain amount, the number of operations goes up by the **square** of that amount.

```
print_square(1) # 1 line
print_square(2) # 4 lines
print_square(3) # 9 lines
```

Let's add the quadratic rate of increase to our graph:

Our `print_square`

function exhibits a quadratic rate of increase which as you can see is a much faster rate of increase, this means that as we pass larger numbers, the number of operations the computer has to perform shoots up very quickly making the program far less efficient
These are only two examples of the rate of increase but there are many more. Here are the most common:

Note. when people refer to the rate of increase, they will often use the term "order". For example, instead of saying: "This algorithm has a linear rate of increase" they will say: "The order of this algorithm is linear"

Big-O notation is a "simplified analysis of an algorithm's efficiency". Big-O gives us an algorithm's complexity in terms of the input size (N) and it's independent of the machine we run the algorithms on. Big-O can give us the time but also space complexity of an algorithm

There are three types of ways we can look at an algorithm's efficiency.

- Worst-case
- Best-case
- Average-case

Normally, when talking about Big-O notation we will typically look at the worst-case scenario, this doesn't mean the others are not used, but normally we want to know what the worst case is.

Big-O notation ignores constants. Example: let's say you have a function that has a running time of O(5n), in this case, we say that it runs on the order of O(n) because as N gets larger, the 5 no longer matters.

Terms priority. This means that if a function has a part that has an O(1) order but another part of that same function has an order of O(n) we will say that the whole function has an order of O(n). This includes sections that might not run, for example, in an if-else statement.

`O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(2n) < O(n!)`

(see graph above)

In this section, we will take a look at example lines of code and see how O(n) is used.

`O(1)`

Take a look at the line below

```
print((5*10)/2) # outputs 25
```

As you can see, this code simply computes a simple math operation and it's not dependent on any input. This is an example of O(1) order or constant time.

What if we have not one but three simple math operations.

```
n = 5 + 2 # O(1)
y = 3 * 5 # O(1)
x = n + y # O(1)
```

Well, in this case, each line of code has an O(1) order, so what we need to do is add all of them like this: `O(1)+O(1)+O(1)`

BUT let's remember the first rule and ignore constants, making the final result be `O(1)`

`O(n)`

Let's try another example:

```
for i in range(n): # n * O(1) = O(n)
print(i) # O(1)
```

The clearest example of Linear time code, it's a simple for loop that iterates from 0 to N. In the code shown above, the print statement has an order of `O(1)`

but because it's inside a loop, we need to multiply `O(1) * N`

which would give us a result of `O(N)`

If, for example, we had both examples above together like this:

```
n = 5 + 2 # O(1)
y = 3 * 5 # O(1)
x = n + y # O(1)
for i in range(n): # n * O(1) = O(n)
print(i) # O(1)
```

We would have the `O(1)`

of the simple math and print lines, and `O(n)`

of the for loop, so, to calculate the total time we need to add both of them BUT let's remember our second rule of terms priority and make `O(N)`

the final result for this code

`O(n)`

The easiest way to get a code to have a Quadratic order it's to simply have a nested loop with both of them iterating from 0 to N. Let's see the code example that we saw earlier:

```
def print_square(n):
for i in range(1, n+1):
for j in range(1, n+1):
print("*", end=" ") # end=" " for printing all "*" in a single line separated by two white spaces
print("") # this is used to mimic an "enter"
print_square(5)
```

The first for loop has a time complexity of `O(N)`

, the second for loop also has a time complexity of `O(N)`

, and finally, the print statements have a complexity of `O(1)`

. And hopefully, you can see pretty clearly that the print statement will be executed `N * N`

times, making this whole code have a time complexity of `O(N)`

If, we put all previous codes together like this:

```
n = 5 + 2 # O(1)
y = 3 * 5 # O(1)
x = n + y # O(1)
for i in range(n): # n * O(1) = O(n)
print(i) # O(1)
for i in range(1, n+1): # O(n) * O(n) = O(n)
for j in range(1, n+1):
print("*", end=" ")
print("") # this is used to mimic an "enter"
```

we know the first code has a complexity of `O(1)`

the second code has a complexity of `O(n)`

and the third code has a complexity of `O(n)`

and because of our second rule, the complexity of the whole code it's `O(n)`

`O(log n)`

An algorithm with a Logarithmic run time, it's that code that reduces the size of the input data in each step (it doesn't need to look at all values of the input data), a great example of this, is a **Binary Search**. Binary Search is a searching algorithm for finding an element's position in a sorted array.

```
def binary_search(arr, element):
left = 0
right = len(arr)-1
while(left <= right):
mid = (left + right)//2
if arr[mid] == element:
return mid
if arr[mid] < element:
left = mid +1
else:
right = mid -1
return -1
print(binary_search([1,2,3,4,5,6,7,8,9], 8))
```

`(O(n log n))`

An algorithm is said to have a log-linear time complexity when each operation in the input data has a `logarithm`

time complexity. It is commonly seen in sorting algorithms

```
codes = set()
for phone in phones_arr:
codes.add(phone[:4])
print('\n'.join(sorted(codes))) # <== O(n log n)
```

The code above iterates through an array of phone numbers and adds the first four digits of each phone to a "codes" set(a set doesn't allow duplicates) and finally the code prints the codes but are sorted.

`O(2)`

Algorithms have an exponential time complexity when the growth doubles with each addition to the input data set. This kind of time complexity is usually seen in brute-force algorithms.

```
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(8)) # outputs 21
```

A great example of an exponential time algorithm is the recursive calculation of Fibonacci numbers, the code above receives a number and prints the N number in the Fibonacci sequence:
`0, 1, 1, 2, 3, 5, 8, 13, 21`

An algorithm where the operational execution complexity increases factorially with the increase in the input size. As you can see from the graph above, this is the most inefficient order that an algorithm can have.

```
def factorial(n):
if n == 1:
return n
else:
return n * factorial(n-1)
print(factorial(4))
```

The function above receives a number and uses recursion to print the result of multiplying all numbers from 1 to N
`4*3*2*1 = 24`

You've reached the end of this post on complexity and Big-O notation to quantify time efficiency, I really hope some of the concepts we saw today have helped you and hopefully you will be thinking not only about solving a problem but to think weather your code is efficient or not. If you liked this post, make sure to share it with another person that might find this interesting, and let me know in the comments your thoughts, suggestions, and what you will like to see next.

See you in the next post, stay tuned for more!

]]>Hello everyone, welcome back!, today I wanted to give a beginners overview of Linked Lists, we will take a good look at what are they? what are they for? and we will look through some examples together.

A linked list is an extension of a list but it's definitely NOT an array. By definition a linked list is a:

Dynamic data structure where each element (called a node) is made up of two items: the data and a reference (or pointer), which points to the next node. A linked list is a collection of nodes where each node is connected to the next node through a pointer.

Here it's an image to give a better understanding of a linked list

With a linked list, there are still some things that have order but there are no indices. Instead, a linked list is characterized by its `Links`

. Each element has a notion of what the next element is since it's connected to it, but not necessarily how long the list is or where it is in the list.
An array's different. There is nothing in one element of the array that says "here's your next element" like a linked list would, instead an array knows what the next element is by what the next `index`

is.

Let's walk through the differences together.

- A data structure consisting of a collection of elements each identified by the array index.
- Supports random access, the programmer can directly access an element in the array using an index
- Elements are stored in contiguous memory locations
- Programmer has to specify the size of the array when declaring the array
- Elements are independent of each other

- A linear collection of data elements whose order is not given by their location in memory.
- Supports sequential access, the programmer has to go sequentially through each element until reaching the searched element
- Elements can be stored anywhere in memory
- There is no need in specifying the length of the linked list
- An element has to point to the next or previous element

When talking about linked lists you will often hear "Node" instead of "Element"

When I was studying linked lists I was already pretty familiar with arrays and felt pretty comfortable using them, and if you are a beginner you are probably feeling the same way and are wondering "Hey, arrays are easy to access, have a set length, the elements are independent of each other, why make my life harder with this new linked list stuff?", if you thought of that, congratulations! you are on your way to becoming a great programmer since you have to always ask yourself the why of things.

The answer to the question "Why use linked lists?" can be a complex one, but for now, the simple and short answer is Efficiency. Turns out, that adding or removing an element from a linked list is so much easier in comparison to an array. And I want to give a quick reminder that we are working with python here.

Let's learn more about linked lists while we work through some examples and start writing some code.

Because of the definition we saw earlier, we are going to approach this, by creating a `Node`

, a node it's going to have, two attributes, the value, and the `next`

attribute that's going to be a pointer pointing to the next node. From the image above, we can see how each node points to the next one until the list it's finished and the last node points to `None`

. this collection of nodes pointing to the next one is going to be the Linked List.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
```

this node has a "value" property that can be passed as an argument, and a "next" attribute that points to the next `Node`

, in this case, it's pointing to None by default.

Using our newly created Node class, it's time to create our `Head`

, the "Head" it's basically the first Node.

```
head = Node(8)
```

Creating a new Node with the value of 8 and saving it in a new head variable.

Like we saw in the previous step, to create a new Node simply code `Node(value)`

, and to link it to the head what we need to code is:

```
head.next = Node(4)
```

In the code above, `head.next`

had the value of None, but now we are replacing that with a new Node with the value of 4.

Right now, we have a linked list that has a Head with the value of 8 then the next node with the value of 4 like this:

Here's our progress:

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
head = Node(2)
head.next = Node(1)
```

To print out the values in the linked list we have to code:

```
print(head.value)
print(head.next.value)
```

```
Output:
8
4
```

Let's remember that this is what we want to accomplish:

To do this, we need to create three more nodes, and we need to attach each one to the next attribute of the node that comes before it. Notice that we don't have a direct reference to any of the nodes other than the head.

```
head.next.next = Node(2)
head.next.next.next = Node(5)
```

Let's print all the values:

```
print(head.value)
print(head.next.value)
print(head.next.next.value)
print(head.next.next.next.value)
```

This process isn't very efficient, let's write a function that receives the head and traverses the linked list to print out all of the values.

```
def print_linked(head):
current = head # current node set to the head
while current: # loop that will run as long as current exists
print(current.value) # printing the current node value
current = current.next # moving to the next node
print_linked(head)
```

Ahh, so much better, now, no matter how long the linked list is, with `print_linked(head)`

we no longer need to code `print(head.next.next.next.next)`

you get the point.

Up until now, we have to create a variable with the head and pass that to our functions in order to interact with it, but an alternative it's to create a Linked_List class and code methods to it. Let's take a look at some code to understand what I mean.

```
# Linked_List class with a default head value of None meaning empty list
class Linked_List:
def __init__(self):
self.head = None
```

Let's add our print function as a method to this class.

```
def print_linked(self):
current = self.head
while current:
print(current.value)
current = current.next
```

In the code above, we made tiny changes to fit in as a method, in this case instead of receiving the head, we receive the `self`

attribute, and instead of setting current to the head, we set it to the `self.head`

, other than that, everything stays the same.

Now, we have our Linked list class with a print method and we have to create the actual object like this:

```
my_linked_list = Linked_List()
```

and if we wanted to print the values of that linked list, all we have to do is:

```
my_linked_list.print_linked()
```

If you tested the lines above you should have seen no output since the linked list is completely empty, let's change that by adding a method that allows us to append items into the list.

```
def append(self, value):
# checking if the linked list is empty
if self.head is None:
# If so, set the head to a new node with the passed value
self.head = Node(value)
return
# Traverse the linked list until reaching the end of the list
current = self.head
while current.next:
current = current.next
# Append the new node with the passed value
current.next = Node(value)
```

Here's the full code:

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Linked_List:
def __init__(self):
self.head = None
def print_linked(self):
current = self.head
while current:
print(current.value)
current = current.next
def append(self, value):
if self.head is None:
self.head = Node(value)
return
current = self.head
while current.next:
current = current.next
current.next = Node(value)
my_linked_list = Linked_List() # this is creating a Linked_List object
my_linked_list.append(8) # appending 8 to the list
my_linked_list.append(4) # appending 4 to the list
my_linked_list.append(2) # appending 2 to the list
my_linked_list.append(5) # appending 5 to the list
my_linked_list.print_linked() # printing the list
```

If you test the code above you shall now see an output of `8 4 2 5`

like the image below

I hope you're getting the hang of this, let's try adding more methods that will make working with linked lists better.

Below you will find the exercises for this section, I highly encourage you to try to code them yourself before watching the solutions that can be found at the end of this section. And I also recommend you try each of the methods individually first before trying to use multiple of them together.

`to_list()`

methodWhen the method is called on the linked list it should convert the linked list back into a normal python list.

```
output:
[8,4,2,5]
```

`prepend()`

methodMethod similar to the `append()`

method but instead of appending at the end of the linked list it will append at the beginning of the list

```
my_linked_list.prepend(7)
my_linked_list.print_linked()
```

```
output:
7
8
4
2
5
```

`search()`

method`print(my_linked_list.search(4))`

will return the Node if there is one with a matching value, if not, it will return a ValueError indicating that the searched value is not found in the list. Because it returns the searched Node you can access its value like this:

```
# if there is a node with the value of 4, 4 will be printed
print(my_linked_list.search(4).value)
```

```
output:
4
```

`remove()`

methodMethod traverses the linked list and removes the Node with the passed value

```
my_linked_list.remove(4)
my_linked_list.print_linked()
```

```
output:
7
8
2
5
```

`pop()`

methodMethod that removes the first node of the linked list

```
my_linked_list.pop()
my_linked_list.print_linked()
```

```
output:
8
2
5
```

`insert()`

methodThis method receives a position and a value and inserts a new node with the passed value in the passed position

```
my_linked_list.insert(10,1)
my_linked_list.print_linked()
```

```
output:
8
10
2
5
```

`size()`

methodThis method shall return the length of the linked list like the method `len()`

would on a normal python list

```
print(my_linked_list.size())
```

```
output:
4
```

`reverse()`

methodReversing a linked list is a really common interview problem and I highly recommend that you put extra effort into understanding this method. This method shall, like the name suggests, reverse the linked list nodes order.

```
my_linked_list.reverse()
my_linked_list.print_linked()
```

```
output:
5
2
10
8
```

Feel free to test out combinations of the methods and try each one individually and do your best in understanding them. You can also try adding your own methods, and remember that the solutions shown below are not the only correct ways of coding them.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Linked_List:
def __init__(self):
self.head = None
# ------------------------------------------ #
def print_linked(self):
current = self.head
while current:
print(current.value)
current = current.next
# ------------------------------------------ #
def append(self, value):
if self.head is None:
self.head = Node(value)
return
current = self.head
while current.next:
current = current.next
current.next = Node(value)
# ------------------------------------------ #
def to_list(self):
out = []
node = self.head
while node:
out.append(node.value)
node = node.next
return out
# ------------------------------------------ #
def prepend(self, value):
if self.head is None:
self.head = Node(value)
return
new_head = Node(value)
new_head.next = self.head
self.head = new_head
# ------------------------------------------ #
def search(self, value):
if self.head is None:
return None
node = self.head
while node:
if node.value == value:
return node
node = node.next
raise ValueError("Value not found in the list.")
# ------------------------------------------ #
def remove(self, value):
if self.head is None:
return
if self.head.value == value:
self.head = self.head.next
return
node = self.head
while node.next:
if node.next.value == value:
node.next = node.next.next
return
node = node.next
raise ValueError("Value not found in the list.")
# ------------------------------------------ #
def pop(self):
if self.head is None:
return None
node = self.head
self.head = self.head.next
return node.value
# ------------------------------------------ #
def insert(self, value, pos):
if self.head is None:
self.head = Node(value)
return
if pos == 0:
self.prepend(value)
return
index = 0
node = self.head
while node.next and index <= pos:
if (pos - 1) == index:
new_node = Node(value)
new_node.next = node.next
node.next = new_node
return
index += 1
node = node.next
else:
self.append(value)
# ------------------------------------------ #
def size(self):
size = 0
node = self.head
while node:
size += 1
node = node.next
return size
# ------------------------------------------ #
def reverse(self):
previous = None
current = self.head
while current:
next = current.next
current.next = previous
previous = current
current = next
self.head = previous
# ------------------------------------------ #
my_linked_list = Linked_List() # creating linked list obj
my_linked_list.append(8) # appending 8
my_linked_list.append(4) # appending 4
my_linked_list.append(2) # appending 2
my_linked_list.append(5) # appending 5
my_linked_list.print_linked() # output: 8 4 2 5
print(my_linked_list.to_list()) # output: [8,4,2,5]
my_linked_list.prepend(7) # appending 7 to the start of the linked list
my_linked_list.print_linked() # output: 7 8 4 2 5
print(my_linked_list.search(4).value) # output: 4
my_linked_list.remove(4) # remove the node with value 4 from the list
my_linked_list.print_linked() # output: 7 8 2 5
my_linked_list.pop() # remove the first node
my_linked_list.print_linked() # output: 8 2 5
my_linked_list.insert(10, 1) # insert node with value 10 in position 1
my_linked_list.print_linked() # output: 8 10 2 5
print(my_linked_list.size()) # output: 4
my_linked_list.reverse() # reverse the order of the nodes
my_linked_list.print_linked() # output: 5 2 10 8
```

Up until this point, we have been working with a type of linked list called: "singly linked list", in this kind of linked list, each node is connected only to the next node in the list.

This connection is typically implemented by setting the `next`

attribute on a node object itself. But there are more types of linked lists.

This type of list is the exact same as the singly linked list with the addition of also having a connection backward.

To implement a Doubly linked list simply change your node to have a previous property like this:

```
class DoubleNode:
def __init__(self, value):
self.value = value
self.next = None
self.previous = None # new property
```

using doubly linked lists can give us advantages that a singly linked list can't, for example, now that we track the tail, we can do things like appending a node to a list's tail in constant time.

```
def append(self, value):
if self.head is None:
self.head = DoubleNode(value)
self.tail = self.head
return
self.tail.next = DoubleNode(value)
self.tail.next.previous = self.tail
self.tail = self.tail.next
return
```

Linked circular lists occur when the chain of nodes links to itself somewhere. For example, NodeA -> NodeB -> NodeC -> NodeD -> NodeB is a circular list because NodeD points to NodeB creating a NodeB -> NodeC -> NodeD -> NodeB loop.

A circular linked list in simple words, it's a singly or doubly list where all nodes are connected to form a loop or "circle" meaning there is no NULL at the end, this can be a problem since if we try to iterate through it we will never find the end. We usually want to detect if there's a loop in our linked list to avoid an infinite iteration.

In this section, we'll see a way we can detect loops in a linked list with python. The way we'll do this is by using two pointers called "runners", both of them will traverse through the linked list but one of them it's going to iterate twice as fast, meaning it will advance two nodes at a time. If a loop exists, the fast runner will eventually catch up with the slow runner and both pointers will be pointing at the same node at the same time, if this happens you will know for sure there's a loop, see the image below to understand this technique.

`hasLoop()`

methodI'm confident you can do this on your own, see the image above thoroughly to understand the algorithm you're about to code, and also take another look at the past exercises, you should find everything you need in this post. Make your best effort! and don't worry if you don't get it right away we'll see the solution below.

`hasLoop()`

methodThe first thing we need to do is to define our `hasLoop()`

method:

```
def hasLoop(self):
# your code goes here
```

Next, we'll check if the list is empty, in the case that the condition is True we'll return False meaning there is no loop

```
if self.head is None:
return False
```

Then, we need to create our two pointers as we talked about earlier

```
slow = self.head
fast = self.head
```

Now the fun part! while the fast node and the fast.next node exists, let's advance the slow node by one and the fast by two

```
while fast and fast.next:
slow = slow.next
fast = fast.next.next
```

and for each iteration, we need to check if the slow node is the same as the fast one, if that is the case then we just found a loop and the code should return True, if the while loops finishes, it means there was an end to the list and the code should return False.

```
if slow == fast:
return True
return False
```

Full code:

```
def hasLoop(self):
if self.head is None: # check if the linked list is empty
return False # return False, meaning there is no loop
slow = self.head # create the slow node
fast = self.head # create the fast node
while fast and fast.next: # while fast and fast.next exist:
slow = slow.next # advance the slow node by one
fast = fast.next.next # advance the fast node by two
if slow == fast: # if the nodes are the same
return True # return True, meaning there is a loop
# if the loop finishes, that means there was an end to the list
# and there was no loop
return False
```

Let's suppose we have a **nested** linked list, this means that the value of each node in the linked list is another ordered linked list. The image below illustrates this.

In this section, we'll **flatten** this linked list, which means to combine all nested lists into one **ordered** single linked list like this:

`NestedLinkedList`

classThis class should inherit the Linked_List class and inside of the new class let's add our flatten method.

```
class NestedLinkedList(Linked_List):
def flatten(self):
# code goes here
```

`merge()`

Helper FunctionThis function will be useful for merging two linked lists and having them into a single **ordered** linked list, this function it's not going to be a `NestedLinkedList`

method and should return an instance of LinkedList.

```
def merge(list1, list2):
# your code goes here
return
```

`merge()`

walkthroughNote, this solution expects the passed lists to be ordered.

First, we need to create a new LinkedList object, that will be named `merged`

```
merged = Linked_List()
```

Next, let's check whether any of the passed lists are None. If list1 is None, immediately return list2, if list2 is None, immediately return list1.

```
if list1 is None:
return list2
if list2 is None:
return list1
```

Then, we'll create two traversers, one called list1_node and the second one called list2_node. And using both of them, we'll traverse both lists while they exist.

```
list1_node = list1.head
list2_node = list2.head
while list1_node or list2_node: # pay attention to the OR
```

If the first node does not exist, meaning the first list is traversed, append the list2_node to the "result list"(merged) and advance the list2_node to the next node in that list.

```
if list1_node is None: # if list1 is traversed
merged.append(list2_node.value) # append the second list node to the "result"
list2_node = list2_node.next # and advance the second node to the next one
```

Repeat the step above but with the list2

```
elif list2_node is None: # if list2 is traversed
merged.append(list1_node.value) # append the first node to the "result"
list1_node = list1_node.next # advance the first node to the next one
```

Now we can work in the cases that none of the lists are done traversing. Which would always be the first iteration.

Because we want the output to be an ordered linked list but with both lists merged, we need to check which node has the smallest value and append it.

```
# if the first node value is smaller than the second node value
elif list1_node.value <= list2_node.value:
merged.append(list1_node.value) # append the first node
list1_node = list1_node.next # and advance to the next node
else:
merged.append(list2_node.value) # append the second node
list2_node = list2_node.next # advance to the next node
```

Finally, after the while loop exits indicating that both lists have been traversed, return the result, (merge) linked list.

```
return merged
```

Here's the full `merge()`

procedure:

```
def merge(list1, list2):
merged = Linked_List()
if list1 is None:
return list2
if list2 is None:
return list1
list1_node = list1.head # [5,6,7] list one
list2_node = list2.head # [1,2,3] list two
while list1_node or list2_node: # 5 - 1
if list1_node is None: # if list1 is traversed
# append the second list node to the "result"
merged.append(list2_node.value)
list2_node = list2_node.next # and advance the second node to the next one
elif list2_node is None: # if list2 is traversed
# append the first node to the "result"
merged.append(list1_node.value)
list1_node = list1_node.next # advance the first node to the next one
# if the first node value is smaller than the second node value
elif list1_node.value <= list2_node.value:
merged.append(list1_node.value) # append the first node
list1_node = list1_node.next # and advance to the next node
else:
merged.append(list2_node.value) # append the second node
list2_node = list2_node.next # advance to the next node
return merged
```

`Flatten()`

MethodWith the help of our `merge()`

helper function, there is an easy solution to finish our `Flatten()`

method by using **recursion**.

We won't talk about recursion in detail in this post, but for those that are not familiar with the concept, recursion is basically invoking a function inside itself, here's a code example that returns the factorial of any number.

```
def factorial(x): # function definition
if x == 1: # break point, important to exit the "loop"
return 1
else:
return (x*factorial(x-1)) # invoking itself
print(factorial(4)) # 4 * 3 * 2 * 1 = 24
```

As you can see from the code above, the `factorial()`

function it's being invoked inside of itself, that's a recursive function.

Back to our `Flatten()`

method. Let's apply recursion!

```
class NestedLinkedList(Linked_List):
def flatten(self):
return self._flatten(self.head)
def _flatten(self, node): # a recursive function
# A termination condition
if node.next is None:
# if there is no next node in the list
# merge the last, current node
return merge(node.value, None)
# _flatten() is calling itself untill a termination condition is achieved
# <-- Both arguments are a simple LinkedList each
return merge(node.value, self._flatten(node.next))
```

`flatten()`

FunctionFirst, let's create three simple, normal,ordered, linked lists.

```
''' Create a simple LinkedList'''
linked_list = Linked_List()
linked_list.append(0)
linked_list.append(4)
linked_list.append(8)
linked_list.print_linked()
# 0 => 4 => 8
''' Create another simple LinkedList'''
second_linked_list = Linked_List()
second_linked_list.append(1)
second_linked_list.append(2)
second_linked_list.append(3)
second_linked_list.print_linked()
# 1 => 2 => 3
''' Create another simple LinkedList'''
third_linked_list = Linked_List()
third_linked_list.append(6)
third_linked_list.append(7)
third_linked_list.append(9)
third_linked_list.print_linked()
# 6 => 7 => 9
```

Next, let's create a **nested** linked list object using our class

```
nested_linked_list = NestedLinkedList()
```

Something really cool, it's that when we defined the `NestedLinkedList`

class, we inherited the `Linked_List`

class, which means that all of the previous methods we wrote for the `Linked_list`

class are going to be available to use in the `NestedLinkedList`

class.

```
# Here we inherited the Linked_List class
class NestedLinkedList(Linked_List): # making all of its methods available here too!
```

Now, let's append the three simple linked lists into this `nested_linked_list`

using the `append()`

method.

```
nested_linked_list.append(linked_list)
nested_linked_list.append(second_linked_list)
nested_linked_list.append(third_linked_list)
```

Lastly, create the flattened list like this:

```
flattened = nested_linked_list.flatten()
```

and print it out!

```
flattened.print_linked()
```

You've reached the end of this lesson on Linked Lists, we saw what are they? how do they differentiate from a normal python list? what are some pros and cons? and we also saw code examples of the most common ways to interact with Linked lists.

I really hope some of the guidelines we saw today were helpful and the basic foundations on this topic were well understood, remember, there is still a lot to learn and we definitively didn't cover everything on linked lists, but I hope this was a good beginner overview and that you grasped the concepts and feel more confident on your programming journey.

Let me know in the comments what you thought about this post and let me know what you will like to see next. See you in the next post, stay tuned!

]]>Hello everyone, welcome back! today I want to talk about a very important topic: how to solve problems?. Learning how to solve problems is one of the most important skills you can learn, and improving as a problem solver is a lifelong challenge that cannot be learned through this short lesson, however, I want to show you important tips on how to tackle more complex programming problems, and hopefully, this post will teach you the essentials to problem-solving.

In this post, we will tackle a specific problem, and talk about how I would go about solving it, and the goal of this exercise is not just to solve the problem but to draw general ideas on how to solve any type of programming problem.

Given your birthday and the current date, calculate your age in days. Compensate for leap years. Assume that the birthday and current date are correct (no time travel).

It's often tempting to start writing code early, the problem with this is that we are likely to write the wrong code and get frustrated, it's also likely that you think that you "solved the problem" but in reality, you might end up doing something completely different to what it was asked.

We need to emphasize that we are working on a `computational problem`

, and what all computational problems have in common is that they have inputs and desired outputs, so, a problem is defined by the set of possible inputs (this is usually an infinite set) and the relationships between those inputs and the desired outputs. And a solution is a procedure that can take any input in that set and produces the desired output that satisfies the relationship, in this case, the relationship we want is that the output is the number of days between the birthday and the current date.

So, the first step to understanding a problem is to understand what the possible inputs are.

If we take another look at the problem we realized that it's clearly stated that we're given two dates as inputs.

Given your birthday and the current date, calculate your age in days. Compensate for leap years. Assume that the birthday and current date are correct (no time travel).

We always need to ask ourselves this question, for the moment we know the type of the inputs, but if we take another look at our problem we can find a good clue on what to expect.

Given your birthday and the current date, calculate your age in days. Compensate for leap years. Assume that the birthday and current date are correct (no time travel).

Thanks to that statement we know that the second date needs to be after the first one. Assumptions like this one make life easier for programmers since our code has to work for fewer possible inputs, however, we are going to be good defensive programmers and check if the second date is after the first date in our code. Checking if the requirement is correct is a good practice since other people or even ourselves can make mistakes.

The other assumption we might need to think about is the range of dates. Calendars are very complicated and they've changed over history, so, we are going to require that the dates are valid dates in the Gregorian calendar, which started in October 1582.

For most real-world problems, it's up to you to figure out how to encode the inputs and this is one of the most important decisions that you can make in solving a problem. In this case, the problem template below indicates how the inputs are represented

```
# function that returns the dates difference in days
def daysBetweenDates( year1, month1, day1, year2, month2, day2 ):
# You're code goes here
```

As we can see there are six parameters to the daysBetweenDates procedure, which means we're going to be passing in six different values to represent those two dates. Some of you might see that there are better ways to pass the date, as an object for example but for the sake of clarity we're going to be passing those six values.

The statement of the question gives us some idea of what the output should be in the calculate your age part.

But it doesn't specify really explicitly what we want the output to be

In this case, after reading the problem, it's clear that the output should be a **number ** representing the days of the difference between date1 and date2 assuming date1 is before date2.

Now that we know what the inputs are and what the outputs are it's time to understand the relationship between the two by working out some examples.

Take a good look at the problems below and try to figure out what the output for each call should be.

```
daysBetweenDates(2020,11,7, 2020,11,7)
daysBetweenDates(2015,12,7, 2015,12,8)
daysBetweenDates(2010,12,8, 2010,12,7)
daysBetweenDates(2019,5,15, 2021,5,15)
daysBetweenDates(2012,6,29,2013,6,31)
```

For problem one, the output should be 0 since both dates are the same. For problem two the output should be 1 since there is only a one-day difference. Output for problem three should be an error since the second date is before the first date. Problem four was hard since, there was a two-year difference and 2020 is a leap year, so, the output should be 731. The last one was tricky since there is no day 31 in June!, so we would like to have an error indicating the invalid input.

At this point, you might be ready to start coding, but if the problem is challenging one you're probably not.

The first step to solving a problem as a human is to look at an example and in this case let's say we wanted to find the difference between the dates (2013,01,24) and (2013,06,29), as a human the first thing that we might do is to look at a calendar and find the first date, then see how many days are left for that month, in this case, that is 7 days, we would probably grab a piece of paper and write that number down. Then we would count how many days has every month until we reach the second date.
`7+28+31+30+31+29 = 156`

We now have a starting point and it's time to write down an algorithm that systematizes how we solve it, in this case, we're going to write this as `Pseudocode`

meaning we aren't focusing on coding real python code but the ideas instead.

```
# find the days between the dates (2013,01,24) and (2013,06,29)
#(year1,month1,day1,year2,month2,day2)
days = # days in month1(31) - starting day(24) = 7
while month1 < month2:
days += # days in current month1
month1 += 1 # moving to the next month
days += day2 # add the remaining days from the second date month(29)
# add the years days
while year1 < year2:
days += # days in year1
```

In this case, I don't think we should, there are several cases that this code does not consider, like:

- input dates in same month
`(2013,06,24 and 2013,06,29) # valid input`

- month2 < month1
`(2012,07,24 and 2013,06,29) # valid input`

- year2 < year1
`(2013,01,24 and 2012,06,29) # invalid input`

- accounting for leap years.

Let's think of a more simple mechanical algorithm. As humans, it's very inefficient to manually count day by day, but not for computers, and I want to emphasize optimization, don't optimize prematurely! focus on solving the problem first.

```
days = 0
while date1 < date2:
date1 += # advance to next day
days += 1
return days
```

This is the most simple approach possible, it's adding 1 day until we reach the second date.

I want to take a look at line three.

```
date1 += # advance to next day
```

This is clearly the most important part, and because of it, we should start by coding a `nextDay(year,month,day)`

procedure. The function should receive a year, month, and day and return the year, month, and day of the next day.

```
def nextDay(year, month, day):
"""
For simplicity, assume every month has 30 days.
"""
if day < 30:
return year, month, day + 1
elif month < 12:
return year, month + 1, 1
else:
return year + 1, 1 ,1
print(nextDay(2021,3,11)) # expected output: 2021,3,12
print(nextDay(2021,3,29)) # expected output: 2021,3,30
print(nextDay(2021,3,30)) # expected output: 2021,4,1
print(nextDay(2021,11,30)) # expected output: 2021,12,1
print(nextDay(2021,12,30)) # expected output: 2022,1,1
```

As we can see, the code above accounts for the case that it's the end of the month and for the end of the year, the only problem with it it's that it's assuming all months have 30 days, so, what should we do next?

In this case, we can make the `nextDay`

procedure to work with real months but we can actually do that later and start coding the `daysBetweenDates`

procedure first, the advantage is that we'll be more confident if we're on the right track and most importantly, we'll be closer to having an answer, then we can correct the `nextDay`

procedure since that will be a significantly smaller detail that shouldn't affect our `daysBetweenDates`

procedure.

```
days = 0
while date1 < date2:
date1 += # advance to next day
days += 1
return days
```

with our new `nextDay`

procedure we can now code this right? well, not quite, if you have already tried you realized what the problem is. The comparison between dates.

```
while date1 < date2:
```

Let's understand exactly what the helper function shall do, in this case, we only need to return a boolean, True if the first date is before the second, and False if it is not. Let's code it!.

```
def date1BeforeDate2(year1,month1,day1,year2,month2,day2):
"""
returns True if the first date is before the second date
"""
if year1 < year2:
return True
if month1 < month2:
return True
if day1 < day2:
return True
return False
```

The solution for this helper function was really easy, we only needed to compare if the year1 was before year2, if that condition it's True, simply return True, then repeat the condition with the months and days, if none of the conditions were True simply return False.

Now that we have our helper function, it's time to try to code the pseudocode again with our newly created function.

Pseudocode:

```
days = 0
while date1 < date2:
date1 += # advance to next day
days += 1
return days
```

DaysBetweenDates procedure:

```
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
"""
returns the difference of two dates in days
"""
days = 0
while date1BeforeDate2(year1, month1, day1, year2, month2, day2):
year1, month1, day1 = nextDay(year1, month1, day1)
days += 1
return days
print(daysBetweenDates(2019, 5, 15, 2021, 5, 15)) # output should have been 731
print(daysBetweenDates(2023, 5, 15, 2020, 5, 15)) # invalid inputs
```

So much progress! however, if you run the code above you will realize that the output is 720 when it should have been 731, why?. Let's not forget that the `nextDay`

procedure is incorrectly assuming all months have 30 days!!. However, it's very clear that we're on the right track. For the second print, we purposely passed invalid inputs to see what happens, in this case, the code output 0, but that is not really what we want.

Like we talked about before, we are going to follow a good defensive programming practice and check for invalid inputs inside our `daysBetweenDates`

procedure.

Python `assert()`

is perfect for what we want. `assert()`

allows us to perform comparisons. If the expression contained within it is False, an exception will be thrown, specifically an `AssertionError`

.

Example:

```
assert(5 < 3) # False, an AssertionError will be shown
assert(2 < 4) # True, nothing will happen
```

This fits perfectly for what we want, that is, checking if the first date is before the second date. Here is my implementation:

```
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
"""
returns the difference of two dates in days
"""
assert(date1BeforeDate2(year1, month1, day1, year2, month2, day2))
days = 0
while date1BeforeDate2(year1, month1, day1, year2, month2, day2):
year1, month1, day1 = nextDay(year1, month1, day1)
days += 1
return days
```

Now if we test an invalid case where the second date is before the first date we should see an `AssertionError`

pop up in the terminal like the image below:

Right now, we're 70% of the way, some of the things left to do are to make sure that the `daysBetweenDates`

procedure accounts for leap years and months with the correct amount of days.

The way I'm going to approach this is that I'm going to write a `daysInMonth(year,month)`

procedure that returns the correct number of days of the specified month.

```
def daysInMonth(year, month):
return # the number of days of the specified month
```

First things first, I'm going to do some googling to figure out what months have 31 days.

After a quick search, I found that the months with 31 days are months: 01, 03, 05, 07, 08, 10, and 12. With this information we can now add a condition in our code, like this:

```
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
```

Let's repeat the process with the month of February, normally it has 28 days except on leap years where February has 29. For this first stage, we are going to check if the month is the number two and return 28 without accounting for leap years yet.

```
def daysInMonth(year, month):
"""
Function that receives a month and a year
and based on that, it will return the number
of days the specific month has.
WARNING: This code does not account for leap years yet
"""
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
elif month == 2:
return 28
else:
return 30
```

As you can see from the code above, in the case that none of the conditions are true, it will return the normal 30 days.

Let's go back to our `nextDay`

procedure and change it so that it now uses the correct number of days.

Before:

```
if day < 30:
```

After:

```
if day < daysInMonth(year, month):
```

Just as we coded our `daysInMonth`

helper procedure, we should also create a procedure to check if a specified year is a leap year, but before we code, we should understand what a leap year is.

## Leap year

a year, occurring once every four years, that has 366 days including February 29 as an intercalary day. - https://en.wikipedia.org/wiki/Leap_year

If you scroll below the Wikipedia article, you will find an algorithm section that is incredibly useful for programmers.

Here is my implementation of the algorithm:

```
def isLeapYear(year):
"""
Function that receives a year and returns
a boolean, True if the year is a leap year
and False if it's not
"""
if not (year % 4 == 0):
return False
elif not (year % 100 == 0):
return True
elif not (year % 400 == 0):
return False
else:
return True
```

After we are done writing our function it's important to test it to make sure it works, in this case, the code is giving the expected output so now it's time to integrate it in our `daysInMonth`

procedure.

For the moment, the `daysInMonth`

procedure does not account for leap years and it's returning 28 days if the month is February, let's change that with our newly created `isLeapYear`

procedure.

```
def daysInMonth(year, month):
"""
Function that receives a month and a year
and based on that, it will return the number
of days the specific month has, accounting for
leap years.
"""
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
elif month == 2:
if isLeapYear(year):
return 29
else:
return 28
else:
return 30
```

Now, if the month is February, the code will check if the year is a leap year, if that it's true it will return 29 days, and if it's not it will return the normal 28 days. At this point, it looks like we are done, now it's time to test the full code with several examples.

After testing we realized that everything looks to be working except for the case where the dates are the same.
`print(daysBetweenDates(2021, 8, 24, 2021, 8, 24))`

If you run this test case you will see an AssertionError when the output should be 0, to fix this I'm going to simply change the assertion line in the `daysBetweenDates`

procedure.

Before:

```
assert(date1BeforeDate2(year1, month1, day1, year2, month2, day2))
```

After:

```
assert not (date1BeforeDate2(year2, month2, day2, year1, month1, day1))
```

Now everything looks to be finished, it's time to do final testing to be confident our code it's working as expected, and in this case, it looks like we can consider our code done!.

Full python code:

```
def isLeapYear(year):
"""
Function that receives a year and returns
a boolean, True if the year is a leap year
and False if it's not
"""
if not (year % 4 == 0):
return False
elif not (year % 100 == 0):
return True
elif not (year % 400 == 0):
return False
else:
return True
def daysInMonth(year, month):
"""
Function that receives a month and a year
and based on that, it will return the number
of days the specific month has, accounting for
leap years.
"""
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
elif month == 2:
if isLeapYear(year):
return 29
else:
return 28
else:
return 30
def nextDay(year, month, day):
"""
The function receives a year, month, and day and returns
the year, month, and day of the next day.
"""
if day < daysInMonth(year, month):
return year, month, day + 1
elif month < 12:
return year, month + 1, 1
else:
return year + 1, 1, 1
def date1BeforeDate2(year1, month1, day1, year2, month2, day2):
"""
The function receives two dates and returns a boolean, True
if the first date is before the second, and False if it is not.
"""
if year1 < year2:
return True
if month1 < month2:
return True
if day1 < day2:
return True
return False
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
"""
returns the difference between two dates in days accounting for
leap years and no time travel.
"""
assert not (date1BeforeDate2(year2, month2, day2, year1, month1, day1))
days = 0
while date1BeforeDate2(year1, month1, day1, year2, month2, day2):
year1, month1, day1 = nextDay(year1, month1, day1)
days += 1
return days
print(daysBetweenDates(2019, 5, 24, 2020, 5, 24))
```

You've reached the end of this lesson on problem-solving for programmers, remember that this is a skill that takes a lifetime to master so don't feel frustrated if you don't get it right away because this isn't easy, but I hope that some of the guidelines I've shared today can help you with any problem you might encounter in the future.

That's all for today guys, I really hope this was helpful, and let me know in the comments any thoughts, suggestions and I will see you in the next post, stay tuned!.

]]>Hello everyone, today's post is going to be a bit shorter than usual but it's a very important topic non the less, I want to talk about adding Fonts to Figma.

Okay, so, when I was making a cover design in Figma I came up with the really common pain of not finding the right font for my project, normally you can find LOT's of fonts to try your design with, but I wasn't finding the font I wanted, `Sans Pro Display`

, I was really surprised this font didn't come included in the really big Figma library, and for the first time ever I had to add a font to Figma, in the end, it wasn't complicated but it took me way longer than it should have and that's why I wanted to share this with you.

The first step is going to find your wanted font, in my case, I simply googled "Sans Pro Display free Font Download" and easily found it here: https://www.cufonfonts.com/font/sf-pro-display, make sure you are downloading from a secure website.

Download your font and you should get a zip file, extract the elements from the zip file and you should get a folder with OpenType Font files like this:

Open your Figma project and if you select your desired text and go to ==> Design ==> Text and search for your newly downloaded Font you will realize that it cannot be found in the library.

The reason why you can't find the font it's because it's not installed in your system yet. To install the font you can simply double click the OpenType Font file and a screen like the one shown below should appear then you simply click on the install button.

This step is SUPER IMPORTANT and I really hoped someone would have told me to RESTART Figma, I lost a lot of time trying to figure out why the font was still not appearing and the solution was really simple, simply close the app and open again.

After restarting the app you should be able to select any text element, go to ==> Design ==> Text and search for the name of your font, Figma will likely auto-complete the search, and now we are ready to start using our new font.

In today's post, we learned how to add any font to Figma, I want to mention that this method is for the Desktop version only, and I really hope this post helps anyone that was looking to expand their font library, here it's the cover design I was working on:

Comment below any thoughts, suggestions, and i will see you in the next post.

]]>Hello everyone, in today's post I wanted to show you how to use the google cloud hosting service. using that service, we can host static web pages easily, free, and securely.

Firebase hosting will provide you impressive good speed without the need for separate CDN and it's FREE, easy to use, and provides the SSL certificate so that your website will be hosted securely with HTTPS.

- Google account for Firebase setup in console
- Domain: you can purchase a domain from any provider
- Firebase-CLI (installation process showed later in this post)

Open Firebase Console ==> Sign in with your Google account ==> Create project ==> Add title ==> Enable Google analyitics for your country.

See the images below for guidance.

Simply execute the following command:

```
npm install -g firebase-tools
```

or you can use:

```
yarn global add firebase-tools
```

- Create a new folder on your computer
- Open a new CMD(command prompt) and cd into the newly created folder
- Now execute the command:

```
firebase login
```

This will redirect you to the sign-in page in the browser, where you just need to simply sign in.

After successfully given access, you should get the below message on the browser

On your command prompt, simply execute the following command:

```
firebase init
```

After executing this command you should see the following screen:

After pressing enter to the question

are you ready to proceed?

you should select the option:

Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys

Use arrows to change selection and select using the `spacebar`

, then press `enter`

.

Once the Hosting option is selected, they'll provide the option to choose an existing project or create a new one. As we have already created a sample static project, you need to just choose the existing option and press `enter`

.

After pressing `enter`

you should see your Firebase project, select it, and press `enter`

.
Then it will ask you for a name for the public directory, you can put the same name as your project, and after pressing `enter`

the following question should appear

If your page is a single page like mine you should answer yes.

After we are done with the configuration we need to run the following command:

```
firebase serve
```

If we followed all of the steps correctly, in the folder created at the beginning we will find another folder and inside an index.html file that will be shown on `localhost:5000`

After watching the screen above without showing problems then we can exit with `Ctrl+C`

We're almost done!, now it's time to delete the index.html file and replace it with our wanted index.html, index.css, images, and icon files.

Back at the CMD terminal we can repeat step 5 and now we should see our website on `localhost:5000`

, it's likely that we see the old file instead of the new one so to fix that make sure to refresh the browser with `Ctrl+Shift+R`

After making sure everything works properly on `localhost:5000`

, we can now exit and proceed with the deploy

```
firebase deploy
```

As we can see from the image above, after the deploy it's complete, we should get a Hosting URL, if we paste that into our web browser we should see our website.

We are Done!

In the likely case that you don't want your website URL to look like this: https://sample-9f6cf.web.app/ and want to use a custom domain instead, we can do just that from the Project dashboard, just follow the images below.

In my case, I already had a domain from https://domains.google/ and after following the three steps from the image above we should get a register type, host, and value (image below).

Now we need to go to Google Domains ==> Select your domain ==>DNS ==> Manage custom records ==> Create new record and enter the values there ==> Save. See the images below for guidance.

Now we should wait some time, normally it's a few minutes, and after that, we are done hosting our website with firebase!.

I really hope this post has taught you how to host your website with Firebase and if you have any suggestions or ideas, let me know in the comments below, see you in the next post!.

]]>`PCA9685`

driver and thought I shared my experience with anyone interested in this topic.
- Raspberry Pi (mine is the 3B+)
- PCA9685 driver
- Servos (the driver supports up to 16)
- 4 female to female jumper cables.
- External 5v power for the driver
- Python3

5v-6v recommended power and you can connect as many servos as you like

Turn on your Raspberry Pi and open a new terminal, then run the following commands:

```
sudo apt-get install git build-essential python-dev
git clone https://github.com/adafruit/Adafruit_Python_PCA9685.git
cd Adafruit_Python_PCA9685
sudo python setup.py install
cd examples
```

Inside the examples folder, you should find the `simplest.py`

example code, to run it use the command

`python3 simplest.py`

However, if we execute this program we get an error

No such file or directory: '/dev/i2c-1'

To fix this we need to open the raspberry pi software configuration

`sudo raspi-config`

Use the down arrow to select interfacing options and press enter, then select P5 I2C and enable the interface ==> ok ==> finish.

Now, if we execute the `simplest.py`

file we shouldn't get any errors and our servo should start moving, Nice!, however the code it's unnecessarily complex for what I need. I would like to call a function that passes the target degree as a parameter and nothing else, this way the code would be easier to read and with fewer bugs, to accomplish this I'm going to use the Adafruit Servo Kit library for python

https://github.com/adafruit/Adafruit_CircuitPython_ServoKit

To use this library, open a new terminal on your Raspberry Pi and execute the following command:
`pip3 install adafruit-circuitpython-servokit`

Here we can see a full example:

```
from time import sleep
from adafruit_servokit import ServoKit
# Set channels to the number of servo channels on your kit.
# 8 for FeatherWing, 16 for Shield/HAT/Bonnet.
kit = ServoKit(channels=8)
kit.servo[0].angle = 175
sleep(1)
kit.servo[0].angle = 45
sleep(1)
```

We can specify the servo in the`kit.servo[0-15].`

I really hope this post helped you out and feel free to share this with someone that might need it, see you next time!.

]]>