面试算法

先用一个用烂了的面试题, 给脑子热个身。

【 问题-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 <int> Grp[100005];
    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< len; i++ ) {
            int u = edges[i][0];
            int v = edges[i][1];
            Grp[u].push_back(v);
            Grp[v].push_back(u);
        }
        
        queue<int> Q;
        int cnt = 0;
        Q.push(0);
        memset(vis, 0, sizeof(vis));
        vis[0] = 1;
        cnt = 1;
        while(!Q.empty()){
            int u = Q.front();
            Q.pop();
            for(int i=0;i<Grp[u].size(); i++){
                int v = Grp[u][i];
                if(!vis[v]) {
                    Q.push(v);
                    cnt ++;
                    vis[v] = 1;
                }
            }
        }
        if(cnt == n) return true;
        else return false;
        
    }
};


这题目还是比较常见的,如果你去大公司面试,这个题目很可能会是第一题, 因为想到很简单。

但是这样的题目让你白板写出来就考验你的代码功底了。

Good Luck!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值