6. LintCode 宽度优先搜索(二)

6. LintCode 宽度优先搜索(二)

[5. LintCode 宽度优先搜索(一)]·(https://blog.csdn.net/weixin_42183401/article/details/111332644)

LintCode 615:课程表

https://www.lintcode.com/problem/course-schedule

描述

现在你总共有 n 门课需要选,记为 0n - 1.
一些课程在修之前需要先修另外的一些课程,比如要学习课程 0 你需要先学习课程 1 ,表示为[0,1]
给定n门课以及他们的先决条件,判断是否可能完成所有课程?

样例
输入: n = 2, prerequisites = [[1,0]] 
输出: true
    
输入: n = 2, prerequisites = [[1,0],[0,1]] 
输出: false    
解题思路

拓扑排序,不能有环,和https://www.lintcode.com/problem/topological-sorting 题一样的解法,最后如果有课程入度不为0则表示不能完成所有课程。

AC代码
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // 存储每个课程的入度(直接用数组更好)
        Map<Integer, Integer> map = new HashMap<>();
        // 存储每个课程的后置课程
        Map<Integer, Set<Integer>> dict = new HashMap<>();
		// 获取每个课程的入度和其后置课程
        for (int[] arr : prerequisites) {
            int x = arr[0];
            int y = arr[1];
            if (!dict.containsKey(y)) {
                Set<Integer> set = new HashSet<>();
                dict.put(y, set);
            }
            // 数据中有重复的边。。需要去重
            if (dict.get(y).contains(x)) {
                continue;
            }
            if (!map.containsKey(x)) {
                map.put(x, 0);
            }
            map.put(x, map.get(x) + 1);
            if (!map.containsKey(y)) { map.put(y, 0); }
            dict.get(y).add(x);
        }
		// 宽搜队列,入度为0的课程首先加入队列
        Deque<Integer> deque = new ArrayDeque<>();
        for (Integer course : map.keySet()) {
            if (map.get(course) == 0) {
                deque.add(course);
            }
        }

        // 简单宽搜过程,当一个课程入度为0了加入队列
        while (!deque.isEmpty()) {
            int course = deque.removeFirst();
            // 取出的课程从map中删除,当map中没有课程了时表明所有课程都可以被选上
            map.remove(course);
            if (!dict.containsKey(course)) {
                continue;
            }
            for (Integer x : dict.get(course)) {
                map.put(x, map.get(x) - 1);
                if (map.get(x) == 0) {
                    deque.add(x);
                }
            }
        }
		// 返回答案
        return map.size() == 0;
    }

LintCode 618:搜索图中节点

https://www.lintcode.com/problem/search-graph-nodes

描述

给定一张 无向图,一个 节点 以及一个 目标值,返回距离这个节点最近 且 值为目标值的节点,如果不能找到则返回 NULL
在给出的参数中, 我们用 map 来存节点的值
保证答案唯一

