Given two words (start and end), and a dictionary, find the length of shortest transformation sequence from start to end, such that:
- Only one letter can be changed at a time
- Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog"
,
return its length 5
.
Note:
- Return 0 if there is no such transformation sequence.
- All words have the same length.
- All words contain only lowercase alphabetic characters.
public class Solution {
public int ladderLength(String start, String end, Set<String> dict) {//字典中可能含有start和end
//dict.add(start);
//dict.add(end);
String[] arr = (String[])dict.toArray(new String[0]); //将dict变成数组,方便记录每一个字典项转换成其他字典项需要的转换次数
int[][] c = new int[arr.length][arr.length];//相当于邻接矩阵
for(int i = 0 ; i < arr.length ; i ++){//构建图的邻接矩阵
for(int j = i + 1 ; j < arr.length ; j ++){
c[i][j] = count(arr[i],arr[j]);
c[j][i] = c[i][j];//对称
}
}
Set<Integer> set = new HashSet<Integer>();//存储到end的转换次数为1的节点
for(int i = 0; i < arr.length ; i ++){
if(count(arr[i],end) == 1 || end.equals(arr[i])){//相等的也要纳入其中
set.add(i);
}
}
//利用邻接矩阵找出两个点之间的最短通路
int len = 0;
int level = 0;
Queue<Integer> queue = new LinkedList<Integer>();
int[] visited = new int[arr.length];
for(int i = 0 ; i < arr.length ; i ++){
if(count(start,arr[i]) == 1 || start.equals(arr[i])){
queue.offer(i);
visited[i] = 1;
level++;
}
}
//visited[startIndex] = 1;
while(queue.size() > 0){
int count = 0;
int nextLevel = 0;//下一层的元素数
len ++;
while(count < level){//第len层全部出队
int tmp = queue.poll();
if(set.contains(tmp)){
return len + 2;
}
for(int i = 0 ; i < arr.length ; i ++){
if(visited[i] != 1 && c[tmp][i] == 1){
queue.offer(i);
visited[i] = 1;
nextLevel++;
}
}
count ++;
}
level = nextLevel;
}
return 0;
}
public int count(String s1, String s2){//字符串之间是否是一次交换
int count = 0;
for(int i = 0 ; i < s1.length(); i++){
if(s1.charAt(i) != s2.charAt(i)){
count++;
if(count > 1){
break;
}
}
}
if(count == 1){//可以直达,
return 1;
}else{//不可直达
return 0;
}
}
}
时间超时,查找原因,根据网上的说法,不是BFS的问题,而是构建图矩阵的时候(用时O(n^2))。。。。
继续度娘...........
有大神提出了另一种方案,不需要构造邻接矩阵,思路就是将字符串中的每一个位置用‘a’~‘z’都都换一次,然后在查看这个字符串是否在字典里,这样就等于找到了所有字典中与原字符串相差为1的字符串。然后就是利用BFS的思想搜索。
public class Solution {
public int ladderLength(String start, String end, HashSet<String> dict) {
if (start == null || end == null || start.equals(end))
return 0;
if (isOneWordDiff(start, end))
return 2;
Queue<String> queue=new LinkedList<String>();//访问队列
Set<String> visited = new HashSet<String>();//表示字典里的对应key访问过了
int level = 0;//某一层的元素数
int len = 0;
//将能够进行一次转换就到字典里的string加到队列,也就是离start最近的
StringBuilder sb = new StringBuilder(start);//重复修改字符串并且是单线程使用StringBuilder
for(int i = 0 ; i < start.length(); i++){
char tmp = sb.charAt(i);
for(char j = 'a' ; j <= 'z' ; j ++){
if(j == tmp){
continue;
}else{
sb.setCharAt(i,j);
if(dict.contains(sb.toString())){
queue.offer(sb.toString());
level ++;
visited.add(sb.toString());
}
}
}
sb.setCharAt(i,tmp);
}
while(!queue.isEmpty())
{
len++;
int count = 0;
int nextLevel = 0;
while(count < level){//控制出队的个数
String tmp = queue.poll();
if(isOneWordDiff(tmp,end)){
return len + 2;
}else{
sb = new StringBuilder(tmp);
for(int i = 0 ; i < tmp.length(); i++){
char c = sb.charAt(i);
for(char j = 'a' ; j <= 'z' ; j ++){
if(j == c){
continue;
}else{
sb.setCharAt(i,j);
if(dict.contains(sb.toString()) && !visited.contains(sb.toString())){
queue.offer(sb.toString());
nextLevel ++;
visited.add(sb.toString());
}
}
}
sb.setCharAt(i,c);
}
}
count++;
}
level = nextLevel;
}
return 0;
}
private boolean isOneWordDiff(String a, String b) {
int diff = 0;
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) != b.charAt(i)) {
diff++;
if (diff >= 2)
break;
}
}
return diff == 1;
}
}
Runtime: 754 ms
继续精简代码,在上一个算法,并没有将start和end加入字典,而且字典中有跟start和end相同的字符串出现,而字典中的这些start和end是没有作用的,白白浪费了26*length*2的时间。这次将start和end都加入字典中。
public class Solution {
public int ladderLength(String start, String end, HashSet<String> dict) {
if (start == null || end == null)
return 0;
if (isOneWordDiff(start, end))
return 2;
dict.add(start);//即使字典已经包含start和end也无所谓
dict.add(end);
Queue<String> queue=new LinkedList<String>();//访问队列
Set<String> visited = new HashSet<String>();//表示字典里的对应key访问过了
StringBuilder sb = new StringBuilder(start);//重复修改字符串并且是单线程使用StringBuilder
int level = 0;//某一层的元素数
int len = 0;
queue.offer(start);
level++;
visited.add(start);
while(!queue.isEmpty())
{
len++;
int count = 0;
int nextLevel = 0;
while(count < level){//控制出队的个数
String tmp = queue.poll();
if(tmp.equals(end)){
return len;
}else{
//将能够进行一次转换就到字典里的string加到队列,也就是离tmp最近的
sb = new StringBuilder(tmp);//重复修改字符串并且是单线程使用StringBuilder
for(int i = 0 ; i < tmp.length(); i++){
char c = sb.charAt(i);
for(char j = 'a' ; j <= 'z' ; j ++){
if(j == c){
continue;
}else{
sb.setCharAt(i,j);
if(dict.contains(sb.toString()) && !visited.contains(sb.toString())){
queue.offer(sb.toString());
nextLevel ++;
visited.add(sb.toString());
}
}
}
sb.setCharAt(i,c);
}
}
count++;
}
level = nextLevel;
}
return 0;
}
private boolean isOneWordDiff(String a, String b) {
int diff = 0;
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) != b.charAt(i)) {
diff++;
if (diff >= 2)
break;
}
}
return diff == 1;
}
}
Runtime: 662 ms
终于完成了,用了一天的时间,想法花费的时间还行,就是实现起来,各种问题接踵而来,最无奈的就是跟字符串打交道,太麻烦了