首页 > 试题广场 >

词语序列

[编程题]词语序列
  • 热度指数:20512 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解
给定两个单词(初始单词和目标单词)和一个单词字典,请找出所有的从初始单词到目标单词的最短转换序列的长度:
  1. 每一次转换只能改变一个单词
  2. 每一个中间词都必须存在单词字典当中
例如:
给定的初始单词start="red",
目标单词end ="tax"。
单词字典dict =["ted","tex","red","tax","tad","den","rex","pee"]
一个最短的转换序列为"red" -> "ted" -> "tad" -> "tax",
返回长度4

注意:
如果没有符合条件的转换序列,返回0。
题目中给出的所有单词的长度都是相同的
题目中给出的所有单词都仅包含小写字母

双向BFS
import java.util.*;

class Solution {
    /**
     *  双向BFS,减少容器的使用的情况下减少比较次数
     *  1. 创建两个初始集合,一个保存从前向后遍历的候选词
     *     另一个保存从后向前的候选词
     *  2. 两个集合取数量小的一个进行遍历,判断词的下一个词
     *     是否就在另一个集合中,如果在的话,返回当前计数+1
     *  3. 如果下一个词不在另一个集合,但是在总集合中,从总
     *     集合擦除该词,遍历后得到新的候选词集合,替换掉当前
     *     被遍历的集合后,计数+1
     *  4. 其他退出条件:如果某一个集合为空,代表没有找到路径
     *     返回0
     */   
    public int ladderLength(String beginWord, String endWord, HashSet<String> dict) {
        int max = dict.size()+1;
        Set<String> aSet = new HashSet<>(max); 
        Set<String> bSet = new HashSet<>(max);
        int count = 1;
        if(!dict.contains(endWord)) return 0;
        aSet.add(beginWord);
        bSet.add(endWord);
        dict.remove(beginWord);
        dict.remove(endWord);
        while(!aSet.isEmpty()&&!bSet.isEmpty()){
            if(aSet.size()>bSet.size()){
                Set<String> tmp = aSet;
                aSet = bSet;
                bSet = tmp;
            }
            Set<String> next = new HashSet<>(max);
            for(String word:aSet){
                char[] chars = word.toCharArray();
                for(int i=0; i<chars.length; i++){
                    for(char c='a'; c<='z'; c++){
                        if(c==chars[i]) continue;
                        char tmp = chars[i];
                        chars[i] = c;
                        String trans = new String(chars);
                        chars[i] = tmp;
                        if(bSet.contains(trans)){
                            return count+1;
                        }
                        if(dict.contains(trans)){
                            dict.remove(trans);
                            next.add(trans);
                        }
                    }
                }
            }
            aSet = next;
            count++;
        }
        return 0;
    }
}


分享最初使用的笨方法,写起来比较罗嗦,时间复杂度还阔以,但是辅助容器用的比较多,胜在好理解,虽然比最优解要慢一倍,但是有这个思考过程,对上面解法理解比较快
import java.util.*;

class Solution {
    class Graph{
        String word;
        List<Graph> nexts;
        public Graph(String word, List<Graph> nexts){
            this.word = word;
            this.nexts = nexts;
        }
    }
    /*
     *  1. 首先判断下endWord是否在wordList中,不在的话根据题意直接返回0
     *  2. 创建邻接表将wordList以及beginWord放入图中
     *      邻接链表的创建原则是当两个词只差一个字母(题目已经限定所有词等长)
     *      问题转化为判断两个词只差一个单词
     *      可以遍历wordList,逐一将每一个字母用"*"替换,然后保存到Map中
     *      这个Map用替换掉某一位字母的字符串为key, 将所有只有某一位不同的单次保存在同一个List中
     *      建立过程记得通过第一步判断的数组索引保存好目标节点的引用,用于下一步最短路径的判断
     *  3. 建立好图以后,知道起始节点和目标节点,可以通过BFS求最短路径
     *      建一个新的Set保存已经遍历过的节点,因为是BFS,所以已经遍历过的节点,到目标节点的距离
     *      肯定比当前节点要短(当前图是无权无向图),求最短路径不需要再去判断
     *      如果不存在路径,返回0
     *  遇到的几个坑:
     *      1) 起始词不能作为接龙词,不代表起始词不在词典中,要处理这种不在的情况
     *      2) 构建邻接关系时候,因为满足条件的结果中肯定包含自己(因为是用*替换的,包含原本字符的情况),这个要排除
     */
    public int ladderLength(String beginWord, String endWord, HashSet<String> dict) {
        
        int n = dict.size();
        if(!dict.contains(endWord)) return 0;
        
        // 方便遍历操作,初始元素如果不在词典中要放入词典(题目是说初始元素不能作为接龙元素)
        dict.add(beginWord);
        Map<String, Graph> created = new HashMap<>(n+1);
        Map<String, List<Graph>> match = new HashMap<>(n+1);
        
        Graph start = new Graph(beginWord, new ArrayList<>());
        created.put(beginWord, start);
        Graph end = new Graph(endWord, new ArrayList<>());
        created.put(endWord, end);
        
        // 创建图的顶点以及保存按照每一位字符擦出的特征信息O(nm)
        for(String word:dict){
            if(!created.containsKey(word)){
                created.put(word, new Graph(word, new ArrayList<>()));
            }
            Graph vertex = created.get(word);
            char[] chars = new char[word.length()];
            word.getChars(0, word.length(), chars, 0);
            for(int i=0; i < chars.length; i++){
                char tmp = chars[i];
                chars[i] = '*';
                String m = new String(chars);
                if(!match.containsKey(m)){
                    match.put(m, new ArrayList<>());
                }
                match.get(m).add(vertex);
                chars[i] = tmp;
            }
        }   
        // 保存邻接信息O(n^2),将之前遍历得到的所有只差一个字符的非本身元素保存到nexts中去
        for(String word:dict){
            Graph vertex = created.get(word);
            char[] chars = new char[word.length()];
            word.getChars(0, word.length(), chars, 0);
            for(int i=0; i < chars.length; i++){
                char tmp = chars[i];
                chars[i] = '*';
                List<Graph> mlist = match.get(new String(chars));
                chars[i] = tmp;
                // 接龙元素不能重复使用,不能自己指向自己
                for(Graph next:mlist){
                    if(next!=vertex){
                        vertex.nexts.add(next);
                    }
                }
            }
        }
        // 通过BFS找到从start到end的最短路径
        int min = 0;
        Set<Graph> searched = new HashSet<>(n+1);
        List<Graph> path = new ArrayList<>(n+1);
        Queue<List<Graph>> pathes = new LinkedList<>();
        path.add(start);
        pathes.add(path);
        searched.add(start);
        // 队列pathes中保存的需要继续遍历(还有可以后续顶点可以遍历)的待遍历路径
        while(!pathes.isEmpty()){
            List<Graph> curPath = pathes.remove();
            Graph last = curPath.get(curPath.size()-1);
            if(last == end){
                // min=0时代表无解
                min = min==0 ? curPath.size() : Math.min(min, curPath.size());
                continue;
            }
            for(Graph next:last.nexts){
                // 没有后续的未遍历顶点跳过
                if(!searched.contains(next)){
                    List<Graph> nextPath = new ArrayList<>(curPath);
                    nextPath.add(next);
                    pathes.add(nextPath);
                    searched.add(next);
                }
            }
        }
        return min;        
    }
}


