A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© cat73 黑马帝   /  2016-7-14 18:43  /  524 人查看  /  10 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 cat73 于 2016-7-14 19:05 编辑

给你两个单词一个作为开头一个作为结尾。
再给你一组单词作为中间单词。
请找到最短的从开头到结尾的转换路径,如果有多个路径均为最短,则均应该输出。

限制如下:
每次转换只允许变动一个字母。
只允许用中间单词里的单词来转换。

例子:
  1. // 参数
  2. String beginWord = "hit";
  3. String endWord = "cog";
  4. String[] wordList = new String[] {"hot", "dot", "dog", "lot", "log"};

  5. // 返回值:
  6. return new String[][] {
  7.     {"hit","hot","dot","dog","cog"},
  8.     {"hit","hot","lot","log","cog"}
  9. };
复制代码

参数与返回值的表达形式不强制,你觉得怎么舒服这么来,重要的是算法。

10 个回复

倒序浏览
cat73 黑马帝 2016-7-14 20:23:40
沙发
这道题的原题在这里:https://leetcode.com/problems/word-ladder-ii/
这是一个效率极低的解法,这网站的测试里居然有 599 个中间单词,这种算法直接炸,5 分钟也跑不完。。。
而这个网站好像不到 10 秒就报超时错误了。。。
  1. public class Solution {
  2.     public List<List<String>> findLadders(String beginWord, String endWord, Set<String> wordList) {
  3.         // 结果
  4.         List<List<String>> results = new ArrayList<>();
  5.         
  6.         // 没有中间单词就可以直接转的情况
  7.         if(hasConversion(beginWord, endWord)) {
  8.             List<String> result = new ArrayList<>();
  9.             result.add(beginWord);
  10.             result.add(endWord);
  11.             results.add(result);
  12.             return results;
  13.         }
  14.         
  15.         // 中间词 Set 转为数组
  16.         String[] wordArray = wordList.toArray(new String[] {});
  17.         
  18.         // 按照中间单词数循环,如果在某个长度找到了可以转换的路径,则不再尝试下一个长度
  19.         for(int len = 1; len <= wordList.size() && results.size() == 0; len++) {
  20.             // 数字迭代器
  21.             Iterator<Integer[]> it = new NumberIterator(0, wordArray.length - 1, len);
  22.             
  23.             loop: while (it.hasNext()) {
  24.                 // 获取下一个数字
  25.                 final Integer[] num = it.next();
  26.                
  27.                 // 数字不能重复
  28.                 for (int i = 0; i < num.length; i++) {
  29.                     for (int j = 0; j < num.length; j++) {
  30.                         if (i != j && num[i] == num[j]) {
  31.                             continue loop;
  32.                         }
  33.                     }
  34.                 }
  35.                
  36.                 // 判断头到第一个中间词能否转换
  37.                 if(!hasConversion(beginWord, wordArray[num[0]])) {
  38.                     continue loop;
  39.                 }
  40.                
  41.                 // 判断两个中间词之间能否转换
  42.                 for (int i = num.length - 1; i > 0; i--) {
  43.                     if(!hasConversion(wordArray[num[i]], wordArray[num[i - 1]])) {
  44.                         continue loop;
  45.                     }
  46.                 }
  47.                
  48.                 // 判断最后一个中间词到尾能否转换
  49.                 if(!hasConversion(wordArray[num[num.length - 1]], endWord)) {
  50.                     continue loop;
  51.                 }
  52.                
  53.                 // 保存结果
  54.                 List<String> result = new ArrayList<>();
  55.                 result.add(beginWord);
  56.                
  57.                 for (int i = 0; i < num.length; i++) {
  58.                     result.add(wordArray[num[i]]);
  59.                 }
  60.                
  61.                 result.add(endWord);
  62.                 results.add(result);
  63.             }
  64.         }
  65.         
  66.         return results;
  67.     }
  68.    
  69.     private boolean hasConversion(String formWord, String toWord) {
  70.         for(int i = 0, difference = 0; i < formWord.length(); i++) {
  71.             if(formWord.charAt(i) != toWord.charAt(i)) {
  72.                 difference += 1;
  73.                 if(difference > 1) {
  74.                     return false;
  75.                 }
  76.             }
  77.         }
  78.         return true;
  79.     }
  80.    
  81.    
  82.     /**
  83.      * 一个数字的迭代器<br>
  84.      * 可通过 next 返回一个 Integer 数组,每一个元素代表在这一位上的数字,返回值是大端序的<br>
  85.      * 此实现不是多线程安全的<br>
  86.      * 最大可描述不大于 2 ^ 32 进制、不长于 Integer.MAX_VALUE 位的所有数字
  87.      *
  88.      * @author Cat73
  89.      */
  90.     public class NumberIterator implements Iterator<Integer[]> {
  91.         private final int min;
  92.         private final int max;
  93.         private final int length;
  94.         private boolean hasNext = true;
  95.         private final Integer[] number;
  96.    
  97.         /**
  98.          * 构造一个新的数字迭代器
  99.          *
  100.          * @param min 每个位上的数字最小是多少?
  101.          * @param max 每个位上的数字最大是多少?
  102.          * @param length 这个数字有多长?
  103.          * @throws RuntimeException 如果 min > max
  104.          */
  105.         public NumberIterator(final int min, final int max, final int length) {
  106.             // 输入检查
  107.             if (min > max) {
  108.                 throw new RuntimeException("max: " + max + ", min: " + min);
  109.             } else if (length < 1) {
  110.                 this.hasNext = false;
  111.             }
  112.    
  113.             // 初始化属性
  114.             this.min = min;
  115.             this.max = max;
  116.             this.length = length;
  117.             this.number = new Integer[this.length];
  118.    
  119.             // 初始化值到最小值
  120.             for (int i = 0; i < this.length; i++) {
  121.                 this.number[i] = this.min;
  122.             }
  123.         }
  124.    
  125.         @Override
  126.         public Integer[] next() {
  127.             // 如果没有下一个元素则抛出异常
  128.             if (!this.hasNext) {
  129.                 throw new java.util.NoSuchElementException();
  130.             }
  131.    
  132.             // 将当前数值当做结果
  133.             final Integer[] result = Arrays.copyOf(this.number, this.length);
  134.    
  135.             // 将数字加 1
  136.             this.number[0] += 1;
  137.             for (int i = 0; i < this.length && this.number[i] > this.max; i++) {
  138.                 // 如果无法再进位则视为没有下一个元素
  139.                 if (i + 1 >= this.length) {
  140.                     this.hasNext = false;
  141.                 } else {
  142.                     this.number[i] = this.min;
  143.                     this.number[i + 1] += 1;
  144.                 }
  145.             }
  146.    
  147.             // 返回当前元素
  148.             return result;
  149.         }
  150.    
  151.         @Override
  152.         public boolean hasNext() {
  153.             return this.hasNext;
  154.         }
  155.     }
  156. }
