Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add luokai0/cloud-skills-by-luo-kai- --skill "algorithms-expert"
Install specific skill from multi-skill repository
# Description
Expert-level algorithms and data structures. Use when implementing or explaining sorting, searching, graph algorithms (BFS, DFS, Dijkstra, A*), dynamic programming, greedy algorithms, trees, heaps, hash maps, or Big O analysis. Also use when the user mentions 'Big O', 'BFS', 'DFS', 'dynamic programming', 'binary search', 'graph algorithm', 'time complexity', 'space complexity', or 'leetcode'.
# SKILL.md
name: algorithms-expert
description: Expert-level algorithms and data structures. Use when implementing or explaining sorting, searching, graph algorithms (BFS, DFS, Dijkstra, A*), dynamic programming, greedy algorithms, trees, heaps, hash maps, or Big O analysis. Also use when the user mentions 'Big O', 'BFS', 'DFS', 'dynamic programming', 'binary search', 'graph algorithm', 'time complexity', 'space complexity', or 'leetcode'.
license: MIT
metadata:
author: luokai0
version: "1.0"
category: coding
Algorithms & Data Structures Expert
You are an expert in algorithms and data structures with deep knowledge of time/space complexity, problem-solving patterns, and practical implementation across languages.
Before Starting
- Problem type β sorting, searching, graph, DP, greedy, string, math?
- Language β Python, JavaScript, Go, Java, C++?
- Constraints β time limit, space limit, input size?
- Goal β solve a specific problem, understand a concept, optimize existing code?
- Level β learning fundamentals or optimizing for interviews/production?
Core Expertise Areas
- Complexity analysis: Big O time and space, amortized analysis, best/worst/average case
- Sorting: QuickSort, MergeSort, HeapSort, TimSort, counting/radix sort
- Searching: binary search, BFS, DFS, A*, bidirectional search
- Graph algorithms: Dijkstra, Bellman-Ford, Floyd-Warshall, Kruskal, Prim, topological sort
- Dynamic programming: memoization, tabulation, space optimization, common patterns
- Data structures: arrays, linked lists, stacks, queues, heaps, trees, tries, graphs, union-find
- String algorithms: KMP, Rabin-Karp, Z-algorithm, suffix arrays, edit distance
- Problem patterns: two pointers, sliding window, divide and conquer, backtracking
Key Patterns & Code
Complexity Reference
Data Structure Operations:
Array: Access O(1) Search O(n) Insert O(n) Delete O(n)
Linked List: Access O(n) Search O(n) Insert O(1) Delete O(1)
Hash Map: Access O(1) Search O(1) Insert O(1) Delete O(1) [avg]
Binary Search Tree: All O(log n) average, O(n) worst
Balanced BST: All O(log n) guaranteed
Heap: Insert O(log n) Extract-min O(log n) Peek O(1)
Trie: Insert/Search O(m) where m = key length
Sorting Algorithms:
QuickSort: O(n log n) avg, O(n^2) worst, O(log n) space β in-place, not stable
MergeSort: O(n log n) all, O(n) space β stable, divide and conquer
HeapSort: O(n log n) all, O(1) space β in-place, not stable
TimSort: O(n log n) all, O(n) space β stable, Python/Java default
CountSort: O(n + k), O(k) space β integer keys only
RadixSort: O(nk), O(n + k) space β integer/string keys
Graph Algorithms:
BFS: O(V + E) β shortest path (unweighted), level order
DFS: O(V + E) β cycle detection, topological sort, connected components
Dijkstra: O((V + E) log V) β shortest path (non-negative weights)
Bellman-Ford: O(VE) β shortest path (negative weights)
Floyd-Warshall: O(V^3) β all-pairs shortest path
Kruskal MST: O(E log E) β minimum spanning tree
Prim MST: O((V + E) log V) β minimum spanning tree
Binary Search β Template
# Standard binary search
def binary_search(nums: list[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2 # avoid overflow
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# Find leftmost position (first occurrence)
def lower_bound(nums: list[int], target: int) -> int:
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
return left
# Find rightmost position (last occurrence)
def upper_bound(nums: list[int], target: int) -> int:
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] <= target:
left = mid + 1
else:
right = mid
return left - 1
# Binary search on answer β when you can check feasibility
def min_days_to_bloom(bloomDay, m, k):
def can_bloom(day):
flowers = bouquets = 0
for d in bloomDay:
if d <= day:
flowers += 1
if flowers == k:
bouquets += 1
flowers = 0
else:
flowers = 0
return bouquets >= m
left, right = min(bloomDay), max(bloomDay)
while left < right:
mid = left + (right - left) // 2
if can_bloom(mid):
right = mid
else:
left = mid + 1
return left if can_bloom(left) else -1
Two Pointers & Sliding Window
# Two pointers β sorted array, find pair with target sum
def two_sum_sorted(nums: list[int], target: int) -> tuple[int, int]:
left, right = 0, len(nums) - 1
while left < right:
s = nums[left] + nums[right]
if s == target:
return (left, right)
elif s < target:
left += 1
else:
right -= 1
return (-1, -1)
# Sliding window β longest substring without repeating chars
def length_of_longest_substring(s: str) -> int:
char_index = {}
left = 0
max_len = 0
for right, char in enumerate(s):
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
char_index[char] = right
max_len = max(max_len, right - left + 1)
return max_len
# Sliding window β minimum window substring
from collections import Counter
def min_window(s: str, t: str) -> str:
need = Counter(t)
have = {}
formed = 0
required = len(need)
left = 0
best = (float('inf'), 0, 0)
for right, char in enumerate(s):
have[char] = have.get(char, 0) + 1
if char in need and have[char] == need[char]:
formed += 1
while formed == required:
if right - left + 1 < best[0]:
best = (right - left + 1, left, right)
left_char = s[left]
have[left_char] -= 1
if left_char in need and have[left_char] < need[left_char]:
formed -= 1
left += 1
return '' if best[0] == float('inf') else s[best[1]:best[2]+1]
Graph Algorithms
from collections import deque
import heapq
# BFS β shortest path in unweighted graph
def bfs_shortest_path(graph: dict, start: int, end: int) -> int:
if start == end:
return 0
visited = {start}
queue = deque([(start, 0)])
while queue:
node, dist = queue.popleft()
for neighbor in graph.get(node, []):
if neighbor == end:
return dist + 1
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, dist + 1))
return -1
# DFS β detect cycle in directed graph
def has_cycle(graph: dict, n: int) -> bool:
WHITE, GRAY, BLACK = 0, 1, 2
color = [WHITE] * n
def dfs(node):
color[node] = GRAY
for neighbor in graph.get(node, []):
if color[neighbor] == GRAY:
return True # back edge = cycle
if color[neighbor] == WHITE and dfs(neighbor):
return True
color[node] = BLACK
return False
return any(dfs(i) for i in range(n) if color[i] == WHITE)
# Dijkstra β shortest path with non-negative weights
def dijkstra(graph: dict, start: int) -> dict:
dist = {start: 0}
heap = [(0, start)] # (distance, node)
while heap:
d, node = heapq.heappop(heap)
if d > dist.get(node, float('inf')):
continue # stale entry
for neighbor, weight in graph.get(node, []):
new_dist = d + weight
if new_dist < dist.get(neighbor, float('inf')):
dist[neighbor] = new_dist
heapq.heappush(heap, (new_dist, neighbor))
return dist
# Topological sort β Kahn's algorithm (BFS-based)
def topological_sort(n: int, edges: list[tuple]) -> list[int]:
graph = [[] for _ in range(n)]
indegree = [0] * n
for u, v in edges:
graph[u].append(v)
indegree[v] += 1
queue = deque(i for i in range(n) if indegree[i] == 0)
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)
return result if len(result) == n else [] # empty = cycle detected
# Union-Find (Disjoint Set Union)
class UnionFind:
def __init__(self, n: int):
self.parent = list(range(n))
self.rank = [0] * n
self.components = n
def find(self, x: int) -> int:
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # path compression
return self.parent[x]
def union(self, x: int, y: int) -> bool:
px, py = self.find(x), self.find(y)
if px == py:
return False # already connected
# Union by rank
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
self.components -= 1
return True
def connected(self, x: int, y: int) -> bool:
return self.find(x) == self.find(y)
Dynamic Programming β Core Patterns
# Pattern 1: 1D DP β Fibonacci / Climbing Stairs
def climb_stairs(n: int) -> int:
if n <= 2:
return n
prev2, prev1 = 1, 2
for _ in range(3, n + 1):
prev2, prev1 = prev1, prev2 + prev1
return prev1
# Pattern 2: 2D DP β Longest Common Subsequence
def lcs(s1: str, s2: str) -> int:
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
# Pattern 3: Knapsack β 0/1 Knapsack
def knapsack(weights: list[int], values: list[int], capacity: int) -> int:
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
# Iterate backwards to avoid using item twice
for w in range(capacity, weights[i] - 1, -1):
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[capacity]
# Pattern 4: Interval DP β Burst Balloons
def max_coins(nums: list[int]) -> int:
nums = [1] + nums + [1]
n = len(nums)
dp = [[0] * n for _ in range(n)]
for length in range(2, n):
for left in range(0, n - length):
right = left + length
for k in range(left + 1, right):
dp[left][right] = max(
dp[left][right],
dp[left][k] + nums[left] * nums[k] * nums[right] + dp[k][right]
)
return dp[0][n - 1]
# Pattern 5: Memoization template
from functools import lru_cache
def solve_with_memo(n: int) -> int:
@lru_cache(maxsize=None)
def dp(state):
if state == 0:
return base_case
# Try all choices
return min(dp(state - choice) + cost for choice in choices if choice <= state)
return dp(n)
Trees β Essential Patterns
from collections import deque
from typing import Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# Inorder traversal (iterative β avoids recursion limit)
def inorder(root: Optional[TreeNode]) -> list[int]:
result, stack = [], []
curr = root
while curr or stack:
while curr:
stack.append(curr)
curr = curr.left
curr = stack.pop()
result.append(curr.val)
curr = curr.right
return result
# Level order traversal (BFS)
def level_order(root: Optional[TreeNode]) -> list[list[int]]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
result.append(level)
return result
# Lowest Common Ancestor
def lca(root: Optional[TreeNode], p: TreeNode, q: TreeNode) -> Optional[TreeNode]:
if not root or root == p or root == q:
return root
left = lca(root.left, p, q)
right = lca(root.right, p, q)
if left and right:
return root # p and q are in different subtrees
return left or right
# Validate BST
def is_valid_bst(root: Optional[TreeNode]) -> bool:
def validate(node, min_val, max_val):
if not node:
return True
if not (min_val < node.val < max_val):
return False
return (validate(node.left, min_val, node.val) and
validate(node.right, node.val, max_val))
return validate(root, float('-inf'), float('inf'))
# Trie
class Trie:
def __init__(self):
self.children = {}
self.is_end = False
def insert(self, word: str) -> None:
node = self
for char in word:
if char not in node.children:
node.children[char] = Trie()
node = node.children[char]
node.is_end = True
def search(self, word: str) -> bool:
node = self
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end
def starts_with(self, prefix: str) -> bool:
node = self
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
Heap Patterns
import heapq
# Min heap β Python's heapq is a min heap
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)
smallest = heapq.heappop(heap) # 1
# Max heap β negate values
max_heap = []
heapq.heappush(max_heap, -3)
heapq.heappush(max_heap, -1)
largest = -heapq.heappop(max_heap) # 3
# K largest elements
def k_largest(nums: list[int], k: int) -> list[int]:
return heapq.nlargest(k, nums)
# K smallest elements
def k_smallest(nums: list[int], k: int) -> list[int]:
return heapq.nsmallest(k, nums)
# Kth largest β O(n log k) with min heap of size k
def kth_largest(nums: list[int], k: int) -> int:
heap = []
for num in nums:
heapq.heappush(heap, num)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
# Merge k sorted lists
def merge_k_sorted(lists: list[list[int]]) -> list[int]:
heap = []
for i, lst in enumerate(lists):
if lst:
heapq.heappush(heap, (lst[0], i, 0))
result = []
while heap:
val, i, j = heapq.heappop(heap)
result.append(val)
if j + 1 < len(lists[i]):
heapq.heappush(heap, (lists[i][j+1], i, j+1))
return result
Backtracking Template
# General backtracking template
def backtrack(state, choices, result):
# Base case: valid complete solution
if is_complete(state):
result.append(state[:])
return
for choice in choices:
if is_valid(state, choice):
state.append(choice) # make choice
backtrack(state, choices, result)
state.pop() # undo choice
# Example: generate all permutations
def permutations(nums: list[int]) -> list[list[int]]:
result = []
used = [False] * len(nums)
def backtrack(current):
if len(current) == len(nums):
result.append(current[:])
return
for i, num in enumerate(nums):
if not used[i]:
used[i] = True
current.append(num)
backtrack(current)
current.pop()
used[i] = False
backtrack([])
return result
# Example: N-Queens
def solve_n_queens(n: int) -> list[list[str]]:
result = []
cols = set()
diag1 = set() # row - col
diag2 = set() # row + col
board = [['.' ] * n for _ in range(n)]
def backtrack(row):
if row == n:
result.append([''.join(r) for r in board])
return
for col in range(n):
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue
cols.add(col)
diag1.add(row - col)
diag2.add(row + col)
board[row][col] = 'Q'
backtrack(row + 1)
cols.remove(col)
diag1.remove(row - col)
diag2.remove(row + col)
board[row][col] = '.'
backtrack(0)
return result
Problem-Solving Framework
Step 1: Understand (2 min)
- Restate the problem in your own words
- Identify input/output types and constraints
- Ask: what are the edge cases?
Step 2: Examples (2 min)
- Work through 2-3 examples manually
- Include edge cases: empty input, single element, duplicates
Step 3: Brute Force (2 min)
- State the naive O(n^2) or O(2^n) solution
- Explain why it is too slow
Step 4: Optimize (5 min)
- Identify the bottleneck
- Apply a technique: hash map, two pointers, DP, sorting, heap
- State new time and space complexity
Step 5: Implement (10 min)
- Write clean, readable code
- Use meaningful variable names
- Handle edge cases
Step 6: Test (3 min)
- Trace through your examples
- Test edge cases: empty, single, max size
- Check for off-by-one errors
Pattern Recognition:
Use hash map when: need O(1) lookup, counting, two-sum style
Use two pointers when: sorted array, palindrome, container problem
Use sliding window when: subarray/substring with constraint
Use BFS when: shortest path, level-by-level, unweighted
Use DFS when: explore all paths, tree traversal, cycle detection
Use DP when: overlapping subproblems, optimal substructure
Use greedy when: local optimal = global optimal, interval problems
Use heap when: k-th element, top-k, streaming median
Use union-find when: connected components, cycle detection, MST
Use trie when: prefix matching, word search, autocomplete
Best Practices
- Always analyze time AND space complexity before coding
- Start with brute force and explain trade-offs before optimizing
- Draw out examples on paper/whiteboard before writing code
- Use mid = left + (right - left) // 2 to avoid integer overflow
- In Python use collections.deque for O(1) popleft, not list.pop(0)
- Prefer iterative DFS over recursive to avoid stack overflow on large inputs
- Always handle edge cases: empty input, single element, all same values
- Name variables clearly: left/right not i/j for two pointers
Common Pitfalls
| Pitfall | Problem | Fix |
|---|---|---|
| Integer overflow | mid = (left + right) // 2 overflows | Use left + (right - left) // 2 |
| Modifying list while iterating | Skips elements or crashes | Iterate over copy or use index |
| Off-by-one in binary search | Infinite loop or wrong result | Use invariant: left <= right |
| Not handling empty/null input | Runtime error | Check edge cases first |
| Exponential recursion without memo | TLE on overlapping subproblems | Add memoization or use tabulation |
| Wrong BFS vs DFS choice | Finds path but not shortest | Use BFS for shortest unweighted path |
| Mutating input | Breaks test cases, side effects | Work on copy or document mutation |
| Incorrect base case in DP | Wrong results for small inputs | Verify base cases manually |
Related Skills
- python-expert: For Python-specific algorithm implementation
- concurrency-expert: For parallel algorithm patterns
- system-design: For applying algorithms in system design
- rust-expert: For high-performance algorithm implementation in Rust
- performance-optimization: For profiling and optimizing algorithms
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.