编辑于 2020-01-06 17:11:31 回复(0)
import java.util.*;

public class Solution {
    public static int ladderLength(String start, String end, HashSet<String> dict) {
        ArrayList<String> dictList = new ArrayList<>(dict);
        dictList.add(0, end);//0
        dictList.add(start);//len-1
        int len = dictList.size();
        boolean[][] isLinked = new boolean[len][len];
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                isLinked[i][j] = isLinked(dictList.get(i), dictList.get(j));
                isLinked[j][i] = isLinked[i][j];
            }
        }
        int[] ladderLen = new int[len];
        Queue<Integer> queue = new LinkedList<>();
        queue.add(0);
        ladderLen[0] = 1;
        label:
        while (!queue.isEmpty()) {
            int from = queue.poll();//Integer
            for (int i = 1; i < len; i++) {
                if (isLinked[from][i] && ladderLen[i] == 0) {//-1
                    queue.add(i);
                    ladderLen[i] = ladderLen[from] + 1;
                    if (i == len - 1) {
                        break label;
                    }
                }
            }
        }
        return ladderLen[len - 1];
    }
    
    //a,b长度相等
    public static boolean isLinked(String a, String b) {
        int diffNum = 0;
        for (int i = 0; i < a.length(); i++) {
            if (a.charAt(i) != b.charAt(i)) {
                diffNum++;
                if (diffNum > 1) {
                    return false;
                }
            }
        }
        return diffNum == 1;
    }

}

发表于 2018-05-05 10:44:16 回复(0)
//使用bfs(广度优先搜素),其中res用于记录结果,size用于记录每一层的元素个数,遍历完一层res++
//注意遍历过的要从字典中删除。

public int ladderLength(String start, String end, HashSet<String> dict) {  
      int res=1;
      LinkedList<String> queue=new LinkedList<>();
      queue.offer(start);
      while(!queue.isEmpty())
      {
    	  int size=queue.size();
    	  while(size>0)
    	  {
    		  String s=queue.poll();
    		  size--;
    		  
    		  if(isDiffOne(s, end))
    			  return res+1;
    		  for(Iterator<String> it=dict.iterator();it.hasNext();)
    		  {
    			  String str=it.next();
    			  if(isDiffOne(str, s))
    			  {
    				  queue.offer(str);
    				  it.remove();
    			  }
    				  
    		  }
    	  }
    	  res++;
      }
      return 0;
    }  
  
    public boolean isDiffOne(String w1, String w2) {  
       int count = 0;          
       for(int i = 0; i < w1.length(); i++) {  
           if(w1.charAt(i) != w2.charAt(i)) {  
               count++;  
           }  
       }
       return count==1?true:false;
    }

发表于 2017-04-18 22:35:09 回复(3)
sdfs sdfsd


发表于 2017-04-01 21:04:01 回复(0)
public class Solution { public int ladderLength(String beginWord, String endWord, Set<String> wordList) {
	Set<String> beginSet = new HashSet<String>(), endSet = new HashSet<String>(); int len = 1; int strLen = beginWord.length();
	HashSet<String> visited = new HashSet<String>();
	
	beginSet.add(beginWord);
	endSet.add(endWord); while (!beginSet.isEmpty() && !endSet.isEmpty()) { if (beginSet.size() > endSet.size()) {
			Set<String> set = beginSet;
			beginSet = endSet;
			endSet = set;
		}

		Set<String> temp = new HashSet<String>(); for (String word : beginSet) { char[] chs = word.toCharArray(); for (int i = 0; i < chs.length; i++) { for (char c = 'a'; c <= 'z'; c++) { char old = chs[i];
					chs[i] = c; String target = String.valueOf(chs); if (endSet.contains(target)) { return len + 1;
					} if (!visited.contains(target) && wordList.contains(target)) {
						temp.add(target);
						visited.add(target);
					}
					chs[i] = old;
				}
			}
		}

		beginSet = temp;
		len++;
	} return 0;
}
}

发表于 2017-03-11 23:14:37 回复(0)