Approach
Breadth-first search
For Web Crawler Multithreaded, the implementation explores reachable states in layers, which is the standard shape for unweighted shortest paths and minimum-step transitions.
- Model each valid configuration as a state and each legal move as an edge.
- Seed the queue with the starting state and mark it immediately.
- Expand each state once, recording distance or reachability for unseen neighbours.
Code notes
- 138 lines of Python from the credited upstream file web-crawler-multithreaded.py.
- The implementation visibly relies on sequence storage, ordered lookup, work queue.
- No explicit loop blocks detected.
Complexity
Verify that each state and transition is processed only a bounded number of times; that determines the traversal cost.
Check the problem constraints before deciding whether this complexity will pass.
Use this to learn the idea, then write your own version.
123 4import threading5import Queue6 7 89101112class HtmlParser(object):13 def getUrls(self, url):14 """15 :type url: str16 :rtype List[str]17 """18 pass19 20 21class Solution(object):22 NUMBER_OF_WORKERS = 823 24 def __init__(self):25 self.__cv = threading.Condition()26 self.__q = Queue.Queue()27 28 def crawl(self, startUrl, htmlParser):29 """30 :type startUrl: str31 :type htmlParser: HtmlParser32 :rtype: List[str]33 """34 SCHEME = "http://"35 def hostname(url):36 pos = url.find('/', len(SCHEME))37 if pos == -1:38 return url39 return url[:pos]40 41 def worker(htmlParser, lookup):42 while True:43 from_url = self.__q.get()44 if from_url is None:45 break46 name = hostname(from_url)47 for to_url in htmlParser.getUrls(from_url):48 if name != hostname(to_url):49 continue50 with self.__cv:51 if to_url not in lookup:52 lookup.add(to_url)53 self.__q.put(to_url)54 self.__q.task_done()55 56 workers = []57 self.__q = Queue.Queue()58 self.__q.put(startUrl)59 lookup = set([startUrl])60 for i in xrange(self.NUMBER_OF_WORKERS):61 t = threading.Thread(target=worker, args=(htmlParser, lookup))62 t.start()63 workers.append(t)64 self.__q.join()65 for t in workers:66 self.__q.put(None)67 for t in workers:68 t.join()69 return list(lookup)70 71 727374import threading75import collections76 77 78class Solution2(object):79 NUMBER_OF_WORKERS = 880 81 def __init__(self):82 self.__cv = threading.Condition()83 self.__q = collections.deque()84 self.__working_count = 085 86 def crawl(self, startUrl, htmlParser):87 """88 :type startUrl: str89 :type htmlParser: HtmlParser90 :rtype: List[str]91 """92 SCHEME = "http://"93 def hostname(url):94 pos = url.find('/', len(SCHEME))95 if pos == -1:96 return url97 return url[:pos]98 99 def worker(htmlParser, lookup):100 while True:101 with self.__cv:102 while not self.__q:103 self.__cv.wait()104 from_url = self.__q.popleft()105 if from_url is None:106 break107 self.__working_count += 1108 name = hostname(from_url)109 for to_url in htmlParser.getUrls(from_url):110 if name != hostname(to_url):111 continue112 with self.__cv:113 if to_url not in lookup:114 lookup.add(to_url)115 self.__q.append(to_url)116 self.__cv.notifyAll()117 with self.__cv:118 self.__working_count -= 1119 if not self.__q and not self.__working_count:120 self.__cv.notifyAll()121 122 workers = []123 self.__q = collections.deque([startUrl])124 lookup = set([startUrl])125 for i in xrange(self.NUMBER_OF_WORKERS):126 t = threading.Thread(target=worker, args=(htmlParser, lookup))127 t.start()128 workers.append(t)129 with self.__cv:130 while self.__q or self.__working_count:131 self.__cv.wait()132 for i in xrange(self.NUMBER_OF_WORKERS):133 self.__q.append(None)134 self.__cv.notifyAll()135 for t in workers:136 t.join()137 return list(lookup)138