(C++) Leetcode:为什么我的代码比示例顶级解决方案慢这么多? (547. 省份数量)

问题描述

https://leetcode.com/problems/number-of-provinces/

当我第一次尝试解决这个问题时,我非常兴奋,仅用了 20/30 分钟,但当我提交代码时,我最终获得了 8.43 个百分位。我查看了最快的解决方案是如何解决这个问题的,结果发现,示例顶级解决方案几乎与我的代码相同,但它的运行速度提高了 3 倍。我一直在比较代码,并不能真正指出足够大的差异。两者都应该同样快......谁能解释为什么?如果我没记错的话,这两种情况的性能都是 O(mn)。

以下是我的代码。这是不言自明的,所以不确定大量评论会有什么好处。

class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        int components = 0;
        vector<bool> visited (isConnected.size(),false);
        
        // go through each row
        for (int i = 0; i < isConnected.size(); i++) {
            // explore only unvisited items
            if (!visited[i]) {
                queue<int> q;
                
                q.push(i);
                components++;
            
                while (!q.empty()) {
                    int node = q.front();
                    q.pop();
                    visited[node] = true;
                    
                    // push all direct connections onto the queue so we explore them
                    for (int j = 0; j < isConnected[0].size(); j++) {
                        if (isConnected[node][j] == 1 && !visited[j]) {
                            q.push(j);
                        }
                    }
                }
            }
        }
        
        return components;
    }
};

以下是运行速度比我的代码快 3 倍的顶级解决方案示例。

class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        if (M.empty()) {
            return 0;
        }
        int count = 0;
        vector<bool> visited(M.size());
        auto bfs = [&](int student) {
            queue<int> q;
            q.push(student);
            visited[student] = true;
                    
            while (!q.empty()) {
                auto current = q.front();
                cout << "current " << current << endl;
                q.pop();
                        
                for (int i = 0; i < M.size(); i++) {
                    if (M[current][i] == 1 and !visited[i]) {
                        visited[i] = true;
                        q.push(i);
                    }
                }
            }
        };
        for (int r = 0; r < M.size(); r++) {
                if (visited[r] == false) {
                    count++;
                    bfs(r);
                }
        }
        return count;
    }
};

解决方法

区别就我可以[看到][1] visited[i] = true; 的位置而言,这会导致每次迭代的内存访问量减少一些。 OP代码需要重新获取bool的地方。

之间可能存在数据或控制流依赖

visited[node] = true;

!visited[j]

最好的代码中没有。

OP 代码内循环

.L118:
        mov     rax,QWORD PTR [rsi+rbx]
        cmp     DWORD PTR [rax+rcx*4],1
        jne     .L116

        mov     rax,rbp
        mov     r8,rcx
        sal     rax,cl
        mov     rcx,QWORD PTR [rsp+80]
        shr     r8,6
        and     rax,QWORD PTR [rcx+r8*8]
        jne     .L116
        mov     rax,QWORD PTR [rsp+192]
        sub     rax,4
        cmp     rdi,rax
        je      .L117

“最佳”代码

.L76:
        mov     rax,1
        jne     .L74

        mov     rax,QWORD PTR [r12]
        mov     rsi,rcx
        shr     rsi,6
        mov     rax,QWORD PTR [rax]
        lea     rsi,[rax+rsi*8]
        mov     eax,1
        sal     rax,QWORD PTR [rsi]
        test    rcx,rax
        jne     .L74
        or      rax,rcx <------------ visited[i] = true;
        mov     QWORD PTR [rsi],rax
        mov     rax,QWORD PTR [rsp+96]
        sub     rax,4
        cmp     r8,rax
        je      .L75
        mov     DWORD PTR [r8],edx
        add     r8,4
        mov     QWORD PTR [rsp+80],r8
        jmp     .L74


  [1]: https://godbolt.org/z/obfqf7