Algorithms | Cheat Sheet


1 Sort

use case

void printArray(std::vector<int>& arr)
{
	for (auto& item : arr) cout << item << " ";
	cout << std::endl;
}
int main()int main()
{{
	std::vector<int> arr = {4, 6, 2, 8, 3, 1, 3, 9, 8, 6, 3, 4};	std::vector<int> arr = {4, 6, 2, 8, 3, 1, 3, 9, 8, 6, 3, 4};
	printArray(arr);	printArray(arr);
	quickSort(arr, 0, arr.size() - 1);quickSort(arr, 0, arr.size() - 1);
	printArray(arr);
}

Merge sort

void merge(std::vector<int>& arr, int lo, int mid, int hi)
{
	int num1 = mid - lo + 1;
	int num2 = hi - mid;
	std::vector<int> leftArr(arr.begin() + lo, arr.begin() + mid + 1);
	std::vector<int> rightArr(arr.begin() + mid + 1, arr.begin() + hi + 1);
	int i = 0;
	int j = 0;
	int k = lo;
	while (i < num1 && j < num2)
	{
		if (leftArr[i] <= rightArr[j]) arr[k++] = leftArr[i++];
		else arr[k++] = rightArr[j++];
	}
	while (i < num1)
	{
		arr[k++] = leftArr[i++];
	}
	while (i < num2)
	{
		arr[k++] = rightArr[j++];
	}
}
void mergeSort(std::vector<int>& arr, int lo, int hi)void mergeSort(std::vector<int>& arr, int lo, int hi)
{{
	if (lo < hi) {	if (lo < hi) {
		int mid = lo + (hi - lo) / 2;	int mid = lo + (hi - lo) / 2;
		mergeSort(arr, lo, mid);mergeSort(arr, lo, mid);
		mergeSort(arr, mid + 1, hi);mid + 1, hi);
		merge(arr, lo, mid, hi);(arr, lo, mid, hi);
	}}
}
private static void merge(int[] arr, int lo, int mid, int hi) {
	int[] help = new int[hi - lo + 1];
	int i = 0;
	int p1 = lo;
	int p2 = mid + 1;
	while (p1 <= mid && p2 <= hi) help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
	while (p1 <= mid) help[i++] = arr[p1++];
	while (p2 <= hi) help[i++] = arr[p2++];
	for (i = 0; i < help.length; i++) arr[lo + i] = help[i];
}
private static void mergesort(int[] arr, int lo, int hi) {
	if (lo >= hi) return;
	int mid = lo + (hi - lo) / 2;
	mergesort(arr, lo, mid);
	mergesort(arr, mid + 1, hi);
	merge(arr, lo, mid, hi);
}

Quick sort

  • 75. Sort Colors
int partition(std::vector<int>& arr, int lo, int hi)
{
	int ridx = std::rand() % (hi - lo + 1) + lo;
	int pivot = arr[ridx];
	int i = lo - 1;
	int j = hi + 1;
	while (true) {
		// Move the left index to the right at least once and while the element at the left index is less than the pivot
		do {
			i++;
		} while (arr[i] < pivot);
		// Move the right index to the left at least once and while the element at the right index is greater than the pivot
		do {
			j--;
		} while (arr[j] > pivot);
		// If the indices crossed, return
		if (i >= j) return j;
		// Swap the elements at the left and right indices
		std::swap(arr[i], arr[j]);
	}
}
void quickSort(std::vector<int>& arr, int lo, int hi)void quickSort(std::vector<int>& arr, int lo, int hi)
{{
	if (lo < hi) {	if (lo < hi) {
		int p = partition(arr, lo, hi);	int p = partition(arr, lo, hi);
		quickSort(arr, lo, p);quickSort(arr, lo, p);
		quickSort(arr, p + 1, hi);p + 1, hi);
	}}
}

Heap sort

Heap structure

https://leetcode.com/explore/featured/card/heap/643/heap/4018

In many computer science applications, we only need to access the largest or smallest element in the dataset.

A priority queue is an abstract data type, while a Heap is a data structure. Therefore, a Heap is not a Priority Queue, but a way to implement a Priority Queue. There are multiple ways to implement a Priority Queue, such as array and linked list. However, operations except for insertion or deletion will have O(N).

A Heap is a special type of binary tree.

  • Is a complete binary tree;
  • The value of each node must be no greater than (or no less than) the value of its child nodes.
class MinHeap
{
public:
	MinHeap(int heapSize)
		: heapSize(heapSize)
	{
		data.resize(heapSize + 1);
		data[0] = 0;
	}
	int peek() { return data[1]; }
	int size() { return realSize; }
	void add(int element) {
		realSize++;
		if (realSize > heapSize)
		{
			cout << "too many elements!" << endl;
			realSize--;
			return;
		}
		data[realSize] = element;
		int curr = realSize;
		// Note if we use an array to represent the complete binary tree
		// and store the root node at index 1
		// index of the parent node of any node is [index of the node / 2]
		// index of the left child node is [index of the node * 2]
		// index of the right child node is [index of the node * 2 + 1]
		int parent = curr / 2;
		// If the newly added element is smaller than its parent node,
		// its value will be exchanged with that of the parent node 
		while (data[curr] < data[parent] && curr > 1)
		{
			std::swap(data[curr], data[parent]);
			curr = parent;
			parent = curr / 2;
		}
	}
	int pop()
	{
		if (realSize < 1)
		{
			cout << "empty!" << endl;
			return INT_MAX;
		}
		int removeElement = data[1];
		// Put the last element in the Heap to the top of Heap
		data[1] = data[realSize];
		realSize--;
		int curr = 1;
		// When the element is not a leaf node
		while (curr <= realSize / 2)
		{
			int left = curr * 2;
			int right = curr * 2 + 1;
			// If the element is larger than the left or right child
			// its value needs to be exchanged with the smaller value
			// of the left and right child
			if (data[curr] <= data[left] && data[curr] <= data[right]) break;
			int minChild = data[left] < data[right] ? left : right;
			std::swap(data[curr], data[minChild]);
			curr = minChild;
		}
		return removeElement;
	}
	void print()
	{
		if (realSize == 0) cout << "no element!" << endl;
		std::string info = "[";
		for (int i = 1; i < realSize; ++i)
		{
			info = info + std::to_string(data[i]) + ",";
		}
		info += std::to_string(data[realSize]) + "]";
		cout << info << endl;
	}
private:
	std::vector<int> data;
	int heapSize;
	int realSize = 0;
};
int main()int main()
{{
	// Test case	// Test case
	MinHeap minHeap(3);MinHeap minHeap(3);
	minHeap.add(3);minHeap.add(3);
	minHeap.add(1);1);
	minHeap.add(2);2);
	minHeap.print();  // [1,3,2]print();  // [1,3,2]
	cout << minHeap.peek() << endl;  // 1cout << minHeap.peek() << endl;  // 1
	cout << minHeap.pop() << endl;  // 1op() << endl;  // 1
	minHeap.print();  // [2,3]minHeap.print();  // [2,3]
	minHeap.add(4);add(4);
	minHeap.add(5);  // Add too many elements5);  // Add too many elements
	minHeap.print();  // [2,3,4]print();  // [2,3,4]
}