复制代码


回复 使用道具 举报
cat73 黑马帝 2016-7-14 21:58:27
藤椅
首先想到的就是这种遍历了,结果时间太长。
然后想到多叉树展开的方案,结果内存开销太大,时间还是太长,有大量重复的部分而且很难剪枝。。
我再想想别的方案。。。
回复 使用道具 举报
cat73 黑马帝 2016-7-14 23:00:59
板凳
599 个 3 个字母的单词的过了,分享下代码(请自行删除 Timing 相关的代码):
接下来是 4565 个 5 个字母的单词。。。
在我的本本上 850ms 跑完。。。
然而还是超时。。。

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.HashSet;
  4. import java.util.List;
  5. import java.util.Map;
  6. import java.util.Set;

  7. public class WordLadderII {
  8.     /**
  9.      * 判断两个单词之间能否进行转换
  10.      *
  11.      * @param formWord 源单词
  12.      * @param toWord 目标单词
  13.      * @return 能否进行转换
  14.      */
  15.     private boolean hasConversion(final String formWord, final String toWord) {
  16.         int difference = 0;
  17.         for (int i = 0; i < formWord.length(); i++) {
  18.             if (formWord.charAt(i) != toWord.charAt(i)) {
  19.                 difference += 1;
  20.                 if (difference > 1) {
  21.                     return false;
  22.                 }
  23.             }
  24.         }
  25.         return difference == 1;
  26.     }

  27.     /**
  28.      * 寻找最短的转换路径
  29.      *
  30.      * @param beginWord 开头单词
  31.      * @param endWord 结尾单词
  32.      * @param wordList 中间单词
  33.      * @return 最短路径列表
  34.      */
  35.     public List<List<String>> findLadders(final String beginWord, final String endWord, final Set<String> wordList) {
  36.         // 结果
  37.         final List<List<String>> results = new ArrayList<>();

  38.         // 初始化 Cache
  39.         Main.cacheTiming.startTiming();
  40.         // 单词转换关系 Cache
  41.         final Map<String, Set<String>> conversionCaches = new HashMap<>();
  42.         // 添加所有单词(含首尾单词)
  43.         conversionCaches.put(beginWord, new HashSet<>());
  44.         conversionCaches.put(endWord, new HashSet<>());
  45.         for (final String word : wordList) {
  46.             conversionCaches.put(word, new HashSet<>());
  47.         }
  48.         // 初始化每个单词可以转换到的单词列表
  49.         for (final String key1 : conversionCaches.keySet()) {
  50.             final Set<String> conversionCache = conversionCaches.get(key1);
  51.             for (final String key2 : conversionCaches.keySet()) {
  52.                 if (this.hasConversion(key1, key2)) {
  53.                     conversionCache.add(key2);
  54.                 }
  55.             }
  56.         }
  57.         Main.cacheTiming.stopTiming();

  58.         // 核心算法
  59.         Main.algorithmTiming.startTiming();
  60.         // 上一层的节点列表
  61.         List<TreeNode<String>> lastNodes = new ArrayList<>();
  62.         lastNodes.add(new TreeNode<>(beginWord));
  63.         // 已经遍历过的单词列表(用于去除循环引用)
  64.         Set<String> lastKeys = new HashSet<>(conversionCaches.size());
  65.         lastKeys.add(beginWord);
  66.         
  67.         // 按照中间单词数循环,如果在某个长度找到了可以转换的路径,则不再尝试下一个长度
  68.         for (int len = 1; len <= wordList.size() + 1 && results.size() == 0; len++) {
  69.             // 当前层节点列表
  70.             List<TreeNode<String>> currentNodes = new ArrayList<>(conversionCaches.size() - lastKeys.size());
  71.             // 当前层使用过的单词列表
  72.             Set<String> currentKeys = new HashSet<>(conversionCaches.size() - lastKeys.size());
  73.             
  74.             // 展开上一层树
  75.             for(TreeNode<String> node : lastNodes) {
  76.                 // 从缓存中读取当前节点的子节点列表
  77.                 for(String word : conversionCaches.get(node.getValue())) {
  78.                     // 如果为结果则加入结果列表
  79.                     if(word.equals(endWord)) {
  80.                         List<String> result = new ArrayList<>();
  81.                         result.add(endWord);
  82.                         
  83.                         TreeNode<String> current = node;
  84.                         do {
  85.                             result.add(current.getValue());
  86.                         } while((current = current.getParentNode()) != null);
  87.                         
  88.                         Collections.reverse(result);

  89.                         results.add(result);
  90.                     } else {
  91.                         // 如果是未使用过的单词则加入节点列表
  92.                         if(!lastKeys.contains(word)) {
  93.                             currentNodes.add(node.addChildNode(new TreeNode<>(word)));
  94.                             currentKeys.add(word);
  95.                         }
  96.                     }
  97.                 }
  98.             }
  99.             
  100.             // 更新上一层节点与使用过的单词的数据
  101.             lastNodes = currentNodes;
  102.             lastKeys.addAll(currentKeys);
  103.         }
  104.         Main.algorithmTiming.stopTiming();

  105.         // 返回结果
  106.         return results;
  107.     }
  108.    
  109.     /**
  110.      * 多叉树节点<br>
  111.      * 一个节点不应该同时属于多个父节点<br>
  112.      * getParentNode 仅返回最后一次被添加到的父节点<br>
  113.      *
  114.      * @author cat73
  115.      */
  116.     public class TreeNode<T> {
  117.         /**
  118.          * 当前节点的值
  119.          */
  120.         private final T value;
  121.         /**
  122.          * 父节点
  123.          */
  124.         private TreeNode<T> parentNode;
  125.         /**
  126.          * 子节点列表
  127.          */
  128.         private List<TreeNode<T>> childNodes;

  129.         /**
  130.          * 构造一个多叉树节点
  131.          *
  132.          * @param value 当前节点的值
  133.          */
  134.         public TreeNode(final T value) {
  135.             this.value = value;
  136.         }

  137.         /**
  138.          * 获取当前节点的值
  139.          *
  140.          * @return 当前节点的值
  141.          */
  142.         public T getValue() {
  143.             return this.value;
  144.         }

  145.         /**
  146.          * 获取父节点
  147.          *
  148.          * @return parentNode 当前节点所属的父节点
  149.          */
  150.         public TreeNode<T> getParentNode() {
  151.             return this.parentNode;
  152.         }

  153.         /**
  154.          * 添加子节点
  155.          *
  156.          * @param childNode 要被添加的子节点
  157.          */
  158.         public TreeNode<T> addChildNode(final TreeNode<T> childNode) {
  159.             if(this.childNodes == null) {
  160.                 this.childNodes = new ArrayList<>();
  161.             }
  162.             this.childNodes.add(childNode);
  163.             childNode.parentNode = this;
  164.             
  165.             return childNode;
  166.         }
  167.     }
  168. }
