查看: 5269| 回复: 0
跳转到指定楼层
上一主题 下一主题
收起左侧

[二分/排序/搜索] 搜索类型bfs, dfs, dp的一点感想

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
最近做了几道搜索类的题, 有一些小感触如下:
主要用126 Word Ladder II 和 140 Word Break 举例。


搜索类题目大致可以用三种方法,BFS,DFS或者DP。

BFS的优势主要在于可以让我们处理同层元素,比如126题每改动一个字符需要加上1个距离,由于一个词可能可以有多种改动1个字符变成另一个词的方法,而这些新词距离都是原词距离+ 1,所以用BFS可以让我们知道现在走到多少层了。


DFS的优势主要在于打印路径。因为BFS用的是Queue,里面一般只存当前元素,或者最多加上一个距离。所以当我们需要找路径时,一般就要用到DFS了。DFS找路径分为两种,一种是打印所有可能的路径,这个时候有两种方法:
一种是返回List,这种方法有点recursion的味道。把当前值和往后所有可能的值拼起来,添加到path里面。
  1. public List<String> allPath (Map<Integer, List<Integer>> graph, int start) {
  2.                             return searchPath(graph, start);
  3.                     }
  4.                     public List<String> searchPath(Map<Integer, List<Integer>> graph, int start) {
  5.                             List<String> path = new ArrayList<>();
  6.                             if (!graph.containsKey(start) || graph.get(start).size() == 0) {
  7.                                     path.add(start + "");
  8.                                     return path;
  9.                             }
  10.                             List<Integer> nexts = graph.get(start);
  11.                             for (int next : nexts) {
  12.                                     List<String> sols = searchPath(graph, next);
  13.                                     for (String sol : sols) {
  14.                                             path.add(start + " " + sol);
  15.                                     }
  16.                             }
  17.                             return path;
  18.                     }
复制代码



另一种是返回void,每次走到最后时在res中添加path
  1. public List<String> allPath2 (Map<Integer, List<Integer>> graph, int start) {
  2.                           List<String> res = new ArrayList<>();
  3.                       StringBuilder path = new StringBuilder();
  4.                       path.append(start + " ");
  5.                           searchPath2(graph, start, path, res);
  6.                           return res;
  7.                     }
  8.                   
  9.                     public void searchPath2(Map<Integer, List<Integer>> graph, int start, StringBuilder path, List<String> res) {
  10.                             if (!graph.containsKey(start) || graph.get(start).size() == 0) {
  11.                                     res.add(path.toString());
  12.                                     return;
  13.                             }
  14.                             List<Integer> nexts = graph.get(start);
  15.                             for (int next : nexts) {
  16.                                     String s = " " + next;
  17.                                     path.append(s);
  18.                                     searchPath2(graph, next, path, res);
  19.                                     path.delete(path.length() - s.length(), path.length());
  20.                             }
  21.                     }
复制代码



如果可能出现重复搜索,比如Word Break II,某串字符可能在前后同时出现,为了避免又一次拆分搜索,可以把字符和它对应的拆分结果(list)存起来,就是memo一下。这样之后再遇到这个小伙伴就可以直接调用啦。
代码如下, map的工作就是memo,如果去掉map相关程序结果一样,但时间会慢不少。

  1. public List<String> wordBreak(String s,  List<String> wordDict) {
  2.         return DFS(s, wordDict, new HashMap<String, List<String>>());
  3.     }      

  4.     List<String> DFS(String s, List<String> wordDict, Map<String, List<String>>map) {
  5.         if (map.containsKey(s))
  6.             return map.get(s);

  7.         List<String>res = new LinkedList<String>();     
  8.         if (s.length() == 0) {
  9.             res.add("");
  10.             return res;
  11.         }               
  12.         for (String word : wordDict) {
  13.             if (s.startsWith(word)) {
  14.                 List<String>sublist = DFS(s.substring(word.length()), wordDict, map);
  15.                 for (String sub : sublist)
  16.                     res.add(word + (sub.isEmpty() ? "" : " ") + sub);               
  17.             }
  18.         }      
  19.         map.put(s, res);
  20.         return res;
  21.     }
复制代码



另一种,需要打印最短/最长路径的所有可能路径。
如果没有环比较容易, 在之前代码基础上加上记录路径长度的变量total和保存最长路径的max即可。

  1. public List<Integer> longestPath (Map<Integer, List<Couple>> graph, int start, int total) {
  2.                     List<List<Integer>> res = new ArrayList<>();
  3.                     List<Integer> path = new ArrayList<>();
  4.                     path.add(start);
  5.                     int[] max = new int[]{Integer.MIN_VALUE};
  6.                     search(graph, start, 0, max, res, path);
  7.                     return res.get(res.size() - 1);
  8.             }
  9.             public void search(Map<Integer, List<Couple>> graph, int start, int total, int[] max, List<List<Integer>> res, List<Integer> path) {
  10.                     if (!graph.containsKey(start) || graph.get(start).size() == 0) {
  11.                             if (max[0] < total) {
  12.                                     max[0] = total;
  13.                                     res.add(new ArrayList<>(path));
  14.                             }
  15.                             return;
  16.                     }
  17.                     List<Couple> pairs = graph.get(start);
  18.                     for (Couple pair : pairs) {
  19.                             int next = pair.node;
  20.                             int dist = pair.dist;
  21.                             path.add(next);
  22.                             search(graph, next, total + dist, max, res, path);
  23.                             path.remove(path.size() - 1);
  24.                     }
  25.             }
复制代码



但是有环的时候比较复杂。
其实一般来说,就算要的是“最”路径,DFS也可以,但是126 Word Ladder用了BFS+DF。word break给定一串字符,往后走,走走总是可以走完的(要么匹配到最后,要么没有next),但是word ladder可能出现字符串a变动一个字符到b,结果b变动一个字符又到了a,这种情况怎么办呢?我第一反应是DFS加个visited。DFS加visited又有两种情况,一种是遇到一个点以后就再也不愿意看见它了,这种情况肯定不能用在这里,因为不同路径可走向同一个字符串,这是允许的。另一种是同一条路径不能出现重复字符串。这种情况在dfs前后分别add,remove那个字符就行。但是这道题要求最短路径。DFS的一大缺点就是所有路径都会走完,哪怕prune一下(之前保存过某个距离且路径长度超过之前保存的距离的直接return),但花费时间还是很大,而且存在先加了一些长距离的后面又逐步加短距离的(DFS我们无法控制它先走那条,它需要一条路径搜索到底再走另一条),我们拿到res以后还要从后往前遍历看。所以先用BFS找最短路径长的同时保存一下neighbors,之后再用DFS通过neighbors找到所有路径更为快捷。

DP的方法可以参照unique path。一般来说,如果是单个方向走,比如只可以往下往右走,用dp更新,非常直接明了。最后return  dp[长][宽]即可。


欢迎大家补充。

评分

参与人数 4大米 +21 收起 理由
jonesG + 1 赞一个
草鸡蛋 + 3 赞一个!
s243yang + 1 很有用的信息!
14417335 + 16

查看全部评分


上一篇:网上找的系统设计资料
下一篇:求助一个google的高频电面题目!

本帖被以下淘专辑推荐:

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表