sort:

void heapSort(std::vector<int>& arr, int lo, int hi)
{
	MinHeap minHeap(arr.size());
	for (auto& item : arr) minHeap.add(item);
	int curr = 0;
	while (minHeap.size() != 0)
	{
		arr[curr++] = minHeap.pop();
	}
}

2 Search

2.1 Binary search

3 Tree

Binary Tree

Traversal

https://leetcode.com/problems/binary-tree-preorder-traversal/description
  • preorder
    • use one stack(add root)
    • algo
      1. do something
      2. push right
      3. push left
    • LC144
  • postorder
    • two stacks(add root)
    • algo
      1. push curr to stack2
      2. push left to stack1
      3. push right to stack2
      4. use stack2 to do something
    • LC145
  • inorder
    • use one stack, don’t push root to the stack
    • algo
      1. if stack is not empty or root is not empty, keep going
      2. if root/curr is not null, keep push to the stack, root updated to its left(idea: keep push left to the stack)
      3. if root/curr is null, then do other logic:
        1. take item from the stack
        2. do something
        3. root updated to the item’s right.
    • LC94
  • BFS
    • use one queue(add root)
    • algo
      1. pop, do something
      2. push left if not null
      3. push right if not null
    • if you need level information, just create a hash map to record!
    • LC102, LC104

recursive(there are 3 places to do something):

void f(Node* node)
{
    // 1
    if (node == nullptr) return;
    // 1
    f(node->left);
    // 2
    // 2
    f(node->right);
    // 3
    // 3
}

preorder:

vector<int> preorderTraversal(TreeNode* root) {
	vector<int> res;
	if (nullptr == root) return res;
	std::stack<TreeNode*> stack;
	stack.push(root);
	while (!stack.empty())
	{
		TreeNode* curr = stack.top();
		stack.pop();
		res.push_back(curr->val);
                if (curr->right != nullptr) stack.push(curr->right);  // be careful
		if (curr->left != nullptr) stack.push(curr->left);		if (curr->left != nullptr) stack.push(curr->left);
	}}
	return res;	return res;
}

postorder:

vector<int> postorderTraversal(TreeNode* root) {
	if (root == nullptr) return {};
	vector<int> res;
	std::stack<TreeNode*> stack1;
	std::stack<TreeNode*> stack2;
	stack1.push(root);
	while (!stack1.empty())
	{
		TreeNode* curr = stack1.top();
		stack1.pop();
		stack2.push(curr);
		if (curr->left != nullptr) stack1.push(curr->left);
		if (curr->right != nullptr) stack1.push(curr->right);
	}
	while (!stack2.empty())
	{
		TreeNode* curr = stack2.top();
		stack2.pop();
		res.push_back(curr->val);
	}
	return res;
}

inorder:

vector<int> inorderTraversal(TreeNode* root) {
	if (root == nullptr) return {};
	std::vector<int> result;
	std::stack<TreeNode*> stack;
	while (!stack.empty() || root != nullptr)
	{
		if (root != nullptr)
		{
			stack.push(root);
			root = root->left;
		} else
		{
			TreeNode* curr = stack.top();
			stack.pop();
			result.push_back(curr->val);
			root = curr->right;
		}
	}
	return result;
}

BFS:

vector<vector<int>> levelOrder(TreeNode* root) {
	if (root == nullptr) return {};
	std::vector<std::vector<int>> res;
	std::queue<TreeNode*> queue;
	std::unordered_map<TreeNode*, int> levels;
	queue.push(root);
	levels.insert({root, 0});
	
	while (!queue.empty())
	{
		TreeNode* curr = queue.front();
		queue.pop();
		int level = levels[curr];
		if (res.size() < level + 1) res.resize(level + 1);
		res[level].push_back(curr->val);
		if (curr->left != nullptr)		if (curr->left != nullptr)
		{{
			queue.push(curr->left);	queue.push(curr->left);
			levels[curr->left] = level + 1;levels[curr->left] = level + 1;
		}}
		if (curr->right != nullptr)if (curr->right != nullptr)
		{		{
			queue.push(curr->right);	queue.push(curr->right);
			levels[curr->right] = level + 1;	levels[curr->right] = level + 1;
		}}
	}}
	return res;return res;
}

Red-black tree

https://iorilan.medium.com/those-data-structures-can-not-learn-from-leet-code-red-black-tree-1-2f14f6149bd3
https://www.cnblogs.com/crazymakercircle/p/16320430.html
  1. It is a self-balanced BST. (Binary Search Tree)
  2. Every node has a color (black or red)
  3. Root is black
  4. When Parent’s color is red then the child MUST be black
  5. From the current node to the leaf, the “black nodes on each path” is the same
  6. On every leaf, there are 2 “None” nodes, color is black

What you need to take note of are Rules #4 and #5. They are the 2 important ones contributing to “self-balancing”.

4 Graph

General structure

struct Edge;
struct Node {
	int id;
	int in = 0;
	int out = 0;
	std::vector<Edge*> edges;
	std::vector<Node*> nexts;
	Node(int id) : id(id) {}
};
struct Edge {
	int weight;
	Node* from;
	Node* to;
	Edge(Node* from, Node* to) : from(from), to(to) {}
};
struct Graph
{
	std::unordered_map<int, Node*> nodes;
	std::unordered_set<Edge*> edges;
	~Graph() {
		for (auto edge : edges) delete edge;
		for (auto node : nodes) delete node.second;
	}
	void Load(const vector<vector<int>>& flights)
	{
		for (auto& flight : flights)
		{
			int from = flight[0];
			int to = flight[1];
			int weight = flight[2];
			// new nodes
			if (nodes.find(from) == nodes.end())
			{
				nodes.insert({from, new Node(from) });
			}
			if (nodes.find(to) == nodes.end())
			{
				nodes.insert({ to, new Node(to) });
			}
			// new edges
			Node* fromNode = nodes[from];
			Node* toNode = nodes[to];
			Edge* newEdge = new Edge(weight, fromNode, toNode);
			fromNode->nexts.emplace_back(toNode);
			fromNode->out++;
			fromNode->edges.emplace_back(newEdge);
			toNode->in++;
			edges.insert(newEdge);
		}
	}
};
std::vector<std::vector<int>> adj_list(n);
for (auto& edge : edges)
{
    adj_list[edge[0]].push_back(edge[1]);
    adj_list[edge[1]].push_back(edge[0]);
}

Disjoint Set

Breadth-First Search(BFS)

https://leetcode.com/explore/featured/card/graph/620/breadth-first-search-in-graph/3883
  • use queue and set
  • 1971. Find if Path Exists in Graph
  • 797. All Paths From Source to Target
void bfs(Node* node)
{
	if (node == nullptr) return;
	std::queue<Node*> queue;
	std::unordered_set<Node*> set;
	queue.push(node);
	set.insert(node);
	while (!queue.empty()) {
		auto curr = queue.front();
		queue.pop();
		for (auto next : curr->nexts)
		{
			if (set.find(next) == set.end()) {
				set.insert(node);
				queue.push(node);
			}
		}
	}
}
class Solution {
public:
    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        vector<vector<int>> paths;
        if (graph.size() == 0) {
            return paths;
        }
        vector<int> path;
        queue<vector<int>> q;
        path.push_back(0);
        q.push(path);
        while (!q.empty()) {
            vector<int> currentPath = q.front();
            q.pop();
            int node = currentPath.back();
            for (int nextNode : graph[node]) {
                vector<int> tmpPath(currentPath.begin(), currentPath.end());
                tmpPath.push_back(nextNode);
                if (nextNode == graph.size() - 1) {
                    paths.push_back(tmpPath);
                } else {
                    q.push(tmpPath);
                }
            }
        }
        return paths;
    }
};

or

class Solution {
public:
    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        vector<vector<int>> res;
        int n = graph.size();
        queue<vector<int>> q;
        vector<int> path;
        path.push_back(0);
        q.push(path);
        while (!q.empty())
        {
            auto currentPath = q.front();
            q.pop();
            int curr = currentPath.back();
            if (curr == n - 1) res.push_back(currentPath);
            for (auto& adj : graph[curr])
            {
                vector<int> newPath = currentPath;
                newPath.push_back(adj);
                q.push(newPath);
            }
        }
        
        return res;
    }
};

Depth-First Search(DFS)

https://leetcode.com/explore/featured/card/graph/619/depth-first-search-in-graph/3882
https://www.bilibili.com/video/BV13g41157hK?p=9&vd_source=98a02d91469172eca3b7b2f6a6d6c19e
void dfs(Node* node)
{
	if (node == nullptr) return;
	std::stack<Node*> stack;
	std::unordered_set<Node*> set;
	stack.push(node);
	set.insert(node);
	// do something
	while (!stack.empty())
	{
		auto curr = stack.top();
		stack.pop();
		for (auto next : curr->nexts)
		{
			if (set.find(node) == set.end())
			{
				// stack.push(curr)
				stack.push(next);
				set.insert(next);
				// do something
				//break;
			}
		}
	}
}

Backtracking

  • 797. All Paths From Source to Target
class Solution {
public:
    int n;
    std::vector<int> path;
    void backtracking(std::vector<std::vector<int>>& paths, std::vector<std::vector<int>>& graph, int curr)
    {
        if (curr == n - 1)
        {
            paths.emplace_back(path);
            return;
        }
        for (const auto& next : graph[curr])
        {
            path.emplace_back(next);
            backtracking(paths, graph, next);
            path.pop_back();
        }
    }
    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        n = graph.size();
        std::vector<std::vector<int>> paths;
        path = { 0 };
        backtracking(paths, graph, 0);
        return paths;
    }
};

