為什么 LeetCode(力扣)「執行代碼」正確,提交代碼出錯?


為什么有時在 LeetCode (力扣)上,Run Code(執行代碼) 正確,Submit(提交代碼)時提示 Wrong Answer(解答錯誤)?

看這篇文章你就懂了,真不是 LeetCode 出 Bug 了。


大家好,我是 「負雪明燭」,一位堅持 7 年寫了 1000 篇 LeetCode 算法題題解的程序員。歡迎關注。

面試必會的算法題系列在寫作中——

  1. 面試必會的算法題——前綴和
  2. 面試必會的算法題——求加法

今天分享的是刷題小經驗 —— 為什么一個測試用例,在「執行代碼」的結果正確,「提交」運行出錯?

往事

談起這個話題,我忍不住想起往事。

第一次遇到這個問題是我在大二剛開始刷 LeetCode 的時候(還沒中文版力扣),那時候我的主語言是 Java,在本地編譯器 Eclipse 上寫完代碼粘貼到 LeetCode 的代碼框里,點擊「Run Code」后,看測試用例能通過,然后就「Submit」了。

滿心期待着出現了一個綠色的 Accept!

幾秒種后,我傻眼了,我看到的是紅色的 Wrong Answer!

當時是新入門的小白,完全不能理解為什么會出錯,還以為是 LeetCode 出現 Bug 了 😂

當時我的同學中在刷題的只有我一個,也沒有微信群交流,只能自己想辦法。捯飭了半天才在網上找到了答案——原來這和 LeetCode 的評測機制有關。

復現

下面我就在中文力扣上復現一下當時的場景。

就像下面這樣,LeetCode 第 1 題:兩數之和。

假如我的代碼是下面這樣,注意 visited 定義的位置:

unordered_map<int, int> visited;
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        for (int i = 0; i < nums.size(); ++i) {
            if (visited.count(target - nums[i])) {
                return {visited[target - nums[i]], i};
            }
            visited[nums[i]] = i;
        }
        return {};
    }
};

題目默認的測試用例是 :

[2,7,11,15]

9

點擊「執行代碼」—— OK,沒問題,輸出和預期結果一致。

image.png

那就點擊「提交」唄 —— 出現了「解答錯誤」!

image.png

出錯的測試用例是:

[3,3]

6

然鵝,我看自己的代碼應該沒問題呢。所以我把這個測試用例放到測試用例的執行框里,點擊「執行代碼」,結果是 [0, 1] !和預期結果是一樣的!

這到底是怎么回事呢?是力扣有 Bug 嗎?

原因

其實不是力扣有 Bug,是我們沒有理解力扣的評測機制。

力扣的判題機在讀取您的代碼后,對每個測試用例,都會初始化一次類,但全局變量和類內靜態變量需要您手動初始化。

你可以把力扣的評測過程想象成下面這樣:

unordered_map<int, int> visited;
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 你的代碼
    }
};

int main() {
    string line;
    while (getline(cin, line)) {
        // 讀取輸入的 nums
        vector<int> nums = stringToIntegerVector(line);
        // 讀取輸入的 target
        getline(cin, line);
        int target = stringToInteger(line);
        
        // 每次實例化一個 Solution(),並執行其 twoSum 方法
        vector<int> ret = Solution().twoSum(nums, target);
		
        // 輸出結果
        string out = integerVectorToString(ret);
        cout << out << endl;
    }
    return 0;
}

看到了嗎?

  • 對於每個測試用例,力扣會實例化一個 Solution(),並執行其 twoSum 方法
  • 如果把 visited 放在了類 Solution 的外邊,作為「全局變量」,那么對於所有測試用例是共享的。因此上一個測試用例的運行結果會影響下一個測試用例,導致「解答錯誤」。
  • 把「解答錯誤」的測試用例放到「測試用例」框里,再運行的結果是對的,因為只運行了一個測試用例,不會互相干擾。

正確做法

為了避免「全局變量」或者「類內的靜態變量」在不同測試用例之間的干擾,我們有兩種辦法:

  1. 推薦做法:不使用「全局變量」或者「類內的靜態變量」;
  2. 在 類內/函數內 對「全局變量」或者「類內的靜態變量」執行初始化。

比方說,我們把 visited 的位置調整到 類內/函數內 ,從而避免了「全局變量」。

class Solution {
private:
    // 類內
    unordered_map<int, int> visited;
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 函數內
        // unordered_map<int, int> visited;
        for (int i = 0; i < nums.size(); ++i) {
            if (visited.count(target - nums[i])) {
                return {visited[target - nums[i]], i};
            }
            visited[nums[i]] = i;
        }
        return {};
    }
};

或者在函數內初始化 visited,比如在 twoSum() 方法中執行 visited.clear()

unordered_map<int, int> visited;
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        visited.clear();
        for (int i = 0; i < nums.size(); ++i) {
            if (visited.count(target - nums[i])) {
                return {visited[target - nums[i]], i};
            }
            visited[nums[i]] = i;
        }
        return {};
    }
};

上面寫法中,我最推薦把 visited 變量寫到 twoSum 以內。

為什么呢?

這樣符合「最小作用域原則」。

最小作用域原則是指:把每個變量定義成只對需要看到它的、最小范圍的代碼段可見。

這樣能規避很多意想不到的錯誤。

總結

在刷題的時候,應盡量避免使用「全局變量」或者「類內的靜態變量」,因為它們可能導致不同「測試用例」互相干擾,導致「解答錯誤」。

定義變量應遵循「最小作用域原則」,能規避很多意想不到的錯誤。

明白了 LeetCode(力扣)的評測機制之后,能讓我們刷題不糊塗👨‍💻👩‍💻

參考:

  1. https://support.leetcode-cn.com/hc/kb/article/1194344/
  2. https://www.ituring.com.cn/article/216213


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM