leetcode orb slam3 5/99--> LeetCode 2: Add Two Numbers
Ah! LeetCode #2 – Add Two Numbers is the classic linked list problem. Let’s break it down and see how it can help you understand concepts used in ORB-SLAM3.
🧩 LeetCode 2: Add Two Numbers
Problem
You are given two non-empty linked lists representing two non-negative integers.
- Digits are stored in reverse order.
- Each node contains a single digit.
- Add the two numbers and return the sum as a linked list.
Example:
Input: l1 = [2,4,3], l2 = [5,6,4]
Output: [7,0,8] # because 342 + 465 = 807
1️⃣ What you learn from this problem
-
Linked list traversal
- You iterate through nodes sequentially.
- In SLAM: iterating through map points or keyframes in a graph is similar.
-
Carrying over values
- You manage a
carry
from one node to the next. - In SLAM: propagating information (e.g., pose updates or feature weights) along a graph is analogous.
- You manage a
-
Node creation / dynamic memory
- You create new nodes for the result dynamically.
- SLAM creates new keyframes or map points as the map grows.
-
Handling different lengths
- The lists can be different lengths.
- In SLAM: keyframes may see different numbers of features; algorithms must handle missing data gracefully.
2️⃣ Typical Python solution
def addTwoNumbers(l1, l2):dummy = ListNode(0)curr = dummycarry = 0while l1 or l2:x = l1.val if l1 else 0y = l2.val if l2 else 0total = x + y + carrycarry = total // 10curr.next = ListNode(total % 10)curr = curr.nextif l1: l1 = l1.nextif l2: l2 = l2.nextif carry:curr.next = ListNode(carry)return dummy.next
3️⃣ How it corresponds to ORB-SLAM3
Two Numbers Concept | ORB-SLAM3 Analogy |
---|---|
Linked list node traversal | Iterating through map points or features in keyframes |
Adding digits with carry | Propagating weights, matches, or updates through keyframes/map points |
Creating new result nodes | Creating new map points or keyframes |
Handling different lengths | Frames may have different number of features; SLAM must handle sparse observations |
Sequential dependency | State updates in SLAM are often sequential (pose graph optimization, feature tracking) |
✅ Key Takeaways
- Sequential data processing – Add Two Numbers teaches careful iteration through dynamic data structures.
- Propagation / accumulation – Carry handling is conceptually like propagating constraints or updates in SLAM.
- Dynamic creation – You need to dynamically expand the result, just like SLAM grows the map dynamically.
- One linked list = one set of features
- Nodes = map points
- Adding two numbers = fusing observations from two keyframes
note1: iterating through map points or keyframes in a graph
Great question 👍 — let’s go step by step into how SLAM does the equivalent of “iterating through linked lists” when dealing with map points and keyframes.
🔹 In ORB-SLAM3
SLAM maintains two main graph-like structures:
-
KeyFrame Graph
- Each KeyFrame stores camera pose + observed features.
- KeyFrames are linked to each other via a covisibility graph (edges weighted by shared map points).
-
Map Points
- Each MapPoint is a 3D landmark in the world.
- MapPoints store which KeyFrames observe them.
So:
- Traversing linked lists in LeetCode is like walking through neighbors in these graphs.
🔹 Example: Iterating KeyFrames
In KeyFrame.cc you’ll see code like this:
// Get all covisible keyframes
vector<KeyFrame*> KeyFrame::GetBestCovisibilityKeyFrames(const int &N)
{vector<pair<int, KeyFrame*> > vPairs;vPairs.reserve(mConnectedKeyFrameWeights.size());for (map<KeyFrame*, int>::iterator mit = mConnectedKeyFrameWeights.begin();mit != mConnectedKeyFrameWeights.end(); mit++){vPairs.push_back(make_pair(mit->second, mit->first));}// sort by weight, then return top Nsort(vPairs.begin(), vPairs.end());reverse(vPairs.begin(), vPairs.end());vector<KeyFrame*> vKeyFrames;vKeyFrames.reserve(N);for (size_t i=0; i<N && i<vPairs.size(); i++)vKeyFrames.push_back(vPairs[i].second);return vKeyFrames;
}
👉 Here SLAM is iterating through a map of connections (mConnectedKeyFrameWeights
) just like you’d traverse nodes in a linked list, but now it’s a graph with weighted edges.
🔹 Example: Iterating MapPoints
In Frame.cc or Tracking.cc, when projecting map points:
for(int i=0; i<N; i++)
{MapPoint* pMP = vpMapPoints[i];if(!pMP)continue;// Project map point into the current framecv::Point2f uv = project(pMP->GetWorldPos());if(IsInFrustum(uv))vpMatches[i] = pMP;
}
👉 This is equivalent to iterating through a linked list:
- Each node = a
MapPoint
- You check/use it if valid (
if (!pMP) continue;
) - You “carry” state forward (like carry in LeetCode #2).
🔹 Analogy to LeetCode #2 (Add Two Numbers)
LeetCode 2 | ORB-SLAM3 Equivalent |
---|---|
Linked list nodes | MapPoints or KeyFrames |
While loop through lists | For-loop through connected nodes |
carry between nodes | Information propagation (pose update, bundle adjustment residuals) |
Create new node in result | Create new MapPoint or add KeyFrame to graph |
Handle unequal lengths | Handle missing observations (some KeyFrames don’t see all points) |
✅ So, SLAM “does it” not with a single list but with graph traversal:
- KeyFrame Graph = nodes are keyframes, edges are covisibility.
- MapPoint associations = each keyframe links to multiple map points.
- Iteration = walking these structures to propagate constraints and update states.
note2: vector<pair<int, KeyFrame*> > vPairs;
Got it 👍 Let’s break down those two C++ lines step by step for a beginner, using very plain language:
vector<pair<int, KeyFrame*> > vPairs;
vPairs.reserve(mConnectedKeyFrameWeights.size());
1. vector<pair<int, KeyFrame*>> vPairs;
-
vector<...>
= a dynamic array (like Pythonlist
). -
pair<int, KeyFrame*>
= a tiny structure holding two things:int
→ the connection weight (like a score).KeyFrame*
→ a pointer to aKeyFrame
object (basically a memory address that tells you where the object is).
So, vector<pair<int, KeyFrame*>> vPairs;
means:
➡️ “Create an empty list called vPairs
, where each item is a pair of (weight, pointer-to-keyframe)
.”
In Python analogy:
vPairs = [] # list of (weight, keyframe) tuples
2. vPairs.reserve(mConnectedKeyFrameWeights.size());
reserve(n)
= tell the vector: “I expect to addn
elements. Please pre-allocate memory for them.”- It doesn’t change the current size of the vector, but it makes future inserts faster by avoiding repeated memory allocations.
Here, mConnectedKeyFrameWeights.size()
is the number of connected keyframes.
So this line says:
➡️ “Reserve space in vPairs
to hold exactly that many pairs.”
In Python, we don’t pre-allocate list size, so the closest analogy is just:
# no direct equivalent in Python, but imagine:
vPairs = [None] * len(mConnectedKeyFrameWeights)
✅ Why this is used in ORB-SLAM3
- ORB-SLAM3 often runs in real-time, so efficiency is critical.
- Using
reserve()
avoids unnecessary slowdowns when the program repeatedly grows the vector while inserting pairs.
note3: mConnectedKeyFrameWeights
Great catch 🎯 You’re asking about this variable:
mConnectedKeyFrameWeights
🔹 What is mConnectedKeyFrameWeights
?
It’s a member variable of the KeyFrame
class in ORB-SLAM3.
Defined in KeyFrame.h
something like:
std::map<KeyFrame*, int> mConnectedKeyFrameWeights;
🔹 Meaning
-
std::map<KeyFrame*, int>
= a dictionary (like Python’sdict
) that maps:KeyFrame*
→ pointer to another keyframeint
→ weight (strength of the connection)
So mConnectedKeyFrameWeights
means:
➡️ “For this keyframe, store all the other keyframes it is connected to, with a weight describing how strong the connection is.”
🔹 What does the weight mean?
The weight is usually the number of map points shared between the two keyframes.
- If two keyframes observe many of the same landmarks, they are strongly connected.
- If they share only a few landmarks, they are weakly connected.
🔹 Analogy with Python
In Python, this would look like:
# For one keyframe
mConnectedKeyFrameWeights = {keyframeA: 25, # shares 25 map pointskeyframeB: 10, # shares 10 map pointskeyframeC: 3 # shares 3 map points
}
This is like a graph adjacency list with weights.
🔹 Why it matters in SLAM
- The covisibility graph in ORB-SLAM3 is built from this map.
- When we want the best covisible keyframes, we look at this map, sort by weight, and take the top ones.
- This is exactly what the function
GetBestCovisibilityKeyFrames()
does.
✅ So in simple terms:
mConnectedKeyFrameWeights
= “a dictionary telling me which other keyframes are connected to me, and how strong that connection is.”
note4: how mConnectedKeyFrameWeights is updated
Perfect follow-up 👌 You’re asking:
how does
mConnectedKeyFrameWeights
(inside a KeyFrame) actually link back to a Frame?
🔹 The Relationship in ORB-SLAM3
-
Frame = a single camera image + extracted features (temporary, lives only while tracking).
-
KeyFrame = a “promoted” Frame that the system decides to keep in the map (persistent, part of the graph).
- Created in
Tracking.cc
when conditions are met. - Constructed from a
Frame
(basically a copy + more bookkeeping).
- Created in
🔹 Where mConnectedKeyFrameWeights
fits
-
Each KeyFrame stores:
- The list of MapPoints it observes.
- A map of other KeyFrames it shares points with →
mConnectedKeyFrameWeights
.
So the pipeline is:
-
Tracking creates a Frame (just current camera pose + features).
-
If the Frame is important, it becomes a KeyFrame.
KeyFrame* pKF = new KeyFrame(currentFrame, mpMap, mpKeyFrameDB);
Here
currentFrame
is passed into the constructor. -
When the KeyFrame is inserted into the map, ORB-SLAM3 compares its MapPoints with MapPoints of existing KeyFrames.
- If it shares many, it calls
AddConnection(pOtherKF, weight)
. - This updates
mConnectedKeyFrameWeights
.
- If it shares many, it calls
🔹 Example: AddConnection
In KeyFrame.cc
:
void KeyFrame::AddConnection(KeyFrame* pKF, const int &weight)
{if(!mConnectedKeyFrameWeights.count(pKF))mConnectedKeyFrameWeights[pKF] = weight;else if(mConnectedKeyFrameWeights[pKF] != weight)mConnectedKeyFrameWeights[pKF] = weight;UpdateBestCovisibles();
}
👉 This is exactly where the dictionary (mConnectedKeyFrameWeights
) is filled.
🔹 Analogy with Python
Imagine:
# Frame -> becomes a KeyFrame
kf1 = KeyFrame(frame1)
kf2 = KeyFrame(frame2)# They share 20 map points
kf1.mConnectedKeyFrameWeights[kf2] = 20
kf2.mConnectedKeyFrameWeights[kf1] = 20
Now kf1
and kf2
are linked in the covisibility graph with weight 20.
✅ Summary
-
Frame is temporary (per camera image).
-
KeyFrame is persistent (goes into the map).
-
mConnectedKeyFrameWeights = for each KeyFrame, store which other KeyFrames it shares features with, and how many.
-
It links back to Frames indirectly because:
- A KeyFrame is built from a Frame.
- Connections are built by comparing features (from Frames) and MapPoints.
note5: GetBestCovisibilityKeyFrames
Ah, you mean GetBestCovisibilityKeyFrames(int N)
in KeyFrame.cc
. Great question 🙌 — understanding who calls this function helps connect the whole ORB-SLAM3 pipeline.
🔹 Function Purpose (quick recap)
vector<KeyFrame*> KeyFrame::GetBestCovisibilityKeyFrames(const int &N)
- Looks at
mConnectedKeyFrameWeights
(the covisibility dictionary). - Sorts connections by weight (shared MapPoints).
- Returns the top N most strongly connected keyframes.
This is used whenever the system needs to know:
➡️ “Which keyframes are most relevant to this one?”
🔹 Where it gets called
-
Local Mapping
-
In
LocalMapping.cc
, when inserting a new keyframe, SLAM needs to decide which neighboring keyframes to optimize together (local bundle adjustment). -
Example call:
vector<KeyFrame*> vNeighs = pKF->GetBestCovisibilityKeyFrames(10);
-
-
Loop Closing
-
In
LoopClosing.cc
, during loop detection and correction, the system retrieves strong neighbors to validate a loop candidate and run pose graph optimization. -
Example:
vector<KeyFrame*> vNeighs = pKF->GetBestCovisibilityKeyFrames(10);
-
-
Relocalization / Tracking
- In
Tracking.cc
, when trying to relocalize the camera, the system compares the current frame with candidate keyframes. - Strong covisibility neighbors help verify matches.
- In
🔹 Analogy
Think of this like LeetCode 347 (Top K Frequent Elements):
- You don’t want all elements, just the top K.
- In SLAM, you don’t optimize against all keyframes, just the most relevant ones.
✅ Summary
GetBestCovisibilityKeyFrames(N)
is called by:
- LocalMapping → to select neighbors for local bundle adjustment.
- LoopClosing → to validate loop candidates.
- Tracking → during relocalization (finding where we are in the map).
So, it’s basically the “find my best friends” function of a keyframe, used in mapping, loop closing, and relocalization.
note6: is make_pair necessary
Nice detail 🤓 You’re zooming into this line inside GetBestCovisibilityKeyFrames
:
vPairs.push_back(make_pair(mit->second, mit->first));
🔹 What’s happening
-
mConnectedKeyFrameWeights
is amap<KeyFrame*, int>
.mit->first
→ pointer to a KeyFrame (KeyFrame*
)mit->second
→ the weight (int
)
But we want a pair<int, KeyFrame*>
(weight first, keyframe second) to sort by weight.
make_pair(a, b)
creates apair<type_of_a, type_of_b>
automatically, without you writing the types.
So:
make_pair(mit->second, mit->first)
creates a pair<int, KeyFrame*>
.
🔹 Is make_pair
necessary?
No ✅ It’s just a convenience. You could write:
vPairs.push_back({mit->second, mit->first});
(since C++11, list-initialization works).
Or even:
vPairs.emplace_back(mit->second, mit->first);
which constructs the pair in place, slightly more efficient.
🔹 Analogy in Python
# Using tuple()
pairs.append(tuple([weight, keyframe]))# Equivalent shorthand
pairs.append((weight, keyframe))
Both work the same. tuple()
is like make_pair
, while (a, b)
is shorthand.
✅ Summary
-
make_pair
is not strictly necessary. -
It was very common in pre-C++11 code.
-
In modern C++ you’d usually write:
vPairs.emplace_back(mit->second, mit->first);