复制代码


回复 使用道具 举报
cat73 黑马帝 2016-7-15 10:29:46
报纸
本帖最后由 cat73 于 2016-7-15 11:33 编辑

重新思考了一下,只要一个节点允许多个父节点,那么缓存根本就没必要,而且可以砍掉一大半转换。
结果只要从 END 节点向前递归就可以了。
这算法 4565 个中间单词在我的电脑上展开树需要 400ms。。。
之前的算法创建缓存 800ms+,展开树 50ms。。。

回复 使用道具 举报
cat73 黑马帝 2016-7-15 12:09:09
地板
5 个字符长度的单词,4565 个中间单词的测试过了,在我的电脑上大概 400ms 左右,分享下代码:
  1. 中间单词数: 4565

  2. 总耗时: 387.973248ms
  3. 展开树耗时: 386.685609ms
  4. 拼接结果耗时: 0.390396ms
  5. 临时测试代码算法耗时: 0.0ms
  6. 临时测试计数器: 0

  7. [nanny, canny, candy, sandy, sands, sends, seeds, sleds, slews, clews, clefs, cleft, cleat, bleat, bloat, float, flout, clout, cloud, aloud]
  8. [nanny, danny, dandy, sandy, sands, sends, seeds, sleds, slews, clews, clefs, cleft, cleat, bleat, bloat, float, flout, clout, cloud, aloud]

  9. end
