<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Bidirectional BFS Archives - Huahua&#039;s Tech Road</title>
	<atom:link href="https://zxi.mytechroad.com/blog/tag/bidirectional-bfs/feed/" rel="self" type="application/rss+xml" />
	<link>https://zxi.mytechroad.com/blog/tag/bidirectional-bfs/</link>
	<description></description>
	<lastBuildDate>Thu, 02 May 2019 04:47:07 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.0.8</generator>

<image>
	<url>https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/cropped-photo-32x32.jpg</url>
	<title>Bidirectional BFS Archives - Huahua&#039;s Tech Road</title>
	<link>https://zxi.mytechroad.com/blog/tag/bidirectional-bfs/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>花花酱 8 Puzzles &#8211; Bidirectional A* vs Bidirectional BFS</title>
		<link>https://zxi.mytechroad.com/blog/searching/8-puzzles-bidirectional-astar-vs-bidirectional-bfs/</link>
					<comments>https://zxi.mytechroad.com/blog/searching/8-puzzles-bidirectional-astar-vs-bidirectional-bfs/#respond</comments>
		
		<dc:creator><![CDATA[zxi]]></dc:creator>
		<pubDate>Tue, 30 Apr 2019 08:57:23 +0000</pubDate>
				<category><![CDATA[Search]]></category>
		<category><![CDATA[A*]]></category>
		<category><![CDATA[AStar]]></category>
		<category><![CDATA[BFS]]></category>
		<category><![CDATA[Bidirectional BFS]]></category>
		<category><![CDATA[search]]></category>
		<guid isPermaLink="false">https://zxi.mytechroad.com/blog/?p=5129</guid>

					<description><![CDATA[<p>8 Puzzles # nodes expended of 1000 solvable instances Conclusion: Nodes expended: BiDirectional A* &#60;&#60; A* (Manhattan) &#60;= Bidirectional BFS &#60; A* Hamming &#60;&#60; BFSRunning&#8230;</p>
<p>The post <a rel="nofollow" href="https://zxi.mytechroad.com/blog/searching/8-puzzles-bidirectional-astar-vs-bidirectional-bfs/">花花酱 8 Puzzles &#8211; Bidirectional A* vs Bidirectional BFS</a> appeared first on <a rel="nofollow" href="https://zxi.mytechroad.com/blog">Huahua&#039;s Tech Road</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image"><img width="861" height="591" src="https://zxi.mytechroad.com/blog/wp-content/uploads/2019/04/nodes.png" alt="" class="wp-image-5133" srcset="https://zxi.mytechroad.com/blog/wp-content/uploads/2019/04/nodes.png 861w, https://zxi.mytechroad.com/blog/wp-content/uploads/2019/04/nodes-300x206.png 300w, https://zxi.mytechroad.com/blog/wp-content/uploads/2019/04/nodes-768x527.png 768w" sizes="(max-width: 861px) 100vw, 861px" /></figure>



<p style="text-align:center">8 Puzzles # nodes expended of 1000 solvable instances</p>



<figure class="wp-block-image"><img width="861" height="591" src="https://zxi.mytechroad.com/blog/wp-content/uploads/2019/05/time.png" alt="" class="wp-image-5134" srcset="https://zxi.mytechroad.com/blog/wp-content/uploads/2019/05/time.png 861w, https://zxi.mytechroad.com/blog/wp-content/uploads/2019/05/time-300x206.png 300w, https://zxi.mytechroad.com/blog/wp-content/uploads/2019/05/time-768x527.png 768w" sizes="(max-width: 861px) 100vw, 861px" /></figure>



<p>Conclusion: </p>



<p>Nodes expended: BiDirectional A* &lt;&lt; A* (Manhattan) &lt;= Bidirectional BFS &lt; A* Hamming &lt;&lt; BFS<br>Running time:  BiDirectional A* &lt;  Bidirectional BFS &lt;=  A* (Manhattan)  &lt; A* Hamming &lt;&lt; BFS </p>



<p>Code:</p>



<pre class="crayon-plain-tag"># Author: Huahua

from collections import deque
import heapq
import numpy as np
import random
import sys

N = 3

def Manhattan(s1, s2):
  dist = 0
  for i1, d in enumerate(s1):
    i2 = s2.index(d)
    x1 = i1 % N
    y1 = i1 // N
    x2 = i2 % N
    y2 = i2 // N
    dist += abs(x1 - x2) + abs(y1 - y2)
  return dist

def Hamming(s1, s2):
  return sum(x != y for x, y in zip(s1, s2))

class Node:
  dirs = [0, -1, 0, 1, 0]
  def __init__(self, state, parent = None, h = 0):
    self.state = state
    self.parent = parent
    self.g = parent.g + 1 if parent else 0
    self.h = h
    self.f = self.g + self.h

  def getMoves(self):
    moves = []
    index = self.state.index(0)
    x = index % N
    y = index // N
    for i in range(4):
      tx = x + Node.dirs[i]
      ty = y + Node.dirs[i + 1]
      if tx &lt; 0 or ty &lt; 0 or tx == N or ty == N:
        continue
      i = ty * N + tx
      move = list(self.state)
      move[index] = move[i]
      move[i] = 0
      moves.append(tuple(move))
    return moves

  def print(self):
    print(np.reshape(self.state, (N, N)))

  def __lt__(self, other):
    return self.f &lt; other.f

def AStarSearch(start_state, end_state, heuristic):
  def h(s):
    return heuristic(s, end_state)
  q = []
  s = Node(start_state, h=h(start_state))
  heapq.heappush(q, s)
  opened = {s.state : s.f}
  closed = dict()
  while q:
    n = heapq.heappop(q)
    if n.state == end_state:
      return n, len(opened), len(closed)
    if n.state in closed: continue
    closed[n.state] = n.f
    for move in n.getMoves():
      node = Node(move, n, h(move))
      if move in opened and opened[move] &lt;= node.f: continue
      opened[node.state] = node.f
      heapq.heappush(q, node)
  return None, len(opened), len(closed)

def BFS(start_state, end_state):
  q = deque()
  q.append(Node(start_state))
  opened = set(start_state)
  closed = 0
  while q:
    n = q.popleft()
    if n.state == end_state:
      return n, len(opened), closed
    closed += 1
    for move in n.getMoves():
      if move in opened: continue
      opened.add(move)
      q.append(Node(move, n))
  return None, len(opened), closed

def getRootNode(n):
  return getRootNode(n.parent) if n.parent else n

def BidirectionalBFS(start_state, end_state):
  def constructPath(p, o):
    while o: 
      t = o.parent
      o.parent = p
      p, o = o, t
    return p
  ns = Node(start_state)
  ne = Node(end_state)
  q = [deque([ns]), deque([ne])]
  opened = [{start_state : ns}, {end_state: ne}]
  closed = [0, 0]
  while q[0]:
    l = len(q[0])
    while l &gt; 0:
      p = q[0].popleft()
      closed[0] += 1
      for move in p.getMoves():
        n = Node(move, p)
        if move in opened[1]:
          o = opened[1][move]
          if getRootNode(n).state == end_state:
            o, n = n, o
          n = constructPath(n, o.parent)
          return n, len(opened[0]) + len(opened[1]), closed[0] + closed[1]
        if move in opened[0]: continue
        opened[0][move] = n
        q[0].append(n)
      l -= 1
    q.reverse()
    opened.reverse()
    closed.reverse()
  return None, len(opened[0]) + len(opened[1]), closed[0] + closed[1]


def print_path(n):
  if not n: return
  print_path(n.parent)
  n.print()

def solvable(state):
  inv = 0
  for i in range(N*N):
    for j in range(i + 1, N*N):
      if all((state[i] &gt; 0, state[j] &gt; 0, state[i] &gt; state[j])):
        inv += 1
  return inv % 2 == 0

if __name__ == '__main__':
  s = [1, 2, 3, 4, 5, 6, 7, 8, 0]
  e = [1, 2, 3, 4, 5, 6, 7, 8, 0]
  i = 0
  while True:
    random.shuffle(s)
    if not solvable(s): continue
    n1, opened1, closed1 = AStarSearch(tuple(s), tuple(e), heuristic=Manhattan)
    n2, opened2, closed2 = AStarSearch(tuple(s), tuple(e), heuristic=Hamming)
    n3, opened3, closed3 = BFS(tuple(s), tuple(e))
    n4, opened4, closed4 = BidirectionalBFS(tuple(s), tuple(e))
    print(&quot;%d\t%d\t%d\t%d&quot; % (closed1, closed2, closed3, closed4))
    sys.stdout.flush()

    # print(&quot;A*&quot;)
    # print_path(n1)
    # print('---------------')
    # print(&quot;BFS&quot;)
    # print_path(n3)
    # print('---------------')
    # print(&quot;Bi-BFS&quot;)
    # print_path(n4)
    # print('---------------')

    i += 1
    if i == 1000: break</pre>



<p>C++ Version</p>



<pre class="crayon-plain-tag">#include &lt;algorithm&gt;
#include &lt;array&gt;
#include &lt;chrono&gt;
#include &lt;deque&gt;
#include &lt;functional&gt;
#include &lt;iostream&gt;
#include &lt;memory&gt;
#include &lt;queue&gt;
#include &lt;unordered_map&gt;
#include &lt;unordered_set&gt;
#include &lt;vector&gt;

using namespace std;
using chrono::high_resolution_clock;

constexpr int N = 3;
constexpr int dirs[] = {0, 1, 0, -1, 0};

struct Node;
typedef string State;
typedef shared_ptr&lt;Node&gt; NodePtr;
typedef function&lt;int(const State&amp; s, const State&amp; t)&gt; Heuristic;

struct Node {
  Node(State s, NodePtr p = nullptr, int h = 0)
      : s(s), p(p), h(h), g(p ? p-&gt;g + 1 : 0), f(this-&gt;h + g) {}

  vector&lt;State&gt; GetNextStates() const {
    vector&lt;State&gt; states;
    int index = find(s.begin(), s.end(), '0') - s.begin();
    int x = index % N;
    int y = index / N;
    for (int i = 0; i &lt; 4; ++i) {
      int tx = x + dirs[i];
      int ty = y + dirs[i + 1];
      if (tx &lt; 0 || ty &lt; 0 || tx == N || ty == N) continue;
      int new_index = ty * N + tx;
      State next(s);
      next[index] = s[new_index];
      next[new_index] = '0';
      states.push_back(move(next));
    }
    return states;
  }

  NodePtr p;
  int h;
  int g;
  int f;
  State s;
};

bool Sovlable(const State&amp; s) {
  int inv_count = 0;
  for (int i = 0; i &lt; N * N; ++i) {
    for (int j = i + 1; j &lt; N * N; ++j) {
      if (s[i] != '0' &amp;&amp; s[j] != '0' &amp;&amp; s[i] &gt; s[j]) {
        ++inv_count;
      }
    }
  }
  return inv_count % 2 == 0;
}

NodePtr GetRoot(NodePtr n) { return n-&gt;p ? GetRoot(n-&gt;p) : n; }
NodePtr WirePath(NodePtr p, NodePtr n) {
  while (n) {
    NodePtr t = n-&gt;p;
    n-&gt;p = p;
    p = n;
    n = t;
  }
  return p;
}

void ConstructPath(NodePtr node, vector&lt;State&gt;* path) {
  while (node) {
    path-&gt;push_back(node-&gt;s);
    node = node-&gt;p;
  }
  reverse(path-&gt;begin(), path-&gt;end());
}

class Solver {
 public:
  virtual bool Solve(State start, State target, vector&lt;State&gt;* path,
                     int* opened, int* closed) = 0;
};

class BFSSolver : public Solver {
 public:
  bool Solve(State start, State target, vector&lt;State&gt;* path, int* opened,
             int* closed) override {
    int expended = 0;
    unordered_set&lt;string&gt; seen;
    deque&lt;NodePtr&gt; q{make_shared&lt;Node&gt;(start)};
    while (!q.empty()) {
      auto cur = q.front();
      q.pop_front();
      ++expended;
      if (cur-&gt;s == target) {
        ConstructPath(cur, path);
        *opened = seen.size();
        *closed = expended;
        return true;
      }
      for (const auto&amp; s : cur-&gt;GetNextStates()) {
        auto r = seen.insert(s);
        if (!r.second) continue;
        q.push_back(make_shared&lt;Node&gt;(s, cur));
      }
    }
    return false;
  }
};

class BidirectionalBFSSolver : public Solver {
 public:
  bool Solve(State start, State target, vector&lt;State&gt;* path, int* opened,
             int* closed) override {
    auto start_node = make_shared&lt;Node&gt;(start);
    auto target_node = make_shared&lt;Node&gt;(target);
    unordered_map&lt;string, NodePtr&gt; seen0{{start, start_node}};
    unordered_map&lt;string, NodePtr&gt; seen1{{target, target_node}};
    deque&lt;NodePtr&gt; q0{start_node};
    deque&lt;NodePtr&gt; q1{target_node};
    int expended = 0;
    while (!q0.empty()) {
      size_t size = q0.size();
      while (size--) {
        auto cur = q0.front();
        q0.pop_front();
        ++expended;

        for (const auto&amp; s : cur-&gt;GetNextStates()) {
          auto n = make_shared&lt;Node&gt;(s, cur);
          auto it = seen1.find(s);
          if (it != seen1.end()) {
            auto p = it-&gt;second;
            if (GetRoot(n)-&gt;s == target) swap(n, p);
            n = WirePath(n, p-&gt;p);
            ConstructPath(n, path);
            *opened = seen0.size() + seen1.size();
            *closed = expended;
            return true;
          }

          if (seen0.count(s)) continue;
          seen0[s] = n;
          q0.push_back(n);
        }
      }

      if (q1.size() &lt; q0.size()) {
        swap(q0, q1);
        swap(seen0, seen1);
      }
    }
    return false;
  }

 private:
};

struct NodeCompare : public binary_function&lt;NodePtr, NodePtr, bool&gt; {
  bool operator()(const NodePtr&amp; x, const NodePtr&amp; y) const {
    return x-&gt;f &gt; y-&gt;f;
  }
};

int ManhattanDistance(const State&amp; s, const State&amp; t) {
  int h = 0;
  for (int i1 = 0; i1 &lt; N * N; ++i1) {
    int i2 = t.find(s[i1]);
    int x1 = i1 % N;
    int y1 = i1 / N;
    int x2 = i2 % N;
    int y2 = i2 / N;
    h += abs(x1 - x2) + abs(y1 - y2);
  }
  return h;
}

int HammingDistance(const State&amp; s, const State&amp; t) {
  int h = 0;
  for (size_t i = 0; i &lt; s.size(); ++i) {
    if (s[i] != t[i]) ++h;
  }
  return h;
}

class AStarSolver : public Solver {
 public:
  explicit AStarSolver(Heuristic heuristic) : heuristic_(heuristic) {}
  bool Solve(State start, State target, vector&lt;State&gt;* path, int* opened,
             int* closed) override {
    unordered_map&lt;string, NodePtr&gt; o;
    unordered_set&lt;string&gt; c;
    priority_queue&lt;NodePtr, vector&lt;NodePtr&gt;, NodeCompare&gt; q;
    q.emplace(new Node(start, nullptr, heuristic_(start, target)));
    while (!q.empty()) {
      auto cur = q.top();
      q.pop();
      if (cur-&gt;s == target) {
        ConstructPath(cur, path);
        *opened = o.size();
        *closed = c.size();
        return true;
      }
      if (!c.insert(cur-&gt;s).second) continue;
      for (const auto&amp; s : cur-&gt;GetNextStates()) {
        auto it = o.find(s);
        auto n = make_shared&lt;Node&gt;(s, cur, heuristic_(s, target));
        if (it != o.end() &amp;&amp; n-&gt;f &gt;= it-&gt;second-&gt;f) continue;
        o[s] = n;
        q.push(n);
      }
    }
    return false;
  }

 private:
  Heuristic heuristic_;
};

class BiAStarSolver : public Solver {
 public:
  explicit BiAStarSolver(Heuristic heuristic) : heuristic_(heuristic) {}
  bool Solve(State start, State target, vector&lt;State&gt;* path, int* opened,
             int* closed) override {
    unordered_map&lt;string, NodePtr&gt; o0, o1;
    unordered_map&lt;string, NodePtr&gt; c0, c1;
    priority_queue&lt;NodePtr, vector&lt;NodePtr&gt;, NodeCompare&gt; q0, q1;
    q0.emplace(new Node(start, nullptr, heuristic_(start, target)));
    q1.emplace(new Node(target, nullptr, heuristic_(target, start)));
    bool farward = true;
    while (!q0.empty()) {
      auto size = q0.size();
      while (size--) {
        auto cur = q0.top();
        q0.pop();
        if (c0.count(cur-&gt;s)) continue;
        c0[cur-&gt;s] = cur;
        if (c1.count(cur-&gt;s)) {
          auto p = c1[cur-&gt;s];
          if (GetRoot(cur)-&gt;s == target) swap(cur, p);
          cur = WirePath(cur, p-&gt;p);
          ConstructPath(cur, path);
          *opened = o0.size() + o1.size();
          *closed = c0.size() + c1.size();
          return true;
        }
        for (const auto&amp; s : cur-&gt;GetNextStates()) {
          auto it = o0.find(s);
          auto n = make_shared&lt;Node&gt;(s, cur,
                                     heuristic_(s, farward ? target : start));
          if (it != o0.end() &amp;&amp; n-&gt;f &gt;= it-&gt;second-&gt;f) continue;
          o0[s] = n;
          q0.push(n);
        }
      }
      if (q1.size() &lt; q0.size()) {
        swap(q0, q1);
        swap(c0, c1);
        swap(o0, o1);
        farward = !farward;
      }
    }
    return false;
  }

 private:
  Heuristic heuristic_;
};

bool VerifyPath(const vector&lt;State&gt;&amp; path, const State&amp; s, const State&amp; t) {
  if (path.empty()) return false;
  if (path.front() != s || path.back() != t) return false;
  for (size_t i = 1; i &lt; path.size(); ++i) {
    Node n(path[i - 1]);
    auto states = n.GetNextStates();
    if (find(begin(states), end(states), path[i]) == end(states)) {
      return false;
    }
  }
  return true;
}

void StatisticsMode() {
  State s{&quot;123456780&quot;};
  State t{&quot;123456780&quot;};

  vector&lt;unique_ptr&lt;Solver&gt;&gt; solvers;
  solvers.emplace_back(new BFSSolver);
  solvers.emplace_back(new AStarSolver(HammingDistance));
  solvers.emplace_back(new AStarSolver(ManhattanDistance));
  solvers.emplace_back(new BidirectionalBFSSolver);
  solvers.emplace_back(new BiAStarSolver(ManhattanDistance));

  for (int i = 0; i &lt; 1000;) {
    random_shuffle(s.begin(), s.end());
    if (!Sovlable(s)) continue;
    ++i;
    for (auto&amp; solver : solvers) {
      vector&lt;State&gt; path;
      int opened;
      int closed;
      auto t0 = high_resolution_clock::now();
      bool sovlable = solver-&gt;Solve(s, t, &amp;path, &amp;opened, &amp;closed);
      auto t1 = high_resolution_clock::now();

      if (!VerifyPath(path, s, t)) {
        cerr &lt;&lt; &quot;Invalid path!&quot; &lt;&lt; endl;
        return;
      }

      auto time_span = chrono::duration_cast&lt;chrono::duration&lt;double&gt;&gt;(t1 - t0);

      cout &lt;&lt; closed &lt;&lt; &quot;\t&quot; &lt;&lt; time_span.count() * 1000 &lt;&lt; &quot;\t&quot;;
    }
    cout &lt;&lt; endl;
  }
}

int main() { StatisticsMode(); }</pre>
<p>The post <a rel="nofollow" href="https://zxi.mytechroad.com/blog/searching/8-puzzles-bidirectional-astar-vs-bidirectional-bfs/">花花酱 8 Puzzles &#8211; Bidirectional A* vs Bidirectional BFS</a> appeared first on <a rel="nofollow" href="https://zxi.mytechroad.com/blog">Huahua&#039;s Tech Road</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://zxi.mytechroad.com/blog/searching/8-puzzles-bidirectional-astar-vs-bidirectional-bfs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>花花酱 LeetCode 752. Open the Lock</title>
		<link>https://zxi.mytechroad.com/blog/searching/leetcode-752-open-the-lock/</link>
					<comments>https://zxi.mytechroad.com/blog/searching/leetcode-752-open-the-lock/#respond</comments>
		
		<dc:creator><![CDATA[zxi]]></dc:creator>
		<pubDate>Mon, 25 Dec 2017 17:13:30 +0000</pubDate>
				<category><![CDATA[Hard]]></category>
		<category><![CDATA[Search]]></category>
		<category><![CDATA[BFS]]></category>
		<category><![CDATA[Bidirectional BFS]]></category>
		<category><![CDATA[shortest path]]></category>
		<guid isPermaLink="false">http://zxi.mytechroad.com/blog/?p=1324</guid>

					<description><![CDATA[<p>Problem: You have a lock in front of you with 4 circular wheels. Each wheel has 10 slots: '0', '1', '2', '3', '4', '5', '6', '7',&#8230;</p>
<p>The post <a rel="nofollow" href="https://zxi.mytechroad.com/blog/searching/leetcode-752-open-the-lock/">花花酱 LeetCode 752. Open the Lock</a> appeared first on <a rel="nofollow" href="https://zxi.mytechroad.com/blog">Huahua&#039;s Tech Road</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><iframe width="500" height="375" src="https://www.youtube.com/embed/M7GgV6TJTdc?feature=oembed" frameborder="0" gesture="media" allow="encrypted-media" allowfullscreen></iframe></p>
<p><strong>Problem:</strong></p>
<div class="question-description">
<p>You have a lock in front of you with 4 circular wheels. Each wheel has 10 slots: <code>'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'</code>. The wheels can rotate freely and wrap around: for example we can turn <code>'9'</code> to be <code>'0'</code>, or <code>'0'</code> to be <code>'9'</code>. Each move consists of turning one wheel one slot.</p>
<p>The lock initially starts at <code>'0000'</code>, a string representing the state of the 4 wheels.</p>
<p>You are given a list of <code>deadends</code> dead ends, meaning if the lock displays any of these codes, the wheels of the lock will stop turning and you will be unable to open it.</p>
<p>Given a <code>target</code> representing the value of the wheels that will unlock the lock, return the minimum total number of turns required to open the lock, or -1 if it is impossible.</p>
<p><b>Example 1:</b></p><pre class="crayon-plain-tag">Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output: 6
Explanation:
A sequence of valid moves would be "0000" -&gt; "1000" -&gt; "1100" -&gt; "1200" -&gt; "1201" -&gt; "1202" -&gt; "0202".
Note that a sequence like "0000" -&gt; "0001" -&gt; "0002" -&gt; "0102" -&gt; "0202" would be invalid,
because the wheels of the lock become stuck after the display becomes the dead end "0102".</pre><p><b>Example 2:</b></p><pre class="crayon-plain-tag">Input: deadends = ["8888"], target = "0009"
Output: 1
Explanation:
We can turn the last wheel in reverse to move from "0000" -&gt; "0009".</pre><p><b>Example 3:</b></p><pre class="crayon-plain-tag">Input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output: -1
Explanation:
We can't reach the target without getting stuck.</pre><p><b>Example 4:</b></p><pre class="crayon-plain-tag">Input: deadends = [&quot;0000&quot;], target = &quot;8888&quot;
Output: -1</pre><p><b>Note:</b></p>
<ol>
<li>The length of <code>deadends</code> will be in the range <code>[1, 500]</code>.</li>
<li><code>target</code> will not be in the list <code>deadends</code>.</li>
<li>Every string in <code>deadends</code> and the string <code>target</code> will be a string of 4 digits from the 10,000 possibilities <code>'0000'</code> to <code>'9999'</code>.</li>
</ol>
<p><ins class="adsbygoogle" style="display: block; text-align: center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-2404451723245401" data-ad-slot="7983117522"></ins><br />
<script>
     (adsbygoogle = window.adsbygoogle || []).push({});
</script></p>
<p>题目大意：</p>
<p>给你一个4位密码锁，0可以转到1和9，1可以转到0和2，。。。，9可以转到0和8。</p>
<p>另外给你一些死锁的密码，比如1234，一但转到任何一个死锁的密码，锁就无法再转动了。</p>
<p>给你一个目标密码，问你最少要转多少次才能从0000转到目标密码。</p>
<p><strong>Solution:</strong></p>
<p>C++</p><pre class="crayon-plain-tag">// Author: Huahua
// Runtime: 133 ms
class Solution {
public:
  int openLock(vector&lt;string&gt;&amp; deadends, string target) {
    const string start = "0000";
    unordered_set&lt;string&gt; dead(deadends.begin(), deadends.end());
    if (dead.count(start)) return -1;
    if (start == target) return 0;
    
    queue&lt;string&gt; q;
    unordered_set&lt;string&gt; visited{start};
    
    int steps = 0;
    q.push(start);
    while (!q.empty()) {
      ++steps;
      const int size = q.size();
      for (int i = 0; i &lt; size; ++i) {
        const string curr = q.front(); 
        q.pop();
        for (int i = 0; i &lt; 4; ++i)
          for (int j = -1; j &lt;= 1; j += 2) {
            string next = curr;
            next[i] = (next[i] - '0' + j + 10) % 10 + '0';
            if (next == target) return steps;
            if (dead.count(next) || visited.count(next)) continue; 
            q.push(next);
            visited.insert(next);
          }
      }
    }
    
    return -1;
  }
};</pre><p>C++ / Bidirectional BFS</p><pre class="crayon-plain-tag">// Author: Huahua
// Runtime: 32 ms
class Solution {
public:
  int openLock(vector&lt;string&gt;&amp; deadends, string target) {
    const string start = "0000";
    unordered_set&lt;string&gt; dead(deadends.begin(), deadends.end());
    if (dead.count(start)) return -1;
    if (target == start) return 0;
    
    queue&lt;string&gt; q1;
    queue&lt;string&gt; q2;
    unordered_set&lt;string&gt; v1{start};
    unordered_set&lt;string&gt; v2{target};
    
    int s1 = 0;
    int s2 = 0;
    q1.push(start);
    q2.push(target);
    while (!q1.empty() &amp;&amp; !q2.empty()) {      
      if (!q1.empty()) ++s1;
      const int size = q1.size();
      for (int i = 0; i &lt; size; ++i) {
        const string curr = q1.front(); 
        q1.pop();
        for (int i = 0; i &lt; 4; ++i)
          for (int j = -1; j &lt;= 1; j += 2) {
            string next = curr;
            next[i] = (next[i] - '0' + j + 10) % 10 + '0';
            if (v2.count(next)) return s1 + s2;
            if (dead.count(next) || v1.count(next)) continue; 
            q1.push(next);
            v1.insert(next);
          }
      }
      swap(q1, q2);
      swap(v1, v2);
      swap(s1, s2);
    }
    
    return -1;
  }    
};</pre><p>C++ / Bidirectional BFS / int state / Array</p><pre class="crayon-plain-tag">// Author: Huahua
// Runtime: 9 ms
class Solution {
public:
  int openLock(vector&lt;string&gt;&amp; deadends, string target) {
    constexpr int kMaxN = 10000;
    const vector&lt;int&gt; bases{1, 10, 100, 1000};
    const int start = 0;
    const int goal = stoi(target);
    
    queue&lt;int&gt; q1;
    queue&lt;int&gt; q2;
    vector&lt;int&gt; v1(kMaxN, 0);
    vector&lt;int&gt; v2(kMaxN, 0);  
    for (const string&amp; deadend : deadends)
        v1[stoi(deadend)] = v2[stoi(deadend)] = -1;
    
    if (v1[start] == -1) return -1;
    if (start == goal) return 0;
    
    v1[start] = 1;
    v2[goal] = 1;
    
    int s1 = 0;
    int s2 = 0;
    q1.push(start);
    q2.push(goal);
    while (!q1.empty() &amp;&amp; !q2.empty()) {      
      if (!q1.empty()) ++s1;
      const int size = q1.size();
      for (int i = 0; i &lt; size; ++i) {
        const int curr = q1.front(); 
        q1.pop();
        for (int i = 0; i &lt; 4; ++i) {
          int r = (curr / bases[i]) % 10;
          for (int j = -1; j &lt;= 1; j += 2) {
            const int next = curr + ((r + j + 10) % 10 - r) * bases[i];
            if (v2[next] == 1) return s1 + s2;
            if (v1[next]) continue;            
            q1.push(next);
            v1[next] = 1;
          }
        }
      }
      swap(q1, q2);
      swap(v1, v2);
      swap(s1, s2);
    }    
    return -1;
  }
};</pre><p>Java</p><pre class="crayon-plain-tag">// Author: Huahua
// Runtime: 132 ms
class Solution {
  public int openLock(String[] deadends, String target) {
    String start = "0000";
    Set&lt;String&gt; dead = new HashSet();
    for (String d: deadends) dead.add(d);
    if (dead.contains(start)) return -1;

    Queue&lt;String&gt; queue = new LinkedList();
    queue.offer(start);        

    Set&lt;String&gt; visited = new HashSet();
    visited.add(start);

    int steps = 0;
    while (!queue.isEmpty()) {
      ++steps;
      int size = queue.size();
      for (int s = 0; s &lt; size; ++s) {
        String node = queue.poll();
        for (int i = 0; i &lt; 4; ++i) {
          for (int j = -1; j &lt;= 1; j += 2) {
            char[] chars = node.toCharArray();
            chars[i] = (char)(((chars[i] - '0') + j + 10) % 10 + '0');
            String next = new String(chars);
            if (next.equals(target)) return steps;
            if (dead.contains(next) || visited.contains(next))
                continue;
            visited.add(next);
            queue.offer(next);
          }
        }
      }
    }
    return -1;
  }
}</pre><p>Python</p><pre class="crayon-plain-tag">"""
Author: Huahua
Runtime: 955 ms
"""
class Solution:
    def openLock(self, deadends, target):
        from collections import deque
        deads = set(deadends)
        start = '0000'
        if start in deads: return -1
        q = deque([(start, 0)])        
        visited = set(start)        
        while q:                
            node, step = q.popleft()
            for i in range(0, 4):
                for j in [-1, 1]:
                    nxt = node[:i] + str((int(node[i]) + j + 10) % 10) + node[i+1:]
                    if nxt == target: return step + 1
                    if nxt in deads or nxt in visited: continue
                    q.append((nxt, step + 1))
                    visited.add(nxt)
        return -1</pre><p>Python / Int state</p><pre class="crayon-plain-tag">"""
Author: Huahua
Runtime: 568 ms
"""
class Solution:
    def openLock(self, deadends, target):
        from collections import deque
        bases = [1, 10, 100, 1000]
        deads = set(int(x) for x in deadends)
        start, goal = int('0000'), int(target)        
        if start in deads: return -1
        if start == goal: return 0
        q = deque([(start, 0)])
        visited = set([start])
        while q:   
            node, step = q.popleft()
            for i in range(0, 4):
                r = (node // bases[i]) % 10
                for j in [-1, 1]:
                    nxt = node + ((r + j + 10) % 10 - r) * bases[i]
                    if nxt == goal: return step + 1
                    if nxt in deads or nxt in visited: continue
                    q.append((nxt, step + 1))
                    visited.add(nxt)
        return -1</pre><p>&nbsp;</p>
</div>
<p>The post <a rel="nofollow" href="https://zxi.mytechroad.com/blog/searching/leetcode-752-open-the-lock/">花花酱 LeetCode 752. Open the Lock</a> appeared first on <a rel="nofollow" href="https://zxi.mytechroad.com/blog">Huahua&#039;s Tech Road</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://zxi.mytechroad.com/blog/searching/leetcode-752-open-the-lock/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>花花酱 LeetCode 126. Word Ladder II</title>
		<link>https://zxi.mytechroad.com/blog/searching/leetcode-126-word-ladder-ii/</link>
					<comments>https://zxi.mytechroad.com/blog/searching/leetcode-126-word-ladder-ii/#respond</comments>
		
		<dc:creator><![CDATA[zxi]]></dc:creator>
		<pubDate>Wed, 27 Sep 2017 04:27:00 +0000</pubDate>
				<category><![CDATA[Search]]></category>
		<category><![CDATA[BFS]]></category>
		<category><![CDATA[Bidirectional BFS]]></category>
		<category><![CDATA[DFS]]></category>
		<category><![CDATA[graph]]></category>
		<category><![CDATA[hard]]></category>
		<guid isPermaLink="false">http://zxi.mytechroad.com/blog/?p=427</guid>

					<description><![CDATA[<p>Problem: Given two words (beginWord and endWord), and a dictionary&#8217;s word list, find all shortest transformation sequence(s) from beginWord to endWord, such that: Only one letter can be changed at&#8230;</p>
<p>The post <a rel="nofollow" href="https://zxi.mytechroad.com/blog/searching/leetcode-126-word-ladder-ii/">花花酱 LeetCode 126. Word Ladder II</a> appeared first on <a rel="nofollow" href="https://zxi.mytechroad.com/blog">Huahua&#039;s Tech Road</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><iframe width="500" height="375" src="https://www.youtube.com/embed/PblfQrdWXQ4?feature=oembed" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe></p>
<p><strong>Problem:</strong></p>
<p>Given two words (<i>beginWord</i> and <i>endWord</i>), and a dictionary&#8217;s word list, find all shortest transformation sequence(s) from <i>beginWord</i> to <i>endWord</i>, such that:</p>
<ol>
<li>Only one letter can be changed at a time</li>
<li>Each transformed word must exist in the word list. Note that <i>beginWord</i> is <i>not</i> a transformed word.</li>
</ol>
<p>For example,</p>
<p>Given:<br />
<i>beginWord</i> = <code>"hit"</code><br />
<i>endWord</i> = <code>"cog"</code><br />
<i>wordList</i> = <code>["hot","dot","dog","lot","log","cog"]</code></p>
<p>Return</p><pre class="crayon-plain-tag">[
    [&quot;hit&quot;,&quot;hot&quot;,&quot;dot&quot;,&quot;dog&quot;,&quot;cog&quot;],
    [&quot;hit&quot;,&quot;hot&quot;,&quot;lot&quot;,&quot;log&quot;,&quot;cog&quot;]
  ]</pre><p><b>Note:</b></p>
<ul>
<li>Return an empty list if there is no such transformation sequence.</li>
<li>All words have the same length.</li>
<li>All words contain only lowercase alphabetic characters.</li>
<li>You may assume no duplicates in the word list.</li>
<li>You may assume <i>beginWord</i> and <i>endWord</i> are non-empty and are not the same.</li>
</ul>
<p><strong>Idea:</strong></p>
<p>BFS to construct the graph + DFS to extract the paths</p>
<p><script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><br />
<ins class="adsbygoogle" style="display: block; text-align: center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-2404451723245401" data-ad-slot="7983117522"></ins><br />
<script>
     (adsbygoogle = window.adsbygoogle || []).push({});
</script></p>
<p><a href="http://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-1.png"><img class="alignnone size-full wp-image-433" src="http://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-1.png" alt="" width="960" height="540" srcset="https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-1.png 960w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-1-300x169.png 300w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-1-768x432.png 768w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-1-624x351.png 624w" sizes="(max-width: 960px) 100vw, 960px" /></a></p>
<p><a href="http://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-2.png"><img class="alignnone size-full wp-image-432" src="http://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-2.png" alt="" width="960" height="540" srcset="https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-2.png 960w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-2-300x169.png 300w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-2-768x432.png 768w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-2-624x351.png 624w" sizes="(max-width: 960px) 100vw, 960px" /></a></p>
<p><a href="http://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-3.png"><img class="alignnone size-full wp-image-431" src="http://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-3.png" alt="" width="960" height="540" srcset="https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-3.png 960w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-3-300x169.png 300w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-3-768x432.png 768w, https://zxi.mytechroad.com/blog/wp-content/uploads/2017/09/126-ep72-3-624x351.png 624w" sizes="(max-width: 960px) 100vw, 960px" /></a></p>
<p><strong>Solutions:</strong></p>
<p>C++, BFS 1</p><pre class="crayon-plain-tag">// Author: Huahua
// Running Time: 216 ms (better than 65.42%)
class Solution {
public:
    vector&lt;vector&lt;string&gt;&gt; findLadders(string beginWord, string endWord, vector&lt;string&gt;&amp; wordList) {
        
        unordered_set&lt;string&gt; dict(wordList.begin(), wordList.end());        
        if (!dict.count(endWord)) return {};
        dict.erase(beginWord);
        dict.erase(endWord);
        
        unordered_map&lt;string, int&gt; steps{{beginWord, 1}};
        unordered_map&lt;string, vector&lt;string&gt;&gt; parents;
        queue&lt;string&gt; q;
        q.push(beginWord);
        
        vector&lt;vector&lt;string&gt;&gt; ans;
        
        const int l = beginWord.length();
        int step = 0;        
        bool found = false;
        
        while (!q.empty() &amp;&amp; !found) {
            ++step;            
            for (int size = q.size(); size &gt; 0; size--) {
                const string p = q.front(); q.pop();
                string w = p;
                for (int i = 0; i &lt; l; i++) {
                    const char ch = w[i];
                    for (int j = 'a'; j &lt;= 'z'; j++) {
                        if (j == ch) continue;
                        w[i] = j;
                        if (w == endWord) {
                            parents[w].push_back(p);
                            found = true;
                        } else {
                            // Not a new word, but another transform
                            // with the same number of steps
                            if (steps.count(w) &amp;&amp; step &lt; steps.at(w))
                                parents[w].push_back(p);
                        }
                        
                        if (!dict.count(w)) continue;
                        dict.erase(w);
                        q.push(w);
                        steps[w] = steps.at(p) + 1;
                        parents[w].push_back(p);
                    }
                    w[i] = ch;
                }
            }
        }
        
        if (found) {
            vector&lt;string&gt; curr{endWord};
            getPaths(endWord, beginWord, parents, curr, ans);
        }
    
        return ans;
    }
private:
    void getPaths(const string&amp; word, 
                  const string&amp; beginWord, 
                  const unordered_map&lt;string, vector&lt;string&gt;&gt;&amp; parents,
                  vector&lt;string&gt;&amp; curr,
                  vector&lt;vector&lt;string&gt;&gt;&amp; ans) {        
        
        if (word == beginWord) {
            ans.push_back(vector&lt;string&gt;(curr.rbegin(), curr.rend()));
            return;
        }
        
        for (const string&amp; p : parents.at(word)) {
            curr.push_back(p);
            getPaths(p, beginWord, parents, curr, ans);
            curr.pop_back();
        }        
    }
};</pre><p></p>
<p><ins class="adsbygoogle"
     style="display:block; text-align:center;"
     data-ad-layout="in-article"
     data-ad-format="fluid"
     data-ad-client="ca-pub-2404451723245401"
     data-ad-slot="7983117522"><br />
</ins></p>
<p>C++ / BFS 2</p><pre class="crayon-plain-tag">// Author: Huahua
// Running time: 129 ms (better than 80.67%)
class Solution {
public:
    vector&lt;vector&lt;string&gt;&gt; findLadders(string beginWord, string endWord, vector&lt;string&gt;&amp; wordList) {
        vector&lt;vector&lt;string&gt;&gt; ans;
        unordered_set&lt;string&gt; dict(wordList.begin(), wordList.end());
        if (!dict.count(endWord)) return ans;
        dict.erase(endWord);        
        
        const int l = beginWord.length();
        unordered_set&lt;string&gt; q{beginWord}, p;
        unordered_map&lt;string, vector&lt;string&gt;&gt; children;
        bool found = false;
        
        while (!q.empty() &amp;&amp; !found) {
            
            for (const string&amp; word : q) 
                dict.erase(word);
            
            for (const string&amp; word : q) {                                
                string curr = word;
                for (int i = 0; i &lt; l; i++) {
                    char ch = curr[i];
                    for (int j = 'a'; j &lt;= 'z'; j++) {
                        curr[i] = j;
                        if (curr == endWord) {
                            found = true;
                            children[word].push_back(curr);
                        } else if (dict.count(curr) &amp;&amp; !found) {
                            p.insert(curr);
                            children[word].push_back(curr);
                        }
                    }
                    curr[i] = ch;
                }
            }
            
            std::swap(p, q);
            p.clear();
        }
        
        if (found) {
            vector&lt;string&gt; path{beginWord};
            getPaths(beginWord, endWord, children, path, ans);
        }
        
        return ans;
    }
private:
    void getPaths(const string&amp; word, 
                  const string&amp; endWord,                   
                  const unordered_map&lt;string, vector&lt;string&gt;&gt;&amp; children,
                  vector&lt;string&gt;&amp; path, 
                  vector&lt;vector&lt;string&gt;&gt;&amp; ans) {
        if (word == endWord) {
            ans.push_back(path);
            return;
        }
        
        const auto it = children.find(word);
        if (it == children.cend()) return;
        
        for (const string&amp; child : it-&gt;second) {
            path.push_back(child);
            getPaths(child, endWord, children, path, ans);
            path.pop_back();
        }
    }    
};</pre><p>&nbsp;</p>
<p>C++ / Bidirectional BFS</p><pre class="crayon-plain-tag">// Author: Huahua
// Running time: 39 ms (better than 93.74%)
class Solution {
public:
    vector&lt;vector&lt;string&gt;&gt; findLadders(string beginWord, string endWord, vector&lt;string&gt;&amp; wordList) {
        vector&lt;vector&lt;string&gt;&gt; ans;
        unordered_set&lt;string&gt; dict(wordList.begin(), wordList.end());
        if (!dict.count(endWord)) return ans;
        
        int l = beginWord.length();
        
        unordered_set&lt;string&gt; q1{beginWord};
        unordered_set&lt;string&gt; q2{endWord};
        unordered_map&lt;string, vector&lt;string&gt;&gt; children;

        bool found = false;
        bool backward = false;
        
        while (!q1.empty() &amp;&amp; !q2.empty() &amp;&amp; !found) {            
            // Always expend the smaller queue first
            if (q1.size() &gt; q2.size()) {
                std::swap(q1, q2);
                backward = !backward;
            }
            
            for (const string&amp; w : q1)
                dict.erase(w);
            for (const string&amp; w : q2)
                dict.erase(w);
                        
            unordered_set&lt;string&gt; q;
            
            for (const string&amp; word : q1) {
                string curr = word;
                for (int i = 0; i &lt; l; i++) {
                    char ch = curr[i];
                    for (int j = 'a'; j &lt;= 'z'; j++) {
                        curr[i] = j;
                        
                        const string* parent = &amp;word;
                        const string* child = &amp;curr;
                        
                        if (backward)
                            std::swap(parent, child);
                        
                        if (q2.count(curr)) {
                            found = true;
                            children[*parent].push_back(*child);
                        } else if (dict.count(curr) &amp;&amp; !found) {
                            q.insert(curr);
                            children[*parent].push_back(*child);
                        }
                    }
                    curr[i] = ch;
                }
            }
            
            std::swap(q, q1);
        }
        
        if (found) {
            vector&lt;string&gt; path{beginWord};
            getPaths(beginWord, endWord, children, path, ans);
        }
        
        return ans;
    }
private:
    void getPaths(const string&amp; word, 
                  const string&amp; endWord,                   
                  const unordered_map&lt;string, vector&lt;string&gt;&gt;&amp; children, 
                  vector&lt;string&gt;&amp; path, 
                  vector&lt;vector&lt;string&gt;&gt;&amp; ans) {        
        if (word == endWord) {
            ans.push_back(path);
            return;
        }
        
        const auto it = children.find(word);
        if (it == children.cend()) return;
        
        for (const string&amp; child : it-&gt;second) {
            path.push_back(child);
            getPaths(child, endWord, children, path, ans);
            path.pop_back();
        }
    }
};</pre><p>&nbsp;</p>
<p>The post <a rel="nofollow" href="https://zxi.mytechroad.com/blog/searching/leetcode-126-word-ladder-ii/">花花酱 LeetCode 126. Word Ladder II</a> appeared first on <a rel="nofollow" href="https://zxi.mytechroad.com/blog">Huahua&#039;s Tech Road</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://zxi.mytechroad.com/blog/searching/leetcode-126-word-ladder-ii/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