Single Source Shortest Path Algorithm

https://leetcode.com/explore/featured/card/graph/622/single-source-shortest-path-algorithm/3885
  • BFS can only solve the shortest path problem in unweighted graphs.
  • edge relaxation operation(ie. updating the label) is a key element in solving the single source shortest path problem.
  • Dijkstra’s algorithm can only be used to solve the “single source shortest path” problem in a graph with non-negative weights.
    • greedy approach
    • Each step selects the “minimum weight” from the currently reached vertices to find the “shortest path” to other vertices.
  • Bellman-Ford algorithm, on the other hand, can solve the “single-source shortest path” in a weighted directed graph with any weights, including, of course, negative weights.

Dijkstra’s Algorithm

  • LC743
  • LC787
// Dijkstra's algorithm to find the shortest path from the source to all other nodes
void dijsktra(std::vector<std::pair<int, int>>* adj, std::vector<int>& tree, int source, int n) {
	// Min-heap priority queue to get the node with the smallest distance/time
	using WeightSumNodePair = std::pair<int, int>;
	std::priority_queue<WeightSumNodePair, std::vector<WeightSumNodePair>, std::greater<WeightSumNodePair>> pq;
	pq.push({ 0, source });
	// Weight for the starting node is 0
	tree[source] = 0;
	while (!pq.empty())
	{
		// Get the current node and the weight
		int currWeight = pq.top().first;
		int currNode = pq.top().second;
		pq.pop();
		// In case other nodes updated the tree
		if (currWeight > tree[currNode]) continue;
		// Broadcast the signal to adjacent nodes
		for (auto edge : adj[currNode])
		{
			int weightToAdd = edge.first;
			int neighborNode = edge.second;
			// if new weight less than the last weight, then update it
			if (currWeight + weightToAdd < tree[neighborNode])
			{
				tree[neighborNode] = currWeight + weightToAdd;  // update
				// Push the neighbor node into the priority queue for further processing
				pq.push({ tree[neighborNode], neighborNode });
			}
		}
	}
}
using WeightDst = std::pair<int, int>;
int networkDelayTime(std::vector<std::vector<int>>& times, int n, int k) {
	// Adjacency list, defined it as per the maximum number of nodes
	// But can be defined with the input size as well
	std::vector<WeightDst> adj[101];
	// build the adj list
	for (auto& time : times) {
		int src = time[0];
		int dst = time[1];
		int weight = time[2];
		adj[src].push_back({ weight, dst});
	}
	std::vector<int> tree(n + 1, INT_MAX);
	dijsktra(adj, tree, k, n);
	int answer = INT_MIN;
	for (int i = 1; i <= n; ++i)
	{
		answer = std::max(answer, tree[i]);
	}
	return answer == INT_MAX ? -1 : answer;
}
int findCheapestPrice(int n, std::vector<std::vector<int>>& flights, int src, int dst, int k) {
	using WeightDstPair = std::pair<int, int>;
	using Next = std::vector<WeightDstPair>;
	std::vector<Next> adj(n);
	for (auto e : flights) {
		adj[e[0]].push_back({ e[1], e[2] });
	}
	std::vector<int> stops(n, INT_MAX);
	std::priority_queue<std::vector<int>, std::vector<std::vector<int>>, std::greater<std::vector<int>>> pq;
	// {dist_from_src_node, node, number_of_stops_from_src_node}
	pq.push({0, src, 0});
	while (!pq.empty())
	{
		auto temp = pq.top();
		pq.pop();
		int dist = temp[0];
		int node = temp[1];
		int steps = temp[2];
		// We have already encountered a path with a lower cost and fewer stops,
		// or the number of stops exceeds the limit.
		if (steps > stops[node] || steps > k + 1) continue;
		stops[node] = steps;
		if (node == dst) return dist;
		for (auto& [neighbor, price] : adj[node]) {
			pq.push({ dist + price, neighbor, steps + 1 });
		}
	}
	return -1;
}

Leave a Reply

Your email address will not be published. Required fields are marked *

css.php