复制代码
  1. public class Solution {
  2.     private List<List<String>> results;
  3.     private List<String> result;
  4.    
  5.     /**
  6.      * 寻找最短的转换路径
  7.      *
  8.      * @param beginWord 开头单词
  9.      * @param endWord 结尾单词
  10.      * @param wordList 中间单词列表
  11.      * @return 最短路径列表
  12.      */
  13.     public List<List<String>> findLadders(final String beginWord, final String endWord, final Set<String> wordList) {
  14.         // 结果
  15.         this.results = new ArrayList<>();

  16.         //Main.expandTreeTiming.startTiming();
  17.         // 最大展开次数
  18.         int maxLen = wordList.size() + 1;
  19.         // 移除与开头单词重复的中间单词
  20.         wordList.remove(beginWord);
  21.         // 将结尾单词添加到单词列表
  22.         wordList.add(endWord);
  23.         
  24.         // 上一级节点列表
  25.         Map<String, TreeNode<String>> lastNodes = new HashMap<>(1);
  26.         // 将首个单词加入
  27.         lastNodes.put(beginWord, new TreeNode<>(beginWord));

  28.         // 按照中间单词数循环,如果在某个长度找到了可以转换的路径,则不再尝试下一个长度
  29.         int len = 1;
  30.         while(!lastNodes.containsKey(endWord) && len <= maxLen) {
  31.             // 当前级节点列表
  32.             Map<String, TreeNode<String>> currentNodes = new HashMap<>();

  33.             // 展开上一级树
  34.             for (final TreeNode<String> node : lastNodes.values()) {
  35.                 String word = node.getValue();
  36.                 // 遍历未使用过的单词
  37.                 for (final String key : wordList) {
  38.                     // 如果两个单词可以互相转换
  39.                     if (this.hasConversion(word, key)) {
  40.                         // 如果已经存在这个单词,则在其所属节点增加一个父节点,否则新建一个子节点
  41.                         if(currentNodes.containsKey(key)) {
  42.                             node.addChildNode(currentNodes.get(key));
  43.                         } else {
  44.                             TreeNode<String> keyNode = new TreeNode<>(key);
  45.                             node.addChildNode(keyNode);
  46.                             currentNodes.put(key, keyNode);
  47.                         }
  48.                     }
  49.                 }
  50.             }

  51.             // 用当前级节点列表覆盖上一级节点列表
  52.             lastNodes = currentNodes;
  53.             // 移除已使用过的中间单词
  54.             wordList.removeAll(currentNodes.keySet());
  55.             
  56.             // 继续搜寻更长的路径
  57.             len += 1;
  58.         }
  59.        // Main.expandTreeTiming.stopTiming();

  60.        // Main.resultTiming.startTiming();
  61.         TreeNode<String> endNode = lastNodes.get(endWord);
  62.         if(endNode != null) {
  63.             this.result = new ArrayList<>(len);
  64.             this.result.add(endWord);
  65.             for(int i = 1; i < len; i++) {
  66.                 this.result.add(null);
  67.             }
  68.             this.addResults(endNode.getParentNodes().iterator(), 1);
  69.         }
  70.        // Main.resultTiming.stopTiming();

  71.         // 返回结果
  72.         return this.results;
  73.     }
  74.    
  75.     private void addResults(Iterator<TreeNode<String>> it, int index) {
  76.         while(it.hasNext()) {
  77.             TreeNode<String> node = it.next();
  78.             this.result.set(index, node.getValue());
  79.             
  80.             List<TreeNode<String>> parentList = node.getParentNodes();
  81.             if(parentList.isEmpty()) {
  82.                 List<String> result = new ArrayList<>(this.result);
  83.                 Collections.reverse(result);
  84.                 this.results.add(result);
  85.             } else {
  86.                 addResults(parentList.iterator(), index + 1);
  87.             }
  88.         }
  89.     }
  90.    
  91.     /**
  92.      * 判断两个单词之间能否进行转换
  93.      *
  94.      * @param formWord 源单词
  95.      * @param toWord 目标单词
  96.      * @return 能否进行转换
  97.      */
  98.     private boolean hasConversion(final String formWord, final String toWord) {
  99.         for (int i = formWord.length() - 1, difference = 0; i >= 0; i--) {
  100.             if (formWord.charAt(i) != toWord.charAt(i)) {
  101.                 difference += 1;
  102.                 if (difference > 1) {
  103.                     return false;
  104.                 }
  105.             }
  106.         }
  107.         return true;
  108.     }

  109. // =============

  110. /**
  111. * 多叉树节点<br>
  112. *
  113. * @author cat73
  114. */
  115. public class TreeNode<T> {
  116.     /**
  117.      * 当前节点的值
  118.      */
  119.     private final T value;
  120.     /**
  121.      * 父节点列表
  122.      */
  123.     private List<TreeNode<T>> parentNode;
  124.     /**
  125.      * 子节点列表
  126.      */
  127.     private List<TreeNode<T>> childNodes;

  128.     /**
  129.      * 构造一个多叉树节点
  130.      *
  131.      * @param value 当前节点的值
  132.      */
  133.     public TreeNode(final T value) {
  134.         this.value = value;
  135.     }

  136.     /**
  137.      * 获取当前节点的值
  138.      *
  139.      * @return 当前节点的值
  140.      */
  141.     public T getValue() {
  142.         return this.value;
  143.     }

  144.     /**
  145.      * 获取父节点列表
  146.      *
  147.      * @return parentNode 当前节点所属的父节点列表
  148.      */
  149.     public List<TreeNode<T>> getParentNodes() {
  150.         if (this.parentNode == null) {
  151.             this.parentNode = new ArrayList<>();
  152.         }
  153.         return this.parentNode;
  154.     }
  155.    
  156.     /**
  157.      * 添加一个父节点
  158.      *
  159.      * @param childNode 要被添加的父节点
  160.      */
  161.     private void addParentNode(TreeNode<T> parentNode) {
  162.         if (this.parentNode == null) {
  163.             this.parentNode = new ArrayList<>();
  164.         }
  165.         this.parentNode.add(parentNode);
  166.     }

  167.     /**
  168.      * 获取子节点列表
  169.      *
  170.      * @return parentNode 当前节点所属的父节点列表
  171.      */
  172.     public List<TreeNode<T>> getChildNodes() {
  173.         if (this.childNodes == null) {
  174.             this.childNodes = new ArrayList<>();
  175.         }
  176.         return this.childNodes;
  177.     }
  178.    
  179.     /**
  180.      * 添加子节点(会同时在子节点增加父节点)
  181.      *
  182.      * @param childNode 要被添加的子节点
  183.      */
  184.     public void addChildNode(final TreeNode<T> childNode) {
  185.         if (this.childNodes == null) {
  186.             this.childNodes = new ArrayList<>();
  187.         }
  188.         this.childNodes.add(childNode);
  189.         childNode.addParentNode(this);
  190.     }
  191. }
  192. }