样例
输入:
{1,2,3,4#2,1,3#3,1,2#4,1,5#5,4}
[3,4,5,50,50]
1
50
输出:
4
解释:
2------3  5
 \     |  | 
  \    |  |
   \   |  |
    \  |  |
      1 --4
Give a node 1, target is 50

there a hash named values which is [3,4,10,50,50], represent:
Value of node 1 is 3
Value of node 2 is 4
Value of node 3 is 10
Value of node 4 is 50
Value of node 5 is 50

Return node 4
解题思路

标准的宽搜题,每次从节点扩展它能到达的节点,为target直接返回节点,不是加入宽搜队列。

AC代码
public UndirectedGraphNode searchNode(ArrayList<UndirectedGraphNode> graph,
                                      Map<UndirectedGraphNode, Integer> values,
                                      UndirectedGraphNode node,
                                      int target) {
    // 出发点的value便是target直接返回
    if (values.get(node) == target) {
        return node;
    }
    // 宽搜队列
    Deque<UndirectedGraphNode> deque = new ArrayDeque<>();
    deque.add(node);    
    // 记录已经走过的节点
    Set<Integer> set = new HashSet<>();
    while (!deque.isEmpty()) {
        // 取出节点
        UndirectedGraphNode unode = deque.removeFirst();
        set.add(unode.label);
        // 拓展
        for (UndirectedGraphNode node1 : unode.neighbors) {
            if (values.get(node1) == target) {
                return node1;
            }
            if (!set.contains(node1.label)) {
                deque.add(node1);
            }
        }
    }
    return null;
}

LintCode 605:序列重构

https://www.lintcode.com/problem/sequence-reconstruction

描述

判断是否序列 org 能唯一地由 seqs重构得出. org是一个由从1到n的正整数排列而成的序列,1 \leq n \leq 10^41≤_n_≤104。 重构表示组合成seqs的一个最短的父序列 (意思是,一个最短的序列使得所有 seqs里的序列都是它的子序列).
判断是否有且仅有一个能从 seqs重构出来的序列,并且这个序列是org

样例
输入:org = [1,2,3], seqs = [[1,2],[1,3]]
输出: false
解释:
[1,2,3] 并不是唯一可以被重构出的序列,还可以重构出 [1,3,2]

输入: org = [1,2,3], seqs = [[1,2],[1,3],[2,3]]
输出: true
解释:
序列 [1,2], [1,3],[2,3] 可以唯一重构出 [1,2,3].
    
输入:org = [4,1,5,2,6,3], seqs = [[5,2,6,3],[4,1,5,2]]
输出:true    
解题思路

拓扑排序
观看题目可以想象 seqs 的一个序列可以构成一个顺序,所有序列加起来就能构成一个拓扑排序。
org 要是唯一的序列,那么seqs只能 构成一个拓扑排序,如果有多的拓扑排序那便不能唯一。

AC代码

写的比较垃圾。。。。

public boolean sequenceReconstruction(int[] org, int[][] seqs) {

    // 边界条件
    if (org.length < 1) { return true;}
	// 存储拓扑顺序
    Map<Integer, List<Integer>> map = new HashMap<>();
    // 存储每个点的入度
    int[] in = new int[org.length + 1];
	// 预处理,有不在org序列中的数或者seqs长度不够org可以直接返回false。
    int length = 0;
    for (int[] arr : seqs) {
        for (int i = 1; i < arr.length; i++) {
            int x = arr[i - 1], y = arr[i];
            if (y > org.length) {
                return false;
            }
            if (!map.containsKey(x)) {
                map.put(x, new ArrayList<>());
            }
            // 存储顺序和入度
            map.get(x).add(y);
            in[y]++;
        }
        // 记录seqs长度
        length += arr.length;
        if (arr.length > 0 && arr[0] > org.length) {
            return false;
        }
    }
    //seqs长度不够org可以直接返回false
    if (length < org.length) {
        return false;
    }

    // 宽搜队列,按找org序列顺序进出队列。
    Deque<Integer> deque = new ArrayDeque<>();
    int index = 0;
    deque.add(org[index]);

    while (!deque.isEmpty()) {
        int x = deque.removeFirst();
        index++;
        // index 等于 org.length表明org序列已经走完且唯一返回true
        if (index == org.length) {
            return true;
        }
        // x 没有扩展了,而org还没结束,返回false
        if (!map.containsKey(x)) {
            return false;
        }
        int next = org[index];
        for (Integer y : map.get(x)) {
            in[y]--;
            // 不是 org序列的下一个元素入度为 0 代表拓扑顺序不唯一,返回false
            if (in[y] == 0 && y != next) {
                return false;
            }
        }
        // 序列的下一个不为0代表序列不成功,返回false
        if (in[next] != 0) {
            return false;
        }
        deque.add(next);
    }
    return false;
}

LintCode 598:僵尸矩阵

https://www.lintcode.com/problem/zombie-in-matrix

描述

给一个二维网格,每一个格子都有一个值,2 代表墙,1 代表僵尸,0 代表人类(数字 0, 1, 2)。僵尸每天可以将上下左右最接近的人类感染成僵尸,但不能穿墙。将所有人类感染为僵尸需要多久,如果不能感染所有人则返回 -1

样例
输入:
[[0,1,2,0,0],
 [1,0,0,2,1],
 [0,1,0,0,0]]
输出:
2
    
输入:
[[0,0,0],
 [0,0,0],
 [0,0,1]]
输出:
4    
解题思路

简单宽搜题,首先将僵尸加入宽搜队列,然后每步将队列的僵尸都去感染附件的人类,已经传播过的僵尸设为2。避免重复感染。
最后还有0表示不能感染所有人,否则返回宽搜了多少次。

AC代码
public class Solution {
  
    public int zombie(int[][] grid) {
        // 宽搜队列
        Deque<Point> deque = new ArrayDeque<>();
        // 预处理,将初始僵尸加入宽搜队列
        int n = grid.length, m = grid[0].length;
        int count = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == 0) {
                    count++;
                } else if (grid[i][j] == 1) {
                    Point point = new Point(i, j);
                    deque.add(point);
                    // 已经传播过的僵尸设为2
                    grid[i][j] = 2;
                }
            }
        }
		// 方便计算上下左右
        int[] a = {0, 0, 1, -1};
        int[] b = {1, -1, 0, 0};

        int day = 0;
        while (!deque.isEmpty()) {
            day++;
            int size = deque.size();
            for (int i = 0; i < size; i++) {
                Point point = deque.removeFirst();
                for (int j = 0; j < 4; j++) {
                    int x = point.x + a[j];
                    int y = point.y + b[j];
                    if (x >= n || x < 0 || y >= m || y < 0 || grid[x][y] == 2) {
                        continue;
                    }
                    count--;
                    grid[x][y] = 2;
                    deque.add(new Point(x, y));
                }
            }
            if (count == 0) {
                return day;
            }
        }

        return count > 0 ? -1 : day;
    }
}

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

