以下是Facebook招聘样本测试的问题。
有K钉。当从挂钉的底部到顶部观察时,每个挂钉可以以半径的递减顺序保持圆盘。有N个半径为1到N的圆盘;给定栓钉的初始配置和栓钉的最终配置,输出从初始配置转换为最终配置所需的移动。您需要以最少的移动次数进行转换。
移动包括拾取任何一个钉子的最顶部圆盘并将其放置在任何其他钉子的顶部。 在任何时候,必须保持所有栓钉的半径减小性能。
约束:
1 <= N <= 8
3 <= K <= 5
输入格式:
N K
第二行包含N个整数。每个整数的范围是1到K,其中第i个整数表示初始配置中存在半径为i的磁盘的挂钩。
第3行表示最终配置,格式与初始配置类似。
输出格式:
第一行包含M - 完成转换所需的最小移动次数。
以下M行描述了一个移动,通过一个挂钩编号来挑选,一个挂钩编号放在上面。 如果有多个解决方案,则输出其中任何一个就足够了。您可以假设,始终存在少于7次移动的解决方案,并且初始确认将与最终确认不同。
示例输入#00:
2 3
1 1
2 2
示例输出#00:
3
1 3
1 2
3 2
示例输入#01:
6 4
4 2 4 3 1 1
1 1 1 1 1 1
示例输出#01:
5
3 1
4 3
4 1
2 1
3 1
讨论此问题的解决方案没有任何害处,因为这是一个示例问题。
经典的河内塔问题的解决方案非常简单:
void hanoi(char s, char i, char d, int n)
{
if(n>0)
{
hanoi(s, d, i, n-1);
cout<<s<<":"<<d<<endl;
hanoi(i, s, d, n-1);
}
}
以上也可以扩展到河内的一般'k'钉塔。但是,这种知识对于设计这个样本难题的解决方案并不是很有用。关于如何处理此问题以及将来会遇到类似问题的任何建议?
答案 0 :(得分:8)
这是我的动态编程解决方案,它在最多O(K ^ N)步骤中找到最佳移动序列,它在K = 5,N = 8的情况下在一秒钟内运行。由于懒惰,我对输入数据进行了硬编码
它是一个通过状态空间的BFS,从不会两次访问同一个州。然后它通过从末尾开始回溯来获得实际路径(该部分与最佳序列的长度成线性关系)。基本上,它是“通过迷宫的最短路径”算法,但“迷宫”是问题的状态空间,起始“位置”是初始状态,结束“位置”是期望状态。
许多类似的问题都可以通过这种方式解决,只要你能定义一个有限的状态空间,你的目标是最小化的两个状态之间的“距离”,以及一种计算你可以从哪个状态移动的状态的方法。当前状态。
例如,可以使用相同的算法解决具有任意数量的“传教士和食人族”问题。
此外,如果您需要“所有最佳解决方案”而不是“任何最佳解决方案”,则可以轻松修改算法以提供它们。
class Program
{
static int N = 8;
static int K = 5;
static List<int> StartState = new List<int> { 3, 3, 2, 1, 4, 1, 5, 2 };
static List<int> EndState = new List<int> { 1, 4, 2, 2, 3, 4, 4, 3 };
static LinkedList<int> StateQueue = new LinkedList<int>();
static int[] MovesToState = new int[(int)Math.Pow(K, N)];
static void Main(string[] args)
{
for (int i = 0; i < StartState.Count; i++)
{
StartState[i]--;
EndState[i]--;
}
int startStateIndex = StateToNum(StartState);
int endStateIndex = StateToNum(EndState);
for (int i = 0; i < MovesToState.Length; i++)
MovesToState[i] = -1;
MovesToState[startStateIndex] = 0;
StateQueue.AddFirst(startStateIndex);
while (StateQueue.Count > 0 && MovesToState[endStateIndex] == -1)
{
var legalMoves = LegalMoves(StateQueue.Last.Value);
foreach (var newStateIndex in legalMoves)
{
int currMoves = MovesToState[StateQueue.Last.Value];
if (MovesToState[newStateIndex] == -1)
{
MovesToState[newStateIndex] = currMoves + 1;
StateQueue.AddFirst(newStateIndex);
}
}
StateQueue.RemoveLast();
}
var currStateIndex = endStateIndex;
var moves = new List<Tuple<int, int>>();
while (currStateIndex != startStateIndex)
{
var legalMoves = LegalMoves(currStateIndex);
int currMoves = MovesToState[currStateIndex];
foreach (var prevStateIndex in legalMoves)
{
if (MovesToState[prevStateIndex] == MovesToState[currStateIndex] - 1)
{
var currState = NumToState(currStateIndex);
var prevState = NumToState(prevStateIndex);
for (int i = 0; i < N; i++)
{
if (currState[i] != prevState[i])
{
moves.Add(new Tuple<int, int>(prevState[i] + 1, currState[i] + 1));
currStateIndex = prevStateIndex;
break;
}
}
}
}
}
Console.WriteLine(MovesToState[endStateIndex]);
moves.Reverse();
foreach (var move in moves)
{
Console.WriteLine("{0} {1}", move.Item1, move.Item2);
}
Console.Read();
}
static List<int> LegalMoves(int stateIndex)
{
var legalMoves = new List<int>();
var state = NumToState(stateIndex);
int[] minOnPeg = new int[K];
for (int i = 0; i < minOnPeg.Length; i++)
minOnPeg[i] = N;
for (int i = 0; i < N; i++)
for (int j = 0; j < K; j++)
if (state[i] == j && i < minOnPeg[j])
minOnPeg[j] = i;
bool[] isTop = new bool[N];
for (int i = 0; i < isTop.Length; i++)
isTop[i] = false;
for (int i = 0; i < K; i++)
if (minOnPeg[i] < N)
isTop[minOnPeg[i]] = true;
for (int i = 0; i < N; i++)
{
if (!isTop[i])
continue;
for (int j = 0; j < K; j++)
{
if (minOnPeg[j] <= i)
continue;
var tmp = state[i];
state[i] = j;
var newStateIndex = StateToNum(state);
legalMoves.Add(newStateIndex);
state[i] = tmp;
}
}
return legalMoves;
}
static int StateToNum(List<int> state)
{
int r = 0;
int f = 1;
foreach (var peg in state)
{
r += f * peg;
f *= K;
}
return r;
}
static List<int> NumToState(int num)
{
var r = new List<int>();
for (int i = 0; i < N; i++)
{
r.Add(num % K);
num = num / K;
}
return r;
}
}
答案 1 :(得分:2)
在java中找到此solution。 基本上它将所有可能的移动映射到树中并执行BFS。
答案 2 :(得分:2)
答案 3 :(得分:0)
上面的链接在博客中有一个很好的算法基准(请注意MAX_MOVES应该增加到11):
6 4
3 3 2 1 4 1
1 4 2 2 3 4
Leandro Facchinetti from this comment的Ruby版本在〜10秒内解决了它。 Java version通过e-digga~0.5秒。我的python版本运行在~30ms。我不确定为什么我的实现如此之快。这是:
import sys
MAX_MOVES = 11
def valid_moves(state, K):
pegs, tops = [-1] * K, []
for r, peg in enumerate(state):
if pegs[peg] < 0:
pegs[peg] = r
for top_r, top_peg in tops:
yield (top_r, top_peg, peg)
tops.append((r, peg))
for dst_peg, peg_r in enumerate(pegs):
if peg_r < 0:
for top_r, top_peg in tops:
yield (top_r, top_peg, dst_peg)
def move_apply(state, move):
r, src, dst = move
return state[:r] + (dst,) + state[r + 1:]
def solve_bfs(initial_state, final_state, K):
known_states = set()
next_states = [(initial_state, [])]
depth = 0
while next_states and depth < MAX_MOVES:
states, next_states = next_states, []
for state, moves in states:
for move in valid_moves(state, K):
new_state = move_apply(state, move)
if new_state in known_states:
continue
new_moves = moves + [move]
if new_state == final_state:
return new_moves
next_states.append((new_state, new_moves))
known_states.add(new_state)
depth += 1
lines = sys.stdin.readlines()
N, K = [int(i) for i in lines[0].strip().split()]
initial_state = tuple(int(i) - 1 for i in lines[1].strip().split())
final_state = tuple(int(i) - 1 for i in lines[2].strip().split())
solution = solve_bfs(initial_state, final_state, K)
if solution:
print len(solution)
for disk, src, dst in solution:
print src + 1, dst + 1
答案 4 :(得分:0)
这里的第一篇文章。我已经使用stackoverflow作为潜伏者,但从未做过贡献所以我想这次我会这样做,因为我有一些代码要分享。
我也尝试过这个问题,花了一段时间才弄明白!我想我会在这里发表我的辛勤工作。我无法在45分钟内解决这个问题。我是一名计算机工程专业的学生(不是计算机科学专业),所以我的数据结构生锈了。我以前从未编写过BFS代码!
无论如何,我的程序使用与前面答案中讨论的相同的BFS方法,但数据结构的处理方式可能略有不同。它基本上首先生成有效的移动宽度(用FIFO队列实现),然后将新的挂钩/磁盘配置(状态)放在格式为{state:previous_state}的字典中。一旦找到最终状态或达到最大递归深度,程序将停止搜索新状态。然后它从字典中检索最终状态并通过字典回溯以找到连续发生的移动。我在程序中使用的主要数据结构是具有不同状态的dict。我实际上并没有记录输出中所需的(从,到)挂钩号码。当我通过在两个连续状态之间找到一个改变的元素来回溯dict树时,计算该输出。状态以与输入相同的格式记录为元组。
希望这可以帮助某人,我欢迎任何关于如何改进我的代码或编码风格的意见或建议。
import sys
MAX_MOVES = 10
def main():
lines = sys.stdin.readlines()
[discs, npegs] = map(int, lines[0].split())
#read in the initial and final states
istate = tuple(int(n) - 1 for n in lines[1].split())
fstate = tuple(int(n) - 1 for n in lines[2].split())
#call recursive function to find possible moves and
#generate new states to add to tree
tree = findStates(istate, fstate, npegs)
solution = findSolution(istate, fstate, tree)
if solution:
print solution[0]
for a, b in solution[1:]:
print "{} {}".format(a,b)
else:
print "No solution found for {} max moves".format(MAX_MOVES)
def findTopDisks(state, npegs):
"""
list the pegs with disks and the top disk radius in a dict with key being peg number
and value being disk radius
This function is used to find valid disks and their peg positions to make a move from
"""
topdict = dict()
for peg in range(npegs):
if peg in state:
topdict[peg] = state.index(peg)
return topdict
def findValidMoves(state, npegs):
"""
Finds the valid moves given the current state and number of pegs.
Yields tuples consisting of source and destination pegs
"""
#find the top disk of every peg number
top_disks = findTopDisks(state, npegs)
for from_peg, disk_r in top_disks.items():
for dest_peg in range(npegs):
if not top_disks.has_key(dest_peg): #dest peg is empty
yield (from_peg, dest_peg)
elif top_disks[dest_peg] > disk_r:
yield (from_peg, dest_peg)
def findStates(istate, fstate, npegs):
"""
Generates new states first by calling findValidMoves on current state to get valid moves.
Then generates the new states and put them in the tree implemented using a dict.
Key of the dict is the current state, and value is the state that leads to that state.
"""
queue = [(istate, 0)]
tree = {istate: None}
while queue and (queue[0][1] < MAX_MOVES):
cstate = queue[0][0]
cmove = queue[0][1]
queue.pop(0)
#enumerate and find all valid moves and add to queue
for from_peg, dest_peg in findValidMoves(cstate, npegs):
if from_peg in cstate:
nstate = list(cstate)
nstate[cstate.index(from_peg)] = dest_peg
nstate = tuple(nstate)
if nstate not in tree: #new state never been found!
tree[nstate] = cstate
if nstate == fstate:
return tree
queue.append((nstate, cmove+1))
return tree
def findSolution(istate, fstate, tree):
"""
back track through dict and find the moves taken to get from istate and final state
"""
solution = []
cstate = fstate
if fstate in tree:
while (cstate != istate):
#compare current state and previous
#find the difference, record in tuple and add to list of solution moves
pstate = tree[cstate]
for p, c in zip(pstate, cstate):
if p != c:
solution.insert(0, (p+1, c+1)) #add one to adjust for 0 offset
break
cstate = pstate
solution.insert(0, len(solution))
return solution
if __name__ == "__main__":
main()
答案 5 :(得分:0)
这是一个Java代码,它使用相邻配置准备图形并解决它。我尝试使用面向对象的方式,但我仍然觉得可能有更好的黑客方法来更快地解决这个问题。希望它有所帮助。
import FBSample.Node.Move;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Scanner;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
/**
*
* @author Sada Kurapati <sadakurapati@gmail.com>
*/
public class FBSample {
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
//pegs initial config
Node source = readPegsConfiguration(k, n, sc);
//read target configuration
Node target = readPegsConfiguration(k, n, sc);
//To keep track what config we visited and avoid cycles
Set<Node> visited = new HashSet<Node>();
try {
minMovesToTarget(source, target, visited);
} catch (Exception ex) {
System.out.println("Exception = " + ex);
}
}
private static void minMovesToTarget(Node source, Node target, Set<Node> visited) throws CloneNotSupportedException {
//Perform BFS
//add soource node to Queue
Queue<Node> q = new LinkedList<Node>();
q.add(source);
Node current = source;
while (!q.isEmpty()) {
current = q.poll();
if (current.equals(target)) { //found the target configuration
break;
}
List<Node> neighbors = current.neighbors();
if (neighbors.size() > 0) {
for (Node n : neighbors) {
if (!visited.contains(n)) {//if not visited, put it in queue
q.offer(n);
visited.add(n);
}
}
}
}
//Printing path and moves if target config found
if (current.equals(target)) {
printOutput(current);
}
}
private static Node readPegsConfiguration(int k, int n, Scanner sc) {
Stack<Integer>[] initialState = new Stack[k];
for (int i = 0; i < k; i++) {
initialState[i] = new Stack<Integer>();
}
//reading and reversing the line as we need to put the elements in decresing size
//disc is key and peg is value.
TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>(Collections.reverseOrder());
for (int i = 0; i < n; i++) {
map.put(i, sc.nextInt());
}
//prepare pegs
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
initialState[entry.getValue() - 1].push(entry.getKey());
}
return new Node(initialState);
}
static void printOutput(Node target) {
Stack<Move> stack = new Stack<>(); //using stack as we need to print the trail from Source - target config
while (target.parent != null) {
stack.add(target.move);
target = target.parent;
}
System.out.println(stack.size());
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
static class Node implements Cloneable {
//pegs
Stack<Integer>[] state = null;
Node parent = null; //for backtracking trail
Move move = null; // The move we made to go to next neighbor config
public Node(Stack<Integer>[] st) {
state = st;
}
@Override
protected Node clone() throws CloneNotSupportedException {
Stack<Integer>[] cloneStacks = new Stack[state.length];
for (int i = 0; i < state.length; i++) {
cloneStacks[i] = (Stack) state[i].clone();
}
Node clone = new Node(cloneStacks);
return clone;
}
//returns the neghboring configurations.
//What all configurations we can get based on current config.
public List<Node> neighbors() throws CloneNotSupportedException {
List<Node> neighbors = new ArrayList<>();
int k = state.length;
for (int i = 0; i < k; i++) {
for (int j = 0; j < k; j++) {
if (i != j && !state[i].isEmpty()) {
Node child = this.clone();
//make a move
if (canWeMove(child.state[i], child.state[j])) {
child.state[j].push(child.state[i].pop());
//this is required to backtrack the trail once we find the target config
child.parent = this;
//the move we made to get to this neighbor
child.move = new Move(i, j);
neighbors.add(child);
}
}
}
}
return neighbors;
}
public boolean canWeMove(Stack<Integer> fromTower, Stack<Integer> toTower) {
boolean answer = false;
if (toTower.isEmpty()) {// if destination peg is empty, then we can move any disc
return true;
}
int toDisc = toTower.peek();
int fromDisc = fromTower.peek();
if (fromDisc < toDisc) { //we can only place small disc on top
answer = true;
}
return answer;
}
@Override
public int hashCode() {
int hash = 7;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Node other = (Node) obj;
if (!Arrays.deepEquals(this.state, other.state)) {
return false;
}
return true;
}
class Move {
int pegFrom, pegTo;
public Move(int f, int t) {
pegFrom = f + 1;
pegTo = t + 1;
}
@Override
public String toString() {
return pegFrom + " " + pegTo;
}
}
}
}