复制代码
回复 使用道具 举报
cat73 黑马帝 2016-7-15 12:10:21
7#
本帖最后由 cat73 于 2016-7-15 12:29 编辑

接下来是 6 个字符长,2623 个中间单词的测试,在我的电脑上只需要 150ms 左右,然而还是报超时错误。。
看来是时间限制更严格了。。。
我大概是理解为什么这道题这么少人过了。。。
=====
加了点代码去测试它的服务器性能。
在我的本本上 400ms 的算法,在它的服务器上:905.258371ms
回复 使用道具 举报
cat73 黑马帝 2016-7-16 05:59:14
8#
终于通过了,真不容易。。。
之前的算法在我的电脑 150ms,现在只要 50ms。。。

然而效率仍然不是最高的。。。
分享下代码:
  1. public class Solution {
  2.     // 为了递归拼接结果的时候少传俩参数才声明的全局变量
  3.     private final List<List<String>> results = new ArrayList<>();
  4.     private List<String> result;

  5.     /**
  6.      * 寻找最短的转换路径
  7.      *
  8.      * @param beginWord 开头单词
  9.      * @param endWord 结尾单词
  10.      * @param wordList 中间单词列表
  11.      * @return 最短路径列表
  12.      */
  13.     public List<List<String>> findLadders(final String beginWord, final String endWord, final Set<String> wordList) {
  14.         // Main.expandTreeTiming.startTiming();
  15.         // 最大展开次数
  16.         final int maxLen = wordList.size() + 1;
  17.         // 移除与开头单词重复的中间单词
  18.         wordList.remove(beginWord);
  19.         // 将结尾单词添加到单词列表
  20.         wordList.add(endWord);

  21.         // 上一级节点列表
  22.         Map<String, TreeNode<String>> lastNodes = new HashMap<>(1);
  23.         // 将首个单词加入
  24.         lastNodes.put(beginWord, new TreeNode<>(beginWord));

  25.         // 按照中间单词数循环,如果在某个长度找到了可以转换的路径,则不再尝试下一个长度
  26.         int len = 1;
  27.         while (!lastNodes.containsKey(endWord) && len <= maxLen) {
  28.             // 当前级节点列表
  29.             final Map<String, TreeNode<String>> currentNodes = new HashMap<>(maxLen - lastNodes.size());

  30.             // 展开上一级树
  31.             for (final TreeNode<String> node : lastNodes.values()) {
  32.                 final char[] chs = node.getValue().toCharArray();

  33.                 // 遍历可以转换到的单词
  34.                 for (int i = 0; i < chs.length; i++) {
  35.                     String key;
  36.                     final char bakCh = chs[i];
  37.                     for (char ch = 'a'; ch <= 'z'; ch++) {
  38.                         if (ch != bakCh) {
  39.                             chs[i] = ch;
  40.                             key = new String(chs);
  41.                             // 找到一个单词
  42.                             if (wordList.contains(key)) {
  43.                                 // 如果已经存在这个单词,则在其所属节点增加一个父节点,否则新建一个子节点
  44.                                 if (currentNodes.containsKey(key)) {
  45.                                     node.addChildNode(currentNodes.get(key));
  46.                                 } else {
  47.                                     final TreeNode<String> keyNode = new TreeNode<>(key);
  48.                                     node.addChildNode(keyNode);
  49.                                     currentNodes.put(key, keyNode);
  50.                                 }
  51.                             }
  52.                         }
  53.                     }
  54.                     chs[i] = bakCh;
  55.                 }
  56.             }

  57.             // 用当前级节点列表覆盖上一级节点列表
  58.             lastNodes = currentNodes;
  59.             // 移除已使用过的中间单词
  60.             wordList.removeAll(currentNodes.keySet());

  61.             // 继续搜寻更长的路径
  62.             len += 1;
  63.         }
  64.         // Main.expandTreeTiming.stopTiming();

  65.         // Main.resultTiming.startTiming();
  66.         // 拼接结果
  67.         // 获取结尾单词所属的节点
  68.         final TreeNode<String> endNode = lastNodes.get(endWord);
  69.         // 如果存在该节点则开始递归拼接结果
  70.         if (endNode != null) {
  71.             // 准备临时保存结果的容器,并初始化所有元素为 null
  72.             this.result = new ArrayList<>(len);
  73.             for (int i = 0; i < len; i++) {
  74.                 this.result.add(null);
  75.             }
  76.             // 调用递归拼接结果
  77.             this.addResults(endNode, len - 1);
  78.         }
  79.         // Main.resultTiming.stopTiming();

  80.         // 返回结果
  81.         return this.results;
  82.     }

  83.     /**
  84.      * 递归拼接结果
  85.      *
  86.      * @param node 当前节点
  87.      * @param index 当前节点的位置
  88.      */
  89.     private void addResults(final TreeNode<String> node, final int index) {
  90.         this.result.set(index, node.getValue());

  91.         final List<TreeNode<String>> parentList = node.getParentNodes();
  92.         if (parentList.isEmpty()) {
  93.             this.results.add(new ArrayList<>(this.result));
  94.         } else {
  95.             for (final TreeNode<String> parentNode : parentList) {
  96.                 this.addResults(parentNode, index - 1);
  97.             }
  98.         }
  99.     }

  100.     // =============

  101.     /**
  102.      * 多叉树节点<br>
  103.      *
  104.      * @author cat73
  105.      */
  106.     public class TreeNode<T> {
  107.         /**
  108.          * 当前节点的值
  109.          */
  110.         private final T value;
  111.         /**
  112.          * 父节点列表
  113.          */
  114.         private List<TreeNode<T>> parentNode;
  115.         /**
  116.          * 子节点列表
  117.          */
  118.         private List<TreeNode<T>> childNodes;
  119.    
  120.         /**
  121.          * 构造一个多叉树节点
  122.          *
  123.          * @param value 当前节点的值
  124.          */
  125.         public TreeNode(final T value) {
  126.             this.value = value;
  127.         }
  128.    
  129.         /**
  130.          * 获取当前节点的值
  131.          *
  132.          * @return 当前节点的值
  133.          */
  134.         public T getValue() {
  135.             return this.value;
  136.         }
  137.    
  138.         /**
  139.          * 获取父节点列表
  140.          *
  141.          * @return parentNode 当前节点所属的父节点列表
  142.          */
  143.         public List<TreeNode<T>> getParentNodes() {
  144.             if (this.parentNode == null) {
  145.                 this.parentNode = new ArrayList<>();
  146.             }
  147.             return this.parentNode;
  148.         }
  149.    
  150.         /**
  151.          * 添加一个父节点
  152.          *
  153.          * @param childNode 要被添加的父节点
  154.          */
  155.         private void addParentNode(final TreeNode<T> parentNode) {
  156.             if (this.parentNode == null) {
  157.                 this.parentNode = new ArrayList<>();
  158.             }
  159.             this.parentNode.add(parentNode);
  160.         }
  161.    
  162.         /**
  163.          * 获取子节点列表
  164.          *
  165.          * @return parentNode 当前节点所属的父节点列表
  166.          */
  167.         public List<TreeNode<T>> getChildNodes() {
  168.             if (this.childNodes == null) {
  169.                 this.childNodes = new ArrayList<>();
  170.             }
  171.             return this.childNodes;
  172.         }
  173.    
  174.         /**
  175.          * 添加子节点(会同时在子节点增加父节点)
  176.          *
  177.          * @param childNode 要被添加的子节点
  178.          */
  179.         public void addChildNode(final TreeNode<T> childNode) {
  180.             if (this.childNodes == null) {
  181.                 this.childNodes = new ArrayList<>();
  182.             }
  183.             this.childNodes.add(childNode);
  184.             childNode.addParentNode(this);
  185.         }
  186.     }
  187. }