LintCode 178:图是否是树

https://www.lintcode.com/problem/graph-valid-tree

描述

给出 n 个节点,标号分别从 0n - 1 并且给出一个 无向 边的列表 (给出每条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树
你可以假设我们不会给出重复的边在边的列表当中. 无向边 [0, 1][1, 0] 是同一条边, 因此他们不会同时出现在我们给你的边的列表当中。

样例
输入: n = 5 edges = [[0, 1], [0, 2], [0, 3], [1, 4]]
输出: true.
    
输入: n = 5 edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]
输出: false.
解题思路

BFS
一棵n个节点的树只有n-1条边,当一个图有n-1条边且没有环时便是一棵树。
对输入预处理存储图的关系后,随便从一个节点宽搜,最后队列能走过n个点便是没有环的图,也就是树。
并查集
用并查集合并相连的点,遍历每条边,合并前判断两个节点是否属于同一个集合,属于同一个集合表明有环,不是树;不是同一个集合则合并。
最后如果所有点都在一个集合中,则说明这个图联通且无环,是一棵树。

AC代码
public boolean validTree(int n, int[][] edges) {
    // 不是n-1条边便不是一棵树。
    if (n == 0 || edges.length != n - 1) {
        return false;
    }

    Map<Integer, Set<Integer>> map = new HashMap<>();
    Set<Integer> set = new HashSet<>();
    Deque<Integer> deque = new ArrayDeque<>();
	
    for (int[] arr : edges) {
        int x = arr[0], y = arr[1];
        if (!map.containsKey(x)) {map.put(x, new HashSet<>());}
        if (!map.containsKey(y)) {map.put(y, new HashSet<>());}
        map.get(x).add(y);
        map.get(y).add(x);
    }
	// 标准宽搜过程,走过的节点都加入set中
    deque.add(0);
    while (!deque.isEmpty()) {
        int x = deque.removeFirst();
        set.add(x);
        if (!map.containsKey(x)) {
            continue;
        }
        for (Integer next : map.get(x)) {
            if (!set.contains(next)) {
                set.add(next);
                deque.add(next);
            }
        }
    }
	// 宽搜没有走完n个节点表明有环,不是树
    return set.size() == n;      
}

LintCode 892:外星人字典

https://www.lintcode.com/problem/alien-dictionary

描述

