BFS and StringBuilder
New LeetCode medium problem: https://leetcode.com/problems/open-the-lock/description/:
Given the small number of permutations and the fact that we're looking for the minimum number of turns, my first inclination was to use a BFS (breadth-first search). To make the problem a little more interesting the author added a set of "dead ends". That's not a problem, all it is needed is to hash-table this set and during the "walk" part of the code make sure that the "neighbor" codes are not in this hash-table. Since we're manipulating a string of digits, I thought it was a better idea to use a StringBuilder instead of string since the former can be altered differently from the latter. That creates some interesting dynamics and calculations that need to be taken care of properly (like if you want to copy a StringBuilder, remember that a simple assignment is not going to work since you're dealing with references rather than values, so that's needed to be taken into consideration). Other than that, standard BFS. Bye now!! Marcelo.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace LeetCode
{
class Program
{
static void Main(string[] args)
{
string[] deadends = { "0201", "0101", "0102", "1212", "2002" };
string target = "0202";
Solution sol = new Solution();
Console.WriteLine(sol.OpenLock(deadends, target));
}
}
public class Solution
{
public int OpenLock(string[] deadends, string target)
{
Hashtable visited = new Hashtable();
Hashtable htDeadends = new Hashtable();
foreach (string s in deadends)
if (!htDeadends.ContainsKey(s)) htDeadends.Add(s, true);
StringBuilder initial = new StringBuilder("0000");
if (htDeadends.ContainsKey(initial.ToString())) return -1;
Queue queue = new Queue();
QueueItem queueItem = new QueueItem(initial, 0);
visited.Add(initial, true);
queue.Enqueue(queueItem);
while (queue.Count > 0)
{
queueItem = (QueueItem)queue.Dequeue();
//Process
if (queueItem.code.ToString().Equals(target))
{
return queueItem.turns;
}
//Walk
for (int i = 0; i < initial.Length; i++)
{
for (int d = -1; d <= 1; d += 2)
{
int val = (int)(queueItem.code[i] - '0') + d;
if (val == 10) val = 0;
if (val == -1) val = 9;
StringBuilder newCode = new StringBuilder(queueItem.code.ToString());
newCode[i] = (char)(val + '0');
if (!htDeadends.ContainsKey(newCode.ToString()) && !visited.ContainsKey(newCode.ToString()))
{
QueueItem newQueueItem = new QueueItem(newCode, queueItem.turns + 1);
queue.Enqueue(newQueueItem);
visited.Add(newCode.ToString(), true);
}
}
}
}
return -1;
}
}
public class QueueItem
{
public StringBuilder code;
public int turns = 0;
public QueueItem(StringBuilder code, int turns)
{
this.code = new StringBuilder(code.ToString());
this.turns = turns;
}
}
}
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', '8', '9'
. The wheels can rotate freely and wrap around: for example we can turn'9'
to be'0'
, or'0'
to be'9'
. Each move consists of turning one wheel one slot.
The lock initially starts at'0000'
, a string representing the state of the 4 wheels.
You are given a list ofdeadends
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.
Given atarget
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.
Given the small number of permutations and the fact that we're looking for the minimum number of turns, my first inclination was to use a BFS (breadth-first search). To make the problem a little more interesting the author added a set of "dead ends". That's not a problem, all it is needed is to hash-table this set and during the "walk" part of the code make sure that the "neighbor" codes are not in this hash-table. Since we're manipulating a string of digits, I thought it was a better idea to use a StringBuilder instead of string since the former can be altered differently from the latter. That creates some interesting dynamics and calculations that need to be taken care of properly (like if you want to copy a StringBuilder, remember that a simple assignment is not going to work since you're dealing with references rather than values, so that's needed to be taken into consideration). Other than that, standard BFS. Bye now!! Marcelo.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace LeetCode
{
class Program
{
static void Main(string[] args)
{
string[] deadends = { "0201", "0101", "0102", "1212", "2002" };
string target = "0202";
Solution sol = new Solution();
Console.WriteLine(sol.OpenLock(deadends, target));
}
}
public class Solution
{
public int OpenLock(string[] deadends, string target)
{
Hashtable visited = new Hashtable();
Hashtable htDeadends = new Hashtable();
foreach (string s in deadends)
if (!htDeadends.ContainsKey(s)) htDeadends.Add(s, true);
StringBuilder initial = new StringBuilder("0000");
if (htDeadends.ContainsKey(initial.ToString())) return -1;
Queue queue = new Queue();
QueueItem queueItem = new QueueItem(initial, 0);
visited.Add(initial, true);
queue.Enqueue(queueItem);
while (queue.Count > 0)
{
queueItem = (QueueItem)queue.Dequeue();
//Process
if (queueItem.code.ToString().Equals(target))
{
return queueItem.turns;
}
//Walk
for (int i = 0; i < initial.Length; i++)
{
for (int d = -1; d <= 1; d += 2)
{
int val = (int)(queueItem.code[i] - '0') + d;
if (val == 10) val = 0;
if (val == -1) val = 9;
StringBuilder newCode = new StringBuilder(queueItem.code.ToString());
newCode[i] = (char)(val + '0');
if (!htDeadends.ContainsKey(newCode.ToString()) && !visited.ContainsKey(newCode.ToString()))
{
QueueItem newQueueItem = new QueueItem(newCode, queueItem.turns + 1);
queue.Enqueue(newQueueItem);
visited.Add(newCode.ToString(), true);
}
}
}
}
return -1;
}
}
public class QueueItem
{
public StringBuilder code;
public int turns = 0;
public QueueItem(StringBuilder code, int turns)
{
this.code = new StringBuilder(code.ToString());
this.turns = turns;
}
}
}
Yay for Python's conciseness :):
ReplyDeleteimport collections
class Solution:
def openLock(self, deadends: 'List[str]', target: 'str') -> 'int':
initial_state = "0000"
deadends = set(deadends)
q = collections.deque([initial_state])
seen = set(q)
turns = 0
def turn_up(idx, state):
return state[:idx] + str((int(state[idx]) + 1) % 10) + state[idx+1:]
def turn_down(idx, state):
return state[:idx] + str((int(state[idx]) + 9) % 10) + state[idx+1:]
while q:
for _ in range(len(q)):
state = q.popleft()
if state == target:
return turns
for i in range(len(state)):
for new_state in (turn_down(i, state), turn_up(i, state)):
if new_state not in seen and state not in deadends:
seen.add(new_state)
q.append(new_state)
turns += 1
return -1