复制代码


回复 使用道具 举报
cat73 黑马帝 2016-7-16 06:58:16
9#
顺便附送 Word Ladder 的答案(直接拿 II 的代码改的,可能效率不是很高了吧- -)

  1. public class Solution {
  2.     /**
  3.      * 寻找最短的转换路径的长度
  4.      *
  5.      * @param beginWord 开头单词
  6.      * @param endWord 结尾单词
  7.      * @param wordList 中间单词列表
  8.      * @return 最短路径的长度
  9.      */
  10.     public int ladderLength(String beginWord, String endWord, Set<String> wordList) {
  11.         // 最大展开次数
  12.         final int maxLen = wordList.size() + 1;
  13.         // 移除与开头单词重复的中间单词
  14.         wordList.remove(beginWord);
  15.         // 将结尾单词添加到单词列表
  16.         wordList.add(endWord);

  17.         // 上一级单词列表
  18.         Set<String> lastWords = new HashSet<>(1);
  19.         lastWords.add(beginWord);

  20.         // 按照中间单词数循环,如果在某个长度找到了可以转换的路径,则不再尝试下一个长度
  21.         int len = 1;
  22.         while (len <= maxLen) {
  23.             // 当前级节点列表
  24.             Set<String> currentWords = new HashSet<>(1);

  25.             // 寻找下一级单词列表
  26.             for (String word : lastWords) {
  27.                 final char[] chs = word.toCharArray();

  28.                 // 遍历可以转换到的单词
  29.                 String key;
  30.                 for (int i = 0; i < chs.length; i++) {
  31.                     final char bakCh = chs[i];
  32.                     for (char ch = 'a'; ch <= 'z'; ch++) {
  33.                         if (ch != bakCh) {
  34.                             chs[i] = ch;
  35.                             key = new String(chs);
  36.                             // 找到一个单词
  37.                             if (wordList.contains(key)) {
  38.                                 wordList.remove(key);
  39.                                 if(key.equals(endWord)) {
  40.                                     // 返回结果
  41.                                     return len + 1;
  42.                                 } else {
  43.                                     currentWords.add(key);
  44.                                 }
  45.                             }
  46.                         }
  47.                     }
  48.                     chs[i] = bakCh;
  49.                 }
  50.             }

  51.             // 用当前级单词列表覆盖上一级单词列表
  52.             lastWords = currentWords;

  53.             // 继续搜寻更长的路径
  54.             len += 1;
  55.         }

  56.         // 返回结果
  57.         return 0;
  58.     }
  59. }
复制代码



回复 使用道具 举报
哥们 你玩的这些太高大上了,鄙人完全懵逼啊

点评

有朋友做出了只需要 30ms 的算法,所以我这个算法还是很烂的- -  发表于 2016-7-24 16:59
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马