先用一个用烂了的面试题, 给脑子热个身。
【 问题-1】:
给一个单链表,请用最快的方法判断单链表是否有环?环的长度是多少?环的接入点是哪个?
【 分析】:
O(n)完全跑一遍一定不够快。
判断是否有环可以使用追赶法。用两个指针,一个快(fast)一个慢(slow),fast指针一次走两步,slow指针一次走一步。如果fast遇到null则不存在环, 如果fast遇到slow就说明存在环。
如果你这样解决这个问题, 面试官一定会问你为什么。因为很多人都知道这样做,但未必真的理解为什么。
如果有环, 两个指针一定是快指针先进入环, 然后慢指针进入环。
接下来我们认为快指针在环中追赶慢指针,因为步数差1,所以二者距离每次减小1,直至追到。
下面来论证需要多少次能追上,
假设二者最初相距m的距离,则需要m次,因为距离每次减小1嘛,也就是最多需要慢指针走完一次环的长度。所以最慢的情况是走完一遍,也就是O(n)的复杂度内可以解决是否有环的问题。
(不好画图,脑补一下)
然后来计算环到底有多长。
用之前的想法来继续想一下,只要让他们继续走,等到再次遇到的时候又走的次数就是环的长度了。
因为当二者相遇,我们可以认为他们的距离是0也可以认为是环的长度,所以只要再次相遇,中间的步数就是环的长度。
环的入口点是哪个:
设环长m, 设相遇的时候,slow走了s步, fast就走了2s步。则2s = km + s。 也就是fast指针走的路程的另外一种表示:在圈内转的圈数k,加slow走的路程。
同时,我们设起点到入口距离为k1, 入口距离相遇点k2, 相遇点距离入口k3。(注意环上的距离,是有方向的),
得到:
s = k1+ k2;
k1 = (k-1)m + k3;
所以起点到入口的距离==相遇点开始转(k-1)圈后再到入口的距离。所以只要从起点和相遇点同时出发,相遇的时候就是入口点。
(看了几个网上的说法,有很多是错的。TVT)
【 问题-2】:
给一个无向图,问这附图是否是一个树。
【 分析】:
首先要明白树的定义。
一个有n个节点,且有n-1条边的联通图是树。
首先要仅有n-1条边,其次要是一个联通图,也就是任意两个点可以互相抵达。
不满足n-1条边的我们可以直接判断掉。
接下来有两种做法。
并查集方法:
并查集是一种数据结构,应该没有封装好的工具,需要自己手动写。
这种数据结构支持两个集合的合并,查询每个元素所属的集合。不懂的可以看代码。
做法是:初始化每个点都属于单独的一个集合,然后取一条边,如果边的两个端点不在一个集合中,就合并这两个集合,如果在一个集合中就说明图中有环,就不是树。
数据:
5 [[0,1],[0,2],[0,3],[1,4]] // 5个点,4条边
代码:
class Solution {
public:
/*
* @param n: An integer
* @param edges: a list of undirected edges
* @return: true if it’s a valid tree, or false
*/
int fa[1000005]; // 并查集,记录每个点所属的集合。
int Find(int x) {
return fa[x] = x == fa[x] ? x : Find(fa[x]);
} // 带压缩路径的并查集
bool validTree(int n, vector<vector<int>> &edges) {
// write your code here
int len = edges.size();
if(len != n - 1 || n == 0) {
return false;
}
for(int i = 0; i < n; i++) {
fa[i] = i;
}
for(int i = 0; i < len; i ++) {
int fv = Find(edges[i][0]);
int fu = Find(edges[i][1]);
if(fv == fu){
return false;
} else {
fa[fv] = fu; // 合并两个集合
}
}
return true;
}
};
BFS方法:
将边存成图,然后BFS一个联通块,看这个图是否联通。联通就是树,否则就不是
class Solution {
public:
/*
* @param n: An integer
* @param edges: a list of undirected edges
* @return: true if it’s a valid tree, or false
*/
int vis[100005];
vector Grp[100005];
bool validTree(int n, vector