有一种新的使用拉丁字母的外来语言。但是,你不知道字母之间的顺序。你会从词典中收到一个非空的单词列表,其中的单词在这种新语言的规则下按字典顺序排序。请推导出这种语言的字母顺序。

  1. 你可以假设所有的字母都是小写。
  2. 如果a是b的前缀且b出现在a之前,那么这个顺序是无效的。
  3. 如果顺序是无效的,则返回空字符串。
  4. 这里可能有多个有效的字母顺序,返回以正常字典顺序看来最小的。
样例
输入:["wrt","wrf","er","ett","rftt"]
输出:"wertf"
解释:
从 "wrt""wrf" ,我们可以得到 't'<'f'"wrt""er" ,我们可以得到'w'<'e'"er""ett" ,我们可以得到 get 'r'<'t'"ett""rftt" ,我们可以得到 'e'<'r'
所以返回 "wertf"
    
输入:["z","x"]
输出:"zx"
解释:
从 "z""x",我们可以得到 'z' < 'x'
所以返回"zx"    
解题思路

拓扑排序。每两个单词之间可以确定一个顺序,这样就可以得到拓扑排序的一个指向。例如 “wrt"和"wrf”,就可以得到 t -> f。
两个单词中的第一个相同位置不同字母可以确定出一个指向。
得到所有指向后便可整理出一个拓扑顺序,判断是否可以排出一个字典顺序(有环便没有)。

AC代码
// 九章
public String alienOrder(String[] words) {
    // 先构造拓扑排序的每一条边
    Map<Character, Set<Character>> graph = constructGraph(words);
    if (graph == null) {
        return "";
    }
	// 宽搜找寻拓扑顺序
    return topologicalSorting(graph);
}

// 建图,找出拓扑排序中的每一条边,也就是字母的顺序
private Map<Character, Set<Character>> constructGraph(String[] words) {
    Map<Character, Set<Character>> graph = new HashMap<>();
	// 先给每个字母初始化
    for (int i = 0; i < words.length; i++) {
        for (int j = 0; j < words[i].length(); j++) {
            char c = words[i].charAt(j);
            if (!graph.containsKey(c)) {
                graph.put(c, new HashSet<>());
            }
        }
    }
	
    for (int i = 0; i < words.length - 1; i++) {
        int index = 0;
        while (index < words[i].length() && index < words[i + 1].length()) {
            // 找到第一个字母不相同的位置,建立边
            if (words[i].charAt(index) != words[i + 1].charAt(index)) {
                graph.get(words[i].charAt(index)).add(words[i + 1].charAt(index));
                break;
            }
            index++;
        }
        // 如果长单词前缀是短单词,并且短单词还排在长单词后面,表明无解。
        if (index == Math.min(words[i].length(), words[i + 1].length())) {
            if (words[i].length() > words[i + 1].length()) {
                return null;
            }
        }
    }

    return graph;
}

// 获取每个字母的入度,同之前的拓扑排序一样
private Map<Character, Integer> getIndegree(Map<Character, Set<Character>> graph) {
    Map<Character, Integer> indegree = new HashMap<>();
    for (Character u : graph.keySet()) {
        indegree.put(u, 0);
    }

    for (Character u : graph.keySet()) {
        for (Character v : graph.get(u)) {
            indegree.put(v, indegree.get(v) + 1);
        }
    }

    return indegree;
}

private String topologicalSorting(Map<Character, Set<Character>> graph) {
    Map<Character, Integer> indgree = getIndegree(graph);
    // 优先队列,因为要返回正常字典顺序看起来最小的
    Queue<Character> queue = new PriorityQueue<>();
	// 将入度为0的单词都加入队列中
    for (Character u : indgree.keySet()) {
        if (indgree.get(u) == 0) {
            queue.offer(u);
        }
    }
	// 标准宽搜过程
    StringBuilder sb = new StringBuilder();
    while (!queue.isEmpty()) {
        Character head = queue.poll();
        sb.append(head);
        for (Character neighbor : graph.get(head)) {
            indgree.put(neighbor, indgree.get(neighbor) - 1);
            if (indgree.get(neighbor) == 0) {
                queue.offer(neighbor);
            }
        }
    }
	// 表明有环
    if (sb.length() != indgree.size()) {
        return "";
    }

    return sb.toString();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值