LeetCode《程序员面试金典》面试题 04.01. 节点间通路

题目

在这里插入图片描述

解题

解题一:邻接表+广度优先搜索

var findWhetherExistsPath = function(n, graph, start, target) {
    // 创建 邻接表
    let record = new Map();
    for (let [src, des] of graph) {
        if (record[src]) record[src].add(des);
        else record[src] = new Set([des]);
    }
    
    let graphArr = [start];
    let isVisited = new Set();
    while (graphArr.length > 0) {
        let front = graphArr.shift();
        if (front === target) return true; // 找到,返回 true
        if (isVisited.has(front) === true) continue; // 访问过,跳过
        isVisited.add(front); // front 变成 visited
        // 如果有从 front 出发的路径,将 next 元素加入 graphArr
        if (record[front]) {
            for (let next of record[front]) {
                graphArr.push(next);
            }
        }
    }
    return false;
};

另一种写法是在加入 graphArr 的时候进行判断(判断更早,graphArr 里没有重复元素),但语义没有 isVisited 那么明显。

// javascript
var findWhetherExistsPath = function(n, graph, start, target) {
    if (start === target) return true; // 必须有
    // 创建 邻接表
    let record = new Map();
    for (let [src, des] of graph) {
        if (record[src]) record[src].add(des);
        else record[src] = new Set([des]);
    }
    
    let graphArr = [start];
    let graphSet = new Set([start]); // 加入 start 元素
    while (graphArr.length > 0) {
        let front = graphArr.shift();
        if (record[front]) { // 如果有从 front 出发的路径
            for (let next of record[front]) {
                if (next === target) return true; // 判断是不是 target
                // 如果已经加入则不重复加,即 graphArr 无重复元素
                if (graphSet.has(next) === false) {
                    graphArr.push(next);
                    graphSet.add(next);
                }
            }
        }
    }
    return false;
};

isVisited 和 graphSet 都是为了应对自环,平行边用 set 存储邻接表时就过滤掉了,对结果无影响。

如果不创建邻接表,每次都去 graph 里找 front 指向的 next,时间复杂度为 O ( n e ) O(ne) O(ne),会超出时间限制。使用邻接表,时间复杂度为 O ( n + e ) O(n+e) O(n+e),空间复杂度为 O ( n + e ) O(n+e) O(n+e)

解题二:邻接表+深度优先搜索

// javascript
var findWhetherExistsPath = function(n, graph, start, target) {
    let isVisited = new Set();
    // 创建 邻接表
    let record = new Map();
    for (let [src, des] of graph) {
        if (record[src]) record[src].add(des);
        else record[src] = new Set([des]);
    }
    return canFindExistingPath(record, start, target, isVisited);
};

var canFindExistingPath = function(graph, start, target, isVisited) {
    if (start === target) return true;
    if (isVisited.has(start) === true) return false;
    isVisited.add(start);
    if (graph[start]) {
        for (let des of graph[start]) {
            if (canFindExistingPath(graph, des, target, isVisited) === true) {
                return true;
            }
        }
    }
    return false;
};

时间复杂度为 O ( n + e ) O(n+e) O(n+e),空间复杂度为 O ( n + e ) O(n+e) O(n+e)

解法三:Bellman Ford

// javascript
var findWhetherExistsPath = function(n, graph, start, target) {
    if (start === target) return true;
    let reachable = new Array(n).fill(false);
    reachable[start] = true;
    for (let i = 0; i < n; i++) {
        for (let [u, v] of graph) {
            if (reachable[u] === true) {
                reachable[v] = true;
            }
        }
    }
    return reachable[target];
};

上面是 Bellman Ford 算法,时间复杂度为 O ( n e ) O(ne) O(ne),空间复杂度为 O ( n ) O(n) O(n),会超过时间限制。下面的改写比较讨巧,因为题目给的测试用例是排序的,因而可以只遍历一遍 graph,将时间复杂度降为 O ( e ) O(e) O(e)

// javascript
var findWhetherExistsPath = function(n, graph, start, target) {
    if (start === target) return true;
    let reachable = new Array(n).fill(false);
    reachable[start] = true;
    for (let [u, v] of graph) {
		if (reachable[u] === true) {
			if (v === target) return true;
	    	reachable[v] = true;
    	}
    }
    return false;
};
可求解成功:n=3, graph=[[0, 1], [1, 2]], start=0, target=2
会求解失败:n=3, graph=[[1, 2], [0, 1]], start=0, target=2

为了解决第二种失败情况,可以像下面一样进行改写:

// javascript
var findWhetherExistsPath = function(n, graph, start, target) {
    if (start === target) return true;
    let set = new Set([start]);
    let count = 1;
    while (true) {
    	for (let [u, v] of graph) {
			if (set.has(u) === true) {
				if (v === target) return true;
	    		set.add(v);
    		}
    	}
    	// 如果 set 里未新增元素,则 start 能到的元素全部找出,break
    	// 如果 set 里新增元素,则 还能找到其他 start 能到达但未被检查过的路径
    	if (set.size === count) break;
    	count = set.size;
    }
    return false;
};

解法四:倒序递归+深度优先搜索

// javascript
var findWhetherExistsPath(n, graph, start, target) {
    // 创建访问状态数组
    let isVisited = new Array(n).fill(false);
    // DFS
    return helper(graph, start, target,isVisited);
}var helper(graph, start, target, isVisited) {
    // 深度优先搜索
    for (let i = 0; i < graph.length; i++) {
        // 确保当前路径未被访问(该判断主要是为了防止图中自环出现死循环的情况)
        if (!isVisited[i]) {
            // 若当前路径起点与终点相符,则直接返回结果
            if (graph[i][0] === start && graph[i][1] === target) {
                return true;
            }
            // 设置访问标志
            isVisited[i] = true;
            // DFS 关键代码,思路:同时逐渐压缩搜索区间
            if (graph[i][1] === target && helper(graph, start, graph[i][0], isVisited)) {
                return true;
            }
            // 清除访问标志
            isVisited[i] = false;
        }
    }
    return false;
}

作者解释见下图,隐约能意会是这个道理,但想得不是特别明白,总感觉解法四能通过而正向递归不能跟 LeetCode 最后一个奇长无比的测试用例有关:如果进行修改,让很多边指向 target 的话,helper 调用次数增加,解法四也会超时。
在这里插入图片描述
总结一下,解法一和二为正统解法;解法三和四有点奇法妙招的意味,多少讨了些巧(测试用例正好能通过),启发是碰到问题如果能找到一些特性从而简化问题,也是能出奇制胜滴。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值