Introduction to Leetcode 127 Word Ladder
- Introduction to Leetcode 127 Word Ladder
- Prerequisites for Solving Word Ladder
- Understanding the Leetcode 127 Word Ladder Problem
- Why BFS is the Ideal Approach
- Conceptualizing the Graph
- Step-by-Step Solution for Leetcode 127 Word Ladder
- Python Code Implementation
- Complexity Analysis
- Common Mistakes and How to Avoid Them
- Conclusion
- Frequently Asked Questions
- Further Reading & Resources
Welcome to this in-depth tutorial where we'll explore Leetcode 127 Word Ladder, a classic problem that masterfully combines graph theory with string manipulation. This challenge is frequently encountered in technical interviews and is an excellent way to solidify your understanding of Breadth-First Search (BFS). Our goal is to make it easy for you to master the BFS approach, providing clear steps and a robust Python implementation.
The Word Ladder problem asks us to find the shortest transformation sequence from a beginWord to an endWord, given a dictionary wordList. Each step in the transformation must change only one letter, and every intermediate word must exist in the wordList. This problem inherently screams "shortest path in an unweighted graph," making BFS the perfect algorithm to tackle it efficiently. By the end of this guide, you'll not only understand the solution but also be able to implement it confidently.
Prerequisites for Solving Word Ladder
Before we dive deep into the solution for Leetcode 127 Word Ladder, it's beneficial to have a solid understanding of a few fundamental concepts. These prerequisites will ensure you can follow along with the logic and code without getting stuck on basics. Familiarity with these topics will significantly enhance your learning experience and your ability to apply the solution effectively.
First, a good grasp of Breadth-First Search (BFS) is essential. BFS is a graph traversal algorithm that explores all the neighbor nodes at the present depth level before moving on to the nodes at the next depth level. It's particularly suited for finding the shortest path in unweighted graphs, which is precisely what the Word Ladder problem requires. If you're new to BFS, a quick review of its mechanics, queues, and visited sets will be incredibly helpful. For a practical application of BFS in another challenging problem, check out our guide on Unraveling the 01 Matrix: Finding the Nearest Zero with BFS and DP.
Second, an understanding of Graph Theory Basics is crucial. The Word Ladder problem can be modeled as finding a path in an implicit graph where words are nodes and an edge exists between two words if they differ by exactly one letter. Knowing what nodes, edges, and paths represent in this context will make the solution much clearer. Exploring related graph algorithms, such as Mastering Depth-First Search (DFS), can further solidify your understanding of graph traversal techniques. Concepts like adjacency are key to visualizing how words connect.
Lastly, proficiency in Python Data Structures such as lists, sets, and dictionaries (hash maps) will be very beneficial. We'll be using a queue for BFS, a set to keep track of visited words, and a dictionary to efficiently find potential neighbors. A basic understanding of string manipulation in Python will also be helpful for tasks like comparing words or generating generic states.
Understanding the Leetcode 127 Word Ladder Problem
The core of successfully solving any algorithmic problem lies in a thorough understanding of its statement, constraints, and examples. For Leetcode 127 Word Ladder, the problem asks us to find the length of the shortest transformation sequence from a beginWord to an endWord. This transformation must adhere to specific rules: each adjacent word in the sequence must differ by exactly one letter, and every transformed word (except the beginWord) must be present in a given wordList. If no such transformation sequence exists, we should return 0.
Let's consider an illustrative example to clarify the problem statement. Suppose beginWord = "hit", endWord = "cog", and wordList = ["hot","dot","dog","lot","log","cog"]. We need to find the shortest sequence. A possible transformation could be:
hit -> hot -> dot -> dog -> cog.
In this sequence, "hit" differs from "hot" by one letter, "hot" from "dot" by one, and so on, with all intermediate words (hot, dot, dog, log, cog) existing in the wordList. The length of this sequence is 5. We are looking for the shortest such sequence.
Key constraints and considerations for this problem include:
- All words have the same length.
- All words consist of lowercase English letters.
beginWordandendWordare non-empty and differ from each other.endWordmust be inwordList. If it's not, no path exists, and the answer is 0.beginWorddoes not need to be inwordList, but if it is, it can be part of the intermediate path. However, when we start the BFS,beginWordacts as our initial node.- The
wordListcontains distinct words.
The goal is not to return the sequence itself, but its length. The beginWord counts as one word in the sequence. Thus, if beginWord = "a" and endWord = "c" with wordList = ["a", "b", "c"], the sequence a -> b -> c has a length of 3.
Why BFS is the Ideal Approach
When tackling the Leetcode 127 Word Ladder problem, the choice of algorithm is paramount, and Breadth-First Search (BFS) stands out as the ideal candidate. The primary reason for this is the problem's explicit requirement to find the shortest transformation sequence. BFS inherently guarantees finding the shortest path in an unweighted graph, which perfectly aligns with this objective.
Let's break down why BFS is so suitable here. Imagine each word in our wordList (and the beginWord) as a node in a graph. An edge exists between two nodes (words) if they differ by exactly one letter. Since each "step" or "transformation" has an equal "cost" (changing one letter), the graph is unweighted. BFS explores the graph layer by layer, expanding outwards from the starting node. This means it will always discover all nodes at depth k before moving to any nodes at depth k+1. Consequently, the first time it reaches the endWord, it guarantees that this path is the shortest possible path from the beginWord because all shorter paths (if they existed) would have been explored and completed at earlier depths.
In contrast, Depth-First Search (DFS) would not be suitable for finding the shortest path directly. DFS explores as far as possible along each branch before backtracking. While DFS can find a path, there's no guarantee it would be the shortest one without additional modifications (like tracking minimum length and exploring all paths, which essentially becomes dynamic programming or backtracking and is less efficient for shortest path in unweighted graphs). For instance, DFS might explore a very long branch before finding the endWord, whereas BFS would immediately find the endWord if it's reachable through a short sequence.
Furthermore, BFS uses a queue structure, which naturally manages the exploration level by level. We can store (word, level) pairs in the queue, incrementing the level for each new layer of words we explore. This allows us to easily track the length of the transformation sequence as we traverse the graph. The moment we dequeue the endWord, we know its associated level represents the shortest path length.
Conceptualizing the Graph
To effectively apply BFS to the Leetcode 127 Word Ladder problem, we first need to clearly conceptualize the underlying graph structure. Although the problem doesn't explicitly give us an adjacency list or matrix, we can infer a graph where:
- Nodes: Each word in the
wordList(and thebeginWord) represents a node. - Edges: An edge exists between two words if they differ by exactly one letter. This means they are "one-step transformable" from each other.
The challenge lies in efficiently finding these edges. A naive approach would be to compare every word in the wordList with every other word to check for a one-letter difference. This would be an O(N^2 * L) operation to build the graph upfront, where N is the number of words and L is the length of each word. For larger wordList sizes, this pre-processing can be prohibitively slow.
A more optimized approach, often referred to as the "wildcard" or "generic state" method, allows us to find neighbors much more efficiently. Instead of comparing word pairs, we pre-process the wordList to create a map (dictionary) where keys are "generic words" (words with one letter replaced by a wildcard, like *) and values are lists of actual words that match that generic pattern.
For example, if we have words like "hot", "dot", "lot", then:
h*twould map to["hot"]*otwould map to["hot", "dot", "lot"]ho*would map to["hot"]d*twould map to["dot"]do*would map to["dot"]l*twould map to["lot"]lo*would map to["lot"]
When we are processing a current_word (say, "hot"), we can generate all its L generic patterns (*ot, h*t, ho*). For each generic pattern, we look it up in our pre-processed map. The list of words associated with that generic pattern will give us all immediate neighbors that differ by one letter. This significantly speeds up neighbor discovery during the BFS, making it close to O(L) for each word, rather than O(N*L).
This all_combo_dict (our wildcard dictionary) is critical for performance. It transforms the problem from repeatedly scanning the entire wordList for neighbors to a simple dictionary lookup. We need to be careful with edge cases; for instance, the beginWord might not be in the wordList, but the endWord must be. If endWord is not in wordList, no path is possible, and we can immediately return 0.
Step-by-Step Solution for Leetcode 127 Word Ladder
Let's walk through the detailed steps to implement the BFS solution for the Leetcode 127 Word Ladder problem. This structured approach will ensure clarity and help you understand the logic behind each part of the code.
Step 1: Initialize Data Structures and Validate Input
The first crucial step is to set up our environment and handle initial conditions. We need to ensure that the endWord is actually reachable within the given wordList. If endWord is not present, no transformation is possible, so we can immediately return 0.
- Word List to Set: Convert
wordListinto asetforO(1)average time complexity lookups. This is crucial for performance when checking if a word exists in the dictionary. - Queue for BFS: Initialize a
deque(double-ended queue) from thecollectionsmodule. This will store tuples of(word, level), wherewordis the current word being processed, andlevelis its distance frombeginWord. Our BFS starts with(beginWord, 1). - Visited Set: Initialize another
setto keep track of words we have already visited. This prevents cycles and redundant processing, ensuring we find the shortest path and don't get stuck in loops. MarkbeginWordas visited from the start.
Step 2: Pre-process the Word List for Efficient Neighbor Lookups
This is the optimization step using "generic states" or "wildcard patterns." Instead of repeatedly generating neighbors by comparing words, we build an adjacency map beforehand.
all_combo_dict: Create a dictionary, let's call itall_combo_dict.- Iterate through every
wordin theword_set. - For each
word, generate all possible "generic words" by replacing each of its letters with a wildcard character (e.g.,*). For a word "hot" of lengthL=3, generate*ot,h*t,ho*. - Store these generic words as keys in
all_combo_dict. The value for each key should be a list of actual words fromword_setthat match this generic pattern. - Example: If
word_setcontains "hot", "dot", "lot", thenall_combo_dict['*ot']would eventually store["hot", "dot", "lot"].
- Iterate through every
This pre-processing step creates an efficient lookup mechanism. When we need to find neighbors for a word X, we just generate L generic patterns for X, and for each pattern, retrieve the list of matching words from all_combo_dict.
Step 3: Start the Breadth-First Search Traversal
With our data structures initialized and our all_combo_dict ready, we can now begin the core BFS algorithm.
- Enqueue Initial State: Add the
(beginWord, 1)tuple to ourqueue. The1represents the length of the sequence starting withbeginWorditself. - Mark as Visited: Immediately add
beginWordto ourvisitedset to prevent re-processing it.
Step 4: Process the Queue Until Empty
This is the main loop of our BFS. We continue as long as there are words in our queue to process.
- Dequeue Current Word: In each iteration,
popthe leftmost element (FIFO) from thequeue. This element will be a(current_word, level)tuple. - Check for End Word: If
current_wordis equal toendWord, we've found the shortest path! Returnlevelimmediately. Since BFS explores level by level, the first timeendWordis reached, it must be via the shortest possible sequence. - Generate Neighbors:
- For each character position
ifrom0toL-1(whereLis the length ofcurrent_word):- Create a
generic_wordby replacing the character at positioniwith a wildcard (*). - Look up this
generic_wordinall_combo_dict. - For every
neighbor_wordin the list retrieved fromall_combo_dictfor thatgeneric_word:- Check Visited: If
neighbor_wordhas not been visited (neighbor_wordnot invisited):- Add
neighbor_wordto thevisitedset. - Enqueue
(neighbor_word, level + 1)into thequeue. This indicates we've found a new word one step further in the transformation sequence.
- Add
- Check Visited: If
- Create a
- For each character position
Step 5: Handle No Path Found
If the queue becomes empty, and we have not returned from the loop (meaning endWord was never found), it signifies that there is no valid transformation sequence from beginWord to endWord under the given constraints. In this scenario, we should return 0.
This step-by-step breakdown covers the entire logic for solving Leetcode 127 Word Ladder using BFS with the wildcard optimization. The next section will present the actual Python code implementation.
Python Code Implementation
Here's the complete Python implementation of the BFS solution for Leetcode 127 Word Ladder, incorporating all the steps discussed.
from collections import deque
from typing import List
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
# Step 1: Validate Input and Initialize Data Structures
# Convert wordList to a set for O(1) average time complexity lookups.
word_set = set(wordList)
# If endWord is not in the wordList, no transformation is possible.
if endWord not in word_set:
return 0
# Queue for BFS, storing (word, level) tuples.
# Start with beginWord at level 1.
queue = deque([(beginWord, 1)])
# Visited set to keep track of words already processed.
visited = {beginWord}
# Length of words. All words have the same length as per constraints.
L = len(beginWord)
# Step 2: Pre-process the Word List for Efficient Neighbor Lookups
# all_combo_dict maps generic words (with a wildcard) to a list of actual words.
all_combo_dict = {}
for word in word_set:
for i in range(L):
# Create a generic word by replacing the i-th char with '*'
generic_word = word[:i] + "*" + word[i+1:]
# Add the actual word to the list for this generic pattern
all_combo_dict.setdefault(generic_word, []).append(word)
# Step 3 & 4: Start and Process the BFS Traversal
while queue:
current_word, level = queue.popleft() # Dequeue (word, level)
# If current_word is the endWord, we found the shortest path.
if current_word == endWord:
return level
# Generate all possible generic transformations for the current word
for i in range(L):
generic_word = current_word[:i] + "*" + current_word[i+1:]
# Retrieve all actual words matching this generic pattern
# from our pre-processed dictionary.
# Use .get() with an empty list as default to handle cases
# where a generic_word might not have any matches (e.g., if beginWord
# creates a generic_word not matching any in word_set)
for neighbor_word in all_combo_dict.get(generic_word, []):
# If the neighbor word has not been visited yet,
# mark it as visited and enqueue it.
if neighbor_word not in visited:
visited.add(neighbor_word)
queue.append((neighbor_word, level + 1))
# Step 5: Handle No Path Found
# If the queue becomes empty and endWord was never reached, return 0.
return 0
Code Explanation:
word_set = set(wordList): Converts the input list into a set. This allows forO(1)average time complexity for checking word existence, which is critical for efficiency.if endWord not in word_set: return 0: An early exit condition. If the target word isn't even in the dictionary, no transformation is possible.queue = deque([(beginWord, 1)]): Initializes adeque(fromcollections) as our BFS queue. Each element is a tuple(word, level), wherelevelis the current path length.beginWordis at level 1.visited = {beginWord}: A set to keep track of words we've already processed. This prevents infinite loops and ensures we find the shortest path.beginWordis immediately marked as visited.L = len(beginWord): Stores the length of the words, which is constant for all words in the problem.all_combo_dict = {}: This dictionary is the core of our optimization.- It's populated by iterating through every
wordinword_set. - For each
word, it generates allLpossible "generic words" by replacing each character with an asterisk*. - These generic words are keys, and their values are lists of actual words that match that pattern.
setdefaultis used to gracefully add words to lists.
- It's populated by iterating through every
while queue:: The main BFS loop. Continues as long as there are words to explore.current_word, level = queue.popleft(): Extracts the word and its current level from the front of the queue.if current_word == endWord: return level: If we've reachedendWord, this is the shortest path, so we return itslevel.for i in range(L): generic_word = current_word[:i] + "*" + current_word[i+1:]: This loop generates theLgeneric patterns for thecurrent_word.for neighbor_word in all_combo_dict.get(generic_word, []):: For each generic pattern, it looks up all potentialneighbor_words from our pre-processedall_combo_dict.get(key, [])safely returns an empty list if the key doesn't exist.if neighbor_word not in visited:: Checks if the neighbor has been processed. If not:visited.add(neighbor_word): Marks it as visited.queue.append((neighbor_word, level + 1)): Adds it to the queue for future processing, incrementing thelevelas it's one step further.
return 0: If the loop finishes andendWordwas never found, it means no path exists.
This implementation effectively solves the Leetcode 127 Word Ladder problem by leveraging BFS and a clever pre-processing step for efficient neighbor discovery.
Complexity Analysis
Understanding the time and space complexity of an algorithm is crucial for evaluating its efficiency and suitability for various constraints. For the Leetcode 127 Word Ladder problem, our BFS approach with the wildcard optimization significantly improves performance compared to a naive graph construction.
Let's define the variables:
N: The number of words in thewordList.L: The length of each word (all words have the same length).
Time Complexity
The overall time complexity can be broken down into two main phases: pre-processing and BFS traversal.
-
Pre-processing (
all_combo_dictconstruction):- We iterate through each of the
Nwords in theword_set. - For each word, we generate
Lgeneric patterns (e.g., "hot" -> "ot", "ht", "ho*"). Generating each generic pattern involves string slicing and concatenation, which takesO(L)time. - Storing these into the
all_combo_dict(which is a hash map) takesO(1)on average per insertion (assuming good hash function distribution). - Therefore, the total time for pre-processing is
N * L * O(L) = O(N * L^2).
- We iterate through each of the
-
BFS Traversal:
- In the worst-case scenario, every word in
word_set(includingbeginWordif it's implicitly added to the graph) might be visited once. So, we process up toNwords. - When we process a
current_wordfrom the queue:- We generate
Lgeneric patterns forcurrent_word, each takingO(L)time. SoO(L^2)to generate all patterns. - For each generic pattern, we perform a dictionary lookup in
all_combo_dict. On average, this isO(1). - The lookup returns a list of
neighbor_words. The total work for iterating through these neighbors across allLpatterns is bounded by the total number of words that can be linked tocurrent_wordvia one-letter differences.
- We generate
- More precisely, for each word
u(node) we visit:- We generate
Lgeneric patterns (O(L)time for each, totalingO(L^2)for all patterns generation if done naively for each word from scratch, orO(L)if string building is optimized). - For each pattern, we iterate over its neighbors. The sum of the lengths of all neighbor lists retrieved for a
current_wordis equivalent to the degree of that word in the implicit graph. - Since each word is processed once, and each edge in the graph is effectively traversed at most twice (once in each direction), the total cost for iterating through all neighbors across the entire BFS traversal is proportional to the total number of edges
Ein our graph.
- We generate
- The total number of edges
Ecan be at mostN * L. For each of theNwords, we checkLgeneric patterns. Eachall_combo_dictlookup takesO(1)average, and then we iterate through the list of words. The total number of times we append to the queue and updatevisitedis proportional toV + E. Here,V = N(number of words) andE(number of connections) can be at mostN * L(as each word hasLgeneric patterns, each potentially connecting to other words). - Therefore, the BFS traversal part is
O(N * L). - Combining pre-processing and BFS, the overall time complexity is
O(N * L^2 + N * L) = O(N * L^2).
- In the worst-case scenario, every word in
Space Complexity
The space complexity is dominated by the storage of the word_set, queue, visited set, and all_combo_dict.
word_set: StoresNwords, each of lengthL.O(N * L).queue: In the worst case, the queue can hold allNwords.O(N * L).visitedset: Also stores up toNwords.O(N * L).all_combo_dict:- Keys: There are
Nwords, and each word generatesLgeneric patterns. So, at mostN * Lunique generic patterns (keys). Each key is of lengthL. - Values: Each value is a list of words. The total number of words stored across all lists is
N * L(each wordwis added to the list ofLgeneric patterns it matches). - Thus, the
all_combo_dictcan consumeO(N * L^2)space. This is because there can beO(N * L)generic keys (each of length L), andO(N * L)entries in total across all value lists (each word is storedLtimes in total).
- Keys: There are
Overall, the space complexity is O(N * L^2), primarily due to the all_combo_dict.
In summary, this optimized BFS approach provides a robust solution with acceptable O(N * L^2) time and space complexity, making it efficient for typical Leetcode constraints.
Common Mistakes and How to Avoid Them
Solving Leetcode 127 Word Ladder can be tricky, and several common pitfalls can lead to incorrect or inefficient solutions. Being aware of these mistakes can help you debug your code more effectively and write a robust solution from the outset.
-
Forgetting to Handle
endWordNot inwordList:- Mistake: Proceeding with the BFS without checking if
endWordexists in thewordList. IfendWordis not available, no path can ever reach it. - How to Avoid: Always include an early check:
if endWord not in word_set: return 0. This is a quick win and handles a critical edge case.
- Mistake: Proceeding with the BFS without checking if
-
Not Using a
visitedSet:- Mistake: Failing to keep track of visited words. This will lead to infinite loops if cycles exist in the graph (e.g.,
hot -> dot -> hot) or redundant re-processing of words, negating the "shortest path" guarantee of BFS. - How to Avoid: Initialize a
visitedset and addbeginWordto it. Before adding anyneighbor_wordto the queue, always checkif neighbor_word not in visited:and thenvisited.add(neighbor_word).
- Mistake: Failing to keep track of visited words. This will lead to infinite loops if cycles exist in the graph (e.g.,
-
Incorrectly Calculating Path Length (Level):
- Mistake: Off-by-one errors in determining the length of the transformation sequence. Some might start
beginWordat level 0, others might count edges instead of nodes. - How to Avoid: Be consistent. The problem asks for the number of words in the shortest sequence. If
beginWordis level 1, then a pathbeginWord -> word2 -> endWordhas lengthlevel_of_endWord. Sticking to(beginWord, 1)and incrementinglevelfor each step is the clearest way. ThebeginWorditself counts as the first word.
- Mistake: Off-by-one errors in determining the length of the transformation sequence. Some might start
-
Inefficient Neighbor Generation (Brute-Force Word Comparison):
- Mistake: In the BFS loop, iterating through the entire
wordListfor everycurrent_wordto find words that differ by one character. This involvesO(N)comparisons, each takingO(L)time. - How to Avoid: Implement the "wildcard" or "generic state" pre-processing step using
all_combo_dict. This reduces neighbor discovery fromO(N * L)toO(L)(plus average lookup time).
- Mistake: In the BFS loop, iterating through the entire
-
Using DFS for Shortest Path:
- Mistake: Attempting to use Depth-First Search (DFS) directly to find the shortest path in an unweighted graph. DFS does not guarantee the shortest path without significant modifications (like explicit path tracking and comparing all paths), which makes it less suitable than BFS for this specific problem. If you wish to learn more about DFS, consult our detailed guide on Mastering Depth-First Search (DFS).
- How to Avoid: Remember that BFS is the canonical algorithm for finding the shortest path in unweighted graphs. Its level-by-level exploration naturally yields the shortest path upon first reaching the target.
-
Modifying
wordListDuring Iteration:- Mistake: Removing words from the
wordListorword_setwhile iterating over it (e.g., trying to usewordList.remove(word)as a way to mark visited). This can lead toRuntimeError: Set changed size during iterationor unexpected behavior. - How to Avoid: Use a separate
visitedset to manage visited states. Theword_setshould remain immutable during the BFS traversal itself (after initial pre-processing).
- Mistake: Removing words from the
By keeping these common mistakes in mind, you can significantly streamline your problem-solving process for Leetcode 127 Word Ladder and similar graph traversal challenges.
Conclusion
We've embarked on a comprehensive journey through Leetcode 127 Word Ladder, dissecting its problem statement, understanding the theoretical underpinnings, and implementing a robust, optimized solution. This problem serves as an excellent illustration of how to model real-world scenarios (or interview challenges) as graph problems and apply classic algorithms for efficient solutions.
The core takeaway is the power of Breadth-First Search (BFS) in finding the shortest path within an unweighted graph. By conceptualizing words as nodes and one-letter differences as edges, BFS systematically explores the graph layer by layer, guaranteeing the discovery of the shortest transformation sequence first. Furthermore, we delved into a crucial optimization: using a pre-processed all_combo_dict (wildcard dictionary) to drastically speed up neighbor discovery. This technique transforms a potentially O(N*L) operation for finding neighbors into a much faster average O(L) lookup, leading to an overall time complexity of O(N * L^2).
We walked through each step, from initializing essential data structures like the queue and visited set, to building the efficient all_combo_dict, and finally, executing the BFS traversal itself. The detailed Python code implementation provided a practical application of these concepts, ensuring you have a working solution ready for adaptation. We also highlighted common pitfalls, such as overlooking the endWord's presence in wordList or inefficiently generating neighbors, empowering you to avoid these issues in your own problem-solving endeavors.
Mastering problems like Leetcode 127 Word Ladder enhances not only your algorithmic skills but also your ability to analyze problem constraints, choose appropriate data structures, and optimize for performance. Keep practicing, and you'll find these patterns reappearing in various forms, ready for you to conquer with your newfound expertise.
Frequently Asked Questions
Q: Why is BFS preferred over DFS for the Word Ladder problem?
A: BFS is ideal because it guarantees finding the shortest path in unweighted graphs by exploring layer by layer. DFS, by contrast, explores depth-first and may find a longer path before eventually finding the shortest one, requiring more complex tracking.
Q: What is the purpose of the all_combo_dict (wildcard dictionary)?
A: The all_combo_dict is an optimization that pre-processes the wordList. It maps generic word patterns (e.g., h*t) to all actual words that match, allowing for O(L) average time complexity to find neighbors instead of a brute-force O(N*L) comparison.
Q: Does the beginWord need to be in the wordList?
A: No, beginWord does not need to be in the wordList. However, the endWord must be present in wordList for a valid transformation path to exist, otherwise, the function should return 0.