求最小操作数
题目详情:
给了A、B两个单词和一个单词集合Dict,每个的长度都相同。我们希望通过若干次操作把单词A变成单词B,每次操作可以改变单词中的一个字母,同时,新产生的单词必须是在给定的单词集合Dict中。求所有行得通步数最少的修改方法。
举个例子如下:
Given:
A = "hit"
B = "cog"
Dict = ["hot","dot","dog","lot","log"]
Return
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
即把字符串A = "hit"转变成字符串B = "cog",有以下两种可能:
"hit" -> "hot" -> "dot" -> "dog" -> "cog";
"hit" -> "hot" -> "lot" -> "log" ->"cog"。
答题说明:
A和B相同的情况下不需要做转换,此时直接返回空集;
main函数是为方便你在提交代码之前进行在线编译测试,可不完成。
经过两个朋友的一起讨论,给出的结论就是利用动态规划。
时间复杂度是 O(字母数 * 转换单词长度 * 字典单词数)
时间复杂度是 O(字母数 * 转换单词长度 * 字典单词数)
解题思路:
这让我想到大学里面的动态规划,于是仔细回忆曾经学过的东西,用手画出上面个东西。大概思路就是,对每个步骤进行分解,从第一步开始,用每个变化后的值到字典中寻找对应的项,如果存在,则将路径进行记录,并推入堆栈。然后依次类推。
师傅的思路:(这个啰嗦而纠结的男人又唠叨了一把,念念叨叨说他以前参赛最先放弃的就是这种实现比分析困难多倍的题目)
什么数量限定,不知道效率需求。简单来说,你把dictionary里面的单词以字母和位数作为边,构建一个转换的图,然后从你的起始点做广度遍历到目标点就OK了。。。最短路径都是WFS的问题,哪里有动态规划的事情了。。。
各种答案:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.felidae.gcj;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
*
* @author rock
*/
public class CharacterTransaction {
public int getMinimal(String[] dictionary, String src, String dest) {
Set<String> dict;
dict = new HashSet<>();
dict.addAll(Arrays.asList(dictionary));
dict.remove(src);
dict.remove(dest);
Queue<String> queue = new LinkedList<>();
Map<String, Integer> visited = new HashMap<>();
Map<String, String> lastStr = new HashMap<>();
queue.add(src);
visited.put(src, 0);
while (!queue.isEmpty()) {
String word = queue.poll();
int length = visited.get(word);
char[] chars = word.toCharArray();
for (int i = 0; i < chars.length; i++) {
for (int j = 'a'; j <= 'z'; j++) {
char oldChar = chars[i];
chars[i] = (char)j;
String newStr = new String(chars);
if (newStr.equals(dest)) {
lastStr.put(newStr, word);
String str = dest;
while (str != null) {
System.out.println(str);
str = lastStr.get(str);
}
return length + 1;
}
if (!visited.containsKey(newStr) && dict.contains(newStr)) {
queue.add(newStr);
visited.put(newStr, length + 1);
lastStr.put(newStr, word);
}
chars[i] = oldChar;
}
}
}
return -1;
}
public static void main(String[] args) {
CharacterTransaction characterTransaction = new CharacterTransaction();
System.out.println(characterTransaction.getMinimal(new String[]{"hit", "hgt", "wez", "wiz","zgt", "ait", "abt", "wit"}, "zit", "wea"));
}
}
function inArray(val, ary)
{
if (typeof ary !== 'object') return -1
for (var i = 0; i < ary.length; i++) {
if (val === ary[i]) return i
}
return -1
}
function getNextSteps(word, tailWord, dict)
{
var steps = []
for (var i = 0; i < word.length; i++) {
var tmpReg = new RegExp(
'^' + word.slice(0, i) + '.' + word.substring(i + 1) + '$',
'i')
for (var j = 0; j < dict.length; j++) {
if (tmpReg.test(dict[j])) {
steps.push(dict[j])
if (dict[j] != tailWord) {
delete dict[j]
}
}
}
}
return steps;
}
function searchPath(headWord, tailWord, dict)
{
if (headWord === tailWord) return []
var h = inArray(headWord, dict)
if (h >= 0) delete dict[h]
var t = inArray(tailWord, dict)
if (t < 0) dict.push(tailWord)
var result = []
var curPath = []
var search = function (word) {
curPath.push(word)
var steps = getNextSteps(word, tailWord, dict)
if (steps.length == 0) {
curPath.pop()
return
} else {
for (var i = 0; i < steps.length; i++) {
if (steps[i] === tailWord) {
var tmp = curPath.concat(tailWord)
if (tmp.length < result.length || result.length == 0) {
result = tmp
}
continue
}
search(steps[i])
}
curPath.pop()
}
}
search(headWord)
return result
}
function test(){
var headWord = 'mrong'
var tailWord = 'xkang'
var dict = [
'mrong',
'xrong', 'xkong',
'mrang', 'xrang',
'grong', 'gkong', 'gkang',
'mrcng', 'mrcog', 'xrcog', 'xkcog', 'xkaog',
'xkang'
]
// mrong -> xrong -> xkong -> xkang
// mrong -> mrang -> xrang -> xkang
// mrong -> grong -> gkong -> gkang -> xkang
// mrong -> mrcng -> mrcog -> xrcog -> xkcog -> xkaog -> xkang
var result = searchPath(headWord, tailWord, dict)
console.log(result.length + ':' + result.join('->'))
}
test();
// 自己的大作。。。。唉。。。。
function WordPath(){
var queue = []; // [string]
var visited = {}; // {string:int}
var pathMap = {}; // {string:string}
var charArr = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
function inArray(str,dict){
if (typeof dict == "object" && dict.hasOwnProperty("length")){
for (var i = 0;i < dict.length;i++){
if (str == dict[i]){
return i;
}
}
}
return -1;
} // return int
function print(dest,pathMap){
var headStr = dest;
var nextStr = pathMap[dest];
var num = 0;
console.info(pathMap);
do{
num++;
console.info(headStr + "=>" + nextStr);
headStr = nextStr;
nextStr = pathMap[headStr];
}while(nextStr);
console.info(num);
} // return void
this.printPath = function(dict,src,dest){
queue.push(src);
visited[src] = 0;
while (queue.length) {
var curStr = queue.shift();
var curStrArr = curStr.split("");
for (var i = 0; i < curStrArr.length; i++) {
for (var j = 0; j < charArr.length; j++) {
var oldChar = curStrArr[i];
curStrArr[i] = charArr[j];
var newStr = curStrArr.join("");
if (newStr == dest) {
pathMap[newStr] = curStr;
visited[newStr]++;
print(dest,pathMap);
return false;
}
if (!pathMap[newStr] && inArray(newStr,dict) != -1) {
pathMap[newStr] = curStr;
queue.push(newStr);
visited[newStr]++;
}
curStrArr[i] = oldChar;
}
}
}
};
}
var path = new WordPath();
path.printPath(["hot","dot","dog","lot","log"],"hit","cog");
运行结果:
localhost:~ marong$ node /********
{ hot: 'hit',
dot: 'hot',
lot: 'hot',
dog: 'dot',
log: 'lot',
cog: 'dog' }
cog=>dog
dog=>dot
dot=>hot
hot=>hit
4
localhost:~ marong$
运行结果:
测试数据为包含10000个长度为4的字符串,起始分别是 “aaaa”,”zzzz“。
用上面第三种算法运行之后的结果为:
zzzz=>ozzz
ozzz=>ozfz
ozfz=>fzfz
fzfz=>fzff
fzff=>fvff
fvff=>uvff
uvff=>ufff
ufff=>uffl
uffl=>ufel
ufel=>efel
efel=>enel
enel=>enea
enea=>wnea
wnea=>wyea
wyea=>wyza
wyza=>waza
waza=>waca
waca=>aaca
aaca=>aaaa
19
34256
但是师傅用他写的java跑相同数据,得出来的结果跟我相同,时间为 300+毫秒。也就是说比我写的快了平方个数量级的毫秒时间。他指出,inArray的实现方式很糟糕,让算法复杂度从 n变为n的平方。他觉得可以想办法利用json对象来实现查找。哈哈,虽然他不会写js,但是却一下子说到点子上了。我立马就反应过来了。将程序进行了修改:
function WordPath(){
var queue = []; // [string]
var visited = {}; // {string:int}
var pathMap = {}; // {string:string}
var dictJson = {}; // {string:int}
var charArr = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
// 新增的将数组转换为json对象的方法
function arrayToJson(arr){
var jsonObj = {};
if (typeof arr == "object" && arr.hasOwnProperty("length")){
for (var i = 0;i < arr.length;i++){
jsonObj[arr[i]] = i+1;
}
}
return jsonObj;
} // return json
function print(dest,pathMap){
var headStr = dest;
var nextStr = pathMap[dest];
var num = 0;
console.info(pathMap);
do{
num++;
console.info(headStr + "=>" + nextStr);
headStr = nextStr;
nextStr = pathMap[headStr];
}while(nextStr);
console.info(num);
} // return void
this.printPath = function(dict,src,dest){
dictJson = arrayToJson(dict); // 通过新增的方法,获取字典数组的json对象
queue.push(src);
visited[src] = 0;
while (queue.length) {
var curStr = queue.shift();
var curStrArr = curStr.split("");
for (var i = 0; i < curStrArr.length; i++) {
for (var j = 0; j < charArr.length; j++) {
var oldChar = curStrArr[i];
curStrArr[i] = charArr[j];
var newStr = curStrArr.join("");
if (newStr == dest) {
pathMap[newStr] = curStr;
visited[newStr]++;
print(dest,pathMap);
return false;
}
// 通过判断目标字符串在dictJson中对应的值,判断其是否存在
if (!pathMap[newStr] && dictJson[newStr]) {
pathMap[newStr] = curStr;
queue.push(newStr);
visited[newStr]++;
}
curStrArr[i] = oldChar;
}
}
}
};
}
// 时间的耗费为:228s