在 LeetCode 的演算法學習旅程中,除了基礎題型,還有許多值得深入探索的進階題目。以下整理了多個常見類別的 LeetCode PHP 問題,希望能幫助大家系統性地掌握不同演算法的應用。
陣列 (Array)
1356. 根據數字二進制下1的數目排序
1365. 有多少小於當前數字的數字
字串 (String)
0028. 實現 strStr()
0925. 長按鍵入
1002. 查找常用字元
1221. 分割平衡字串
鏈表 (Linked List)
0143. 重排鏈表
0707. 設計鏈表
雜湊表 (Hash Table)
0205. 同構字串
1207. 獨一無二的出現次數
二叉樹 (Binary Tree)
0096. 不同的二叉搜索樹
0106. 從中序與後序遍歷序列構造二叉樹
0108. 將有序陣列轉換為二叉搜索樹
0110. 平衡二叉樹
0112. 路徑總和
0129. 求根到葉子節點數字之和
0222. 完全二叉樹的節點個數
0337. 打家劫舍 III
0404. 左葉子之和
0501. 二叉搜索樹中的眾數
0513. 找樹左下角的值
0530. 二叉搜索樹的最小絕對差
0538. 把二叉搜索樹轉換為累加樹
0617. 合併二叉樹
0654. 最大二叉樹
0669. 修剪二叉搜索樹
0700. 二叉搜索樹中的搜索
0701. 二叉搜索樹中的插入操作
0968. 監控二叉樹
1382. 將二叉搜索樹變平衡
貪心演算法 (Greedy)
0045. 跳躍遊戲 II
0134. 加油站
0376. 擺動序列
0452. 用最少數量的箭引爆氣球
0763. 劃分字母區間
字串 (String)
0925. 長按鍵入 (Long Pressed Name)
問題描述:判斷一個給定的名字 name 是否可以透過長按鍵盤輸入得到另一個字串 typed。例如,name = "alex"
,typed = "aaleex"
,則為 true
。
正確解題思路概述:
這是一個典型的雙指針問題。使用兩個指針 i 和 j 分別遍歷 name 和 typed。當兩者字元匹配時,兩個指針都向前移動;當不匹配時,檢查 typed 的指針 j 是否可以透過長按來匹配前一個字元,如果可以,則只移動 j,否則表示不匹配。最後,確保 name 的所有字元都已被遍歷。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
function isLongPressedName($name, $typed) {
$i = 0;
$j = 0;
while ($j < strlen($typed)) {
if ($i < strlen($name) && $name[$i] === $typed[$j]) {
$i++;
$j++;
} elseif ($j > 0 && $typed[$j] === $typed[$j - 1]) {
$j++;
} else {
return false;
}
}
return $i === strlen($name);
}
}
除錯分析 (Debugging Analysis):
這段程式碼看起來很接近正確答案,但在某個邊界情況下會出錯。考慮這個測試案例:name = "saeed", typed = "ssaaedd"。
i=0
,j=0
,name[0]
(s) 和typed[0]
(s) 匹配,i++
,j++
。i=1
,j=1
,name[1]
(a) 和typed[1]
(s) 不匹配。進入elseif
判斷,typed[1]
和typed[0]
(s) 匹配,j++
。i=1
,j=2
,name[1]
(a) 和typed[2]
(a) 匹配,i++
,j++
。...這個過程會順利進行到
name
結束。現在考慮另一個測試案例:
name = "pyplrz"
,typed = "ppyypllr"
。i=0
,j=0
,name[0]
(p) 和typed[0]
(p) 匹配,i++
,j++
。i=1
,j=1
,name[1]
(y) 和typed[1]
(p) 不匹配。進入elseif
判斷,typed[1]
(p) 和typed[0]
(p) 匹配,j++
。i=1
,j=2
,name[1]
(y) 和typed[2]
(y) 匹配,i++
,j++
。...以此類推,直到
name
中的r
。i
指向r
,j
指向l
。i
指向l
,j
指向l
,匹配,i++
,j++
。i
指向r
,j
指向l
,不匹配。進入elseif
,typed[j]
(l) 和typed[j-1]
(l) 匹配,j++
。錯誤點:當
i
遍歷完name
後,程式碼依然在迴圈中。例如,name = "le"
,typed = "lleee"
。當i
遍歷到e
後,i=2
,typed
還有多餘的e
。迴圈會繼續執行,但if ($i < strlen($name) && ...)
中的&&
條件會因為i
已經等於strlen($name)
而導致if
判斷失敗,轉而進入elseif
。這會讓程式碼錯誤地認為typed
中多餘的字元是合法的長按字元,但實際上這些字元已經超過了name
的長度。
正確的 PHP 解決方案 (Corrected PHP Solution):
class Solution {
function isLongPressedName($name, $typed) {
$i = 0;
$j = 0;
while ($j < strlen($typed)) {
// Case 1: characters match
if ($i < strlen($name) && $name[$i] === $typed[$j]) {
$i++;
$j++;
}
// Case 2: long pressed character in typed string
elseif ($j > 0 && $typed[$j] === $typed[$j - 1]) {
$j++;
}
// Case 3: mismatch found
else {
return false;
}
}
// Final check: ensures all characters in name were matched
return $i === strlen($name);
}
}
除錯本類問題的技巧:
分步追蹤法:對於雙指針或多指針問題,不要一次性想完所有邏輯。可以透過紙筆或在程式碼中加入註解,一步步追蹤指針的移動,特別是當遇到邊界條件時。
邊界條件思考:在寫完程式碼後,立即思考以下邊界情況:
name
和typed
哪個長度更長?typed
中是否有在name
中不存在的字元?typed
中多餘的長按字元是否只出現在name
的尾部?
迴圈結束條件:仔細檢查迴圈的結束條件。通常,
while
迴圈結束後,需要一個最終檢查,來判斷所有條件是否都已滿足。在這個問題中,就是name
是否完整匹配了。
鏈表 (Linked List)
0707. 設計鏈表 (Design Linked List)
問題描述:設計一個鏈表,支援 get
、addAtHead
、addAtTail
、addAtIndex
和 deleteAtIndex
等操作。
正確解題思路概述:
這是一道考驗基本功的實作題。需要對單向或雙向鏈表的節點和指針操作有清晰的理解。一個通用的做法是使用一個虛擬頭節點 (dummy head) 來簡化邊界情況的處理,這樣在頭部進行增刪操作時就不需要特殊處理。
帶有錯誤的程式碼 (Buggy Solution):
class Node {
public $val;
public $next;
public function __construct($val) {
$this->val = $val;
$this->next = null;
}
}
class MyLinkedList {
public $head;
public $size;
function __construct() {
$this->head = null;
$this->size = 0;
}
function get($index) {
if ($index < 0 || $index >= $this->size) {
return -1;
}
$curr = $this->head;
for ($i = 0; $i < $index; $i++) {
$curr = $curr->next;
}
return $curr->val;
}
function addAtHead($val) {
$newNode = new Node($val);
$newNode->next = $this->head;
$this->head = $newNode;
$this->size++;
}
function addAtTail($val) {
if ($this->head === null) {
$this->addAtHead($val);
return;
}
$curr = $this->head;
while ($curr->next !== null) {
$curr = $curr->next;
}
$curr->next = new Node($val);
$this->size++;
}
function addAtIndex($index, $val) {
if ($index < 0 || $index > $this->size) {
return;
}
if ($index === 0) {
$this->addAtHead($val);
return;
}
$curr = $this->head;
for ($i = 0; $i < $index - 1; $i++) {
$curr = $curr->next;
}
$newNode = new Node($val);
$newNode->next = $curr->next;
$curr->next = $newNode;
$this->size++;
}
function deleteAtIndex($index) {
if ($index < 0 || $index >= $this->size) {
return;
}
if ($index === 0) {
$this->head = $this->head->next;
$this->size--;
return;
}
$curr = $this->head;
for ($i = 0; $i < $index - 1; $i++) {
$curr = $curr->next;
}
$curr->next = $curr->next->next;
$this->size--;
}
}
除錯分析 (Debugging Analysis):
這段程式碼乍看之下沒有問題,幾乎涵蓋了所有操作。然而,它在 addAtTail 和 addAtIndex 的實現中,有一個嚴重的邏輯漏洞。
錯誤點:當鏈表為空時,
addAtTail
會呼叫addAtHead
,並正確地將size
加一。但當我們在非空鏈表的尾部添加節點時,while ($curr->next !== null)
迴圈會正確地找到尾部節點,然後添加新節點並將size
加一。問題在於,如果我們在addAtTail
中不檢查空鏈表,直接使用while ($curr->next !== null)
,當鏈表為空時$curr
會是null
,然後while
迴圈會因為null
沒有next
屬性而報錯。另一個錯誤點:
addAtIndex
和deleteAtIndex
的邏輯。當index
等於size
時,addAtIndex
的迴圈for ($i = 0; $i < $index - 1; $i++)
只會遍歷到倒數第二個節點,然後在它的後面插入新節點,這正是在尾部添加的操作。這部分邏輯沒有問題。但如果我們不處理index === 0
的情況,直接從$this->head
開始遍歷,那麼$curr
會停留在index-1
的位置,而當index
為 0 時,index-1
是 -1,這會導致迴圈執行錯誤。雖然程式碼中對此進行了特別處理,但它增加了不必要的複雜性。
正確的 PHP 解決方案 (Corrected PHP Solution):
這裡介紹使用虛擬頭節點的方法,這是一種更健壯、更優雅的解決方案。
class Node {
public $val;
public $next;
public function __construct($val) {
$this->val = $val;
$this->next = null;
}
}
class MyLinkedList {
public $dummyHead;
public $size;
function __construct() {
$this->dummyHead = new Node(0); // Use a dummy head node
$this->size = 0;
}
function get($index) {
if ($index < 0 || $index >= $this->size) {
return -1;
}
$curr = $this->dummyHead->next;
for ($i = 0; $i < $index; $i++) {
$curr = $curr->next;
}
return $curr->val;
}
function addAtHead($val) {
$this->addAtIndex(0, $val);
}
function addAtTail($val) {
$this->addAtIndex($this->size, $val);
}
function addAtIndex($index, $val) {
if ($index < 0 || $index > $this->size) {
return;
}
$pred = $this->dummyHead;
for ($i = 0; $i < $index; $i++) {
$pred = $pred->next;
}
$newNode = new Node($val);
$newNode->next = $pred->next;
$pred->next = $newNode;
$this->size++;
}
function deleteAtIndex($index) {
if ($index < 0 || $index >= $this->size) {
return;
}
$pred = $this->dummyHead;
for ($i = 0; $i < $index; $i++) {
$pred = $pred->next;
}
$pred->next = $pred->next->next;
$this->size--;
}
}
除錯本類問題的技巧:
使用虛擬頭節點:這是鏈表操作的黃金法則。它能將所有對鏈表頭部的操作(增刪)都統一為對普通節點的操作,從而極大地簡化邏輯,避免空鏈表 (
null
) 的判斷。指針的角色:在操作中,明確每個指針的作用。例如,
$curr
應該指向當前節點,$pred
應該指向前一個節點。在增刪操作時,通常需要找到目標節點的前一個節點來進行指針的修改。畫圖追蹤法:對於複雜的指針操作,畫出鏈表的圖,標註每個指針的位置。然後,一步步按照程式碼修改箭頭(指針),這能幫助你視覺化錯誤。
雜湊表 (Hash Table)
0205. 同構字串 (Isomorphic Strings)
問題描述:判斷兩個字串 s 和 t 是否同構。同構的定義是 s
中的每個字元都能被唯一地對應到 t
中的一個字元,且這種對應關係是雙向唯一的。
正確解題思路概述:
這類問題的核心是建立一個穩固的雙向映射。使用兩個雜湊表,一個負責 s 到 t 的映射,另一個負責 t 到 s 的映射。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
function isIsomorphic($s, $t) {
if (strlen($s) !== strlen($t)) {
return false;
}
$map = [];
for ($i = 0; $i < strlen($s); $i++) {
if (isset($map[$s[$i]])) {
if ($map[$s[$i]] !== $t[$i]) {
return false;
}
} else {
$map[$s[$i]] = $t[$i];
}
}
return true;
}
}
除錯分析 (Debugging Analysis):
這段程式碼只使用了一個雜湊表,嘗試建立從 s 到 t 的映射。這解決了單向映射的問題,但卻忽略了雙向唯一的條件。
錯誤點:考慮
s = "foo"
,t = "bar"
。f
->b
,映射建立。o
->a
,映射建立。第二個
o
來到,map['o']
已存在,值為a
,與t[2]
(r) 不符,返回false
。這是正確的。
再考慮另一個測試案例:
s = "paper"
,t = "title"
。p
->t
,映射建立。a
->i
,映射建立。p
->t
,map['p']
存在,值為t
,與t[2]
(t) 匹配,繼續。e
->l
,映射建立。r
->e
,映射建立。
問題出現在這裡:
s = "ab"
,t = "aa"
。a
->a
,映射建立。b
->a
,map['b']
不存在,建立映射map['b'] = 'a'
。程式碼會返回
true
,但這兩個字串並不同構!因為s
中的a
和b
都被映射到了t
中的a
,這違背了唯一對應的條件。
正確的 PHP 解決方案 (Corrected PHP Solution):
為了確保雙向唯一,我們需要使用兩個雜湊表。
class Solution {
function isIsomorphic($s, $t) {
if (strlen($s) !== strlen($t)) {
return false;
}
$mapST = [];
$mapTS = [];
for ($i = 0; $i < strlen($s); $i++) {
$charS = $s[$i];
$charT = $t[$i];
// Check s -> t mapping
if (isset($mapST[$charS])) {
if ($mapST[$charS] !== $charT) {
return false;
}
} else {
$mapST[$charS] = $charT;
}
// Check t -> s mapping
if (isset($mapTS[$charT])) {
if ($mapTS[$charT] !== $charS) {
return false;
}
} else {
$mapTS[$charT] = $charS;
}
}
return true;
}
}
除錯本類問題的技巧:
分清單向與雙向:在處理映射問題時,首先要確認是單向關係還是雙向關係。如果題目要求「唯一對應」,通常意味著需要雙向檢查。
反向思維:如果一個字元被映射,檢查是否還有其他字元映射到同一個值。這就是為什麼需要第二個雜湊表來檢查
t
到s
的反向映射。使用
isset()
檢查存在性:在 PHP 中,使用isset()
檢查鍵是否存在比直接訪問更快,也更安全。
雜湊表 (Hash Table)
0205. 同構字串 (Isomorphic Strings)
問題描述:判斷兩個字串 s 和 t 是否同構。同構的定義是 s
中的每個字元都能被唯一地對應到 t
中的一個字元,且這種對應關係是雙向唯一的。
正確解題思路概述:
這類問題的核心是建立一個穩固的雙向映射。使用兩個雜湊表,一個負責 s 到 t 的映射,另一個負責 t 到 s 的映射。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
function isIsomorphic($s, $t) {
if (strlen($s) !== strlen($t)) {
return false;
}
$map = [];
for ($i = 0; $i < strlen($s); $i++) {
if (isset($map[$s[$i]])) {
if ($map[$s[$i]] !== $t[$i]) {
return false;
}
} else {
$map[$s[$i]] = $t[$i];
}
}
return true;
}
}
除錯分析 (Debugging Analysis):
這段程式碼只使用了一個雜湊表,嘗試建立從 s 到 t 的映射。這解決了單向映射的問題,但卻忽略了雙向唯一的條件。
錯誤點:考慮
s = "ab"
,t = "aa"
。i=0
時,s[0]
(a
) 到t[0]
(a
) 的映射建立。i=1
時,s[1]
(b
) 到t[1]
(a
) 的映射建立。程式碼會返回
true
,但這兩個字串並不同構!因為s
中的a
和b
都被映射到了t
中的a
,這違背了唯一對應的條件。
正確的 PHP 解決方案 (Corrected PHP Solution):
為了確保雙向唯一,我們需要使用兩個雜湊表。
class Solution {
function isIsomorphic($s, $t) {
if (strlen($s) !== strlen($t)) {
return false;
}
$mapST = [];
$mapTS = [];
for ($i = 0; $i < strlen($s); $i++) {
$charS = $s[$i];
$charT = $t[$i];
// Check s -> t mapping
if (isset($mapST[$charS])) {
if ($mapST[$charS] !== $charT) {
return false;
}
} else {
$mapST[$charS] = $charT;
}
// Check t -> s mapping
if (isset($mapTS[$charT])) {
if ($mapTS[$charT] !== $charS) {
return false;
}
} else {
$mapTS[$charT] = $charS;
}
}
return true;
}
}
除錯本類問題的技巧:
分清單向與雙向:在處理映射問題時,首先要確認是單向關係還是雙向關係。如果題目要求「唯一對應」,通常意味著需要雙向檢查。
反向思維:如果一個字元被映射,檢查是否還有其他字元映射到同一個值。這就是為什麼需要第二個雜湊表來檢查
t
到s
的反向映射。使用
isset()
檢查存在性:在 PHP 中,使用isset()
檢查鍵是否存在比直接訪問更安全。
二叉樹 (Binary Tree)
0108. 將有序陣列轉換為二叉搜索樹 (Convert Sorted Array to Binary Search Tree)
問題描述:給定一個按升序排列的整數陣列,將其轉換為一棵高度平衡的二叉搜索樹。
正確解題思路概述:
高度平衡的二叉搜索樹需要確保左右子樹的節點數量盡可能相等。這意味著我們應該選擇陣列的中點作為根節點。接著,遞歸地使用陣列的左半部分和右半部分來構造左右子樹。這是一個典型的分治(Divide and Conquer)問題。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
/**
* @param Integer[] $nums
* @return TreeNode
*/
function sortedArrayToBST($nums) {
if (empty($nums)) {
return null;
}
$mid = floor(count($nums) / 2);
$root = new TreeNode($nums[$mid]);
$root->left = $this->sortedArrayToBST(array_slice($nums, 0, $mid));
$root->right = $this->sortedArrayToBST(array_slice($nums, $mid + 1));
return $root;
}
}
除錯分析 (Debugging Analysis):
這段程式碼看起來邏輯正確,但存在一個常見的效能問題。
錯誤點:
array_slice
函式在每次遞歸呼叫時,都會複製陣列的一部分。對於一個長度為 N 的陣列,array_slice
的時間複雜度為 O(k),其中 k 是要複製的元素個數。在每次遞歸中,都會創建新的子陣列,這不僅消耗了大量的記憶體,也讓時間複雜度從 O(N) 變成了 O(NlogN)。這在處理大型陣列時會導致效能問題,甚至超時。
正確的 PHP 解決方案 (Corrected PHP Solution):
避免複製陣列,而是傳遞左右邊界索引。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
/**
* @param Integer[] $nums
* @return TreeNode
*/
function sortedArrayToBST($nums) {
return $this->buildBST($nums, 0, count($nums) - 1);
}
private function buildBST($nums, $left, $right) {
if ($left > $right) {
return null;
}
$mid = $left + floor(($right - $left) / 2);
$root = new TreeNode($nums[$mid]);
$root->left = $this->buildBST($nums, $left, $mid - 1);
$root->right = $this->buildBST($nums, $mid + 1, $right);
return $root;
}
}
除錯本類問題的技巧:
避免不必要的複製:對於處理大型陣列或字串的遞歸問題,優先考慮使用索引來定義子問題範圍,而不是創建新的子陣列。這能顯著提升效能。
明確遞歸邊界:在遞歸函數中,明確定義什麼時候應該停止。例如,當左索引大於右索引時,表示子陣列為空,應返回
null
。中點計算:在計算中點索引時,
$left + floor(($right - $left) / 2)
這種寫法比floor(($left + $right) / 2)
更安全,因為它可以避免在$left
和$right
都很大時發生整數溢出,儘管在 PHP 中不太常見。
二叉樹 (Binary Tree)
0110. 平衡二叉樹 (Balanced Binary Tree)
問題描述:給定一個二叉樹,判斷它是否是平衡二叉樹。平衡二叉樹的定義是:對於樹中的任意一個節點,其左、右子樹的高度差的絕對值不超過 1。
正確解題思路概述:
這是一個遞歸問題。對於每個節點,我們需要同時計算其左右子樹的高度,並判斷它們是否平衡。可以採用後序遍歷(左右中)的方式。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function isBalanced($root) {
if ($root === null) {
return true;
}
$leftHeight = $this->maxDepth($root->left);
$rightHeight = $this->maxDepth($root->right);
if (abs($leftHeight - $rightHeight) > 1) {
return false;
}
return $this->isBalanced($root->left) && $this->isBalanced($root->right);
}
private function maxDepth($node) {
if ($node === null) {
return 0;
}
return 1 + max($this->maxDepth($node->left), $this->maxDepth($node->right));
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯是正確的,但效能卻非常糟糕。
錯誤點:在
isBalanced
函式中,對於每個節點,它都分別呼叫了maxDepth
來計算左右子樹的高度。maxDepth
本身是一個遞歸函式,會遍歷子樹的所有節點。然後,isBalanced
又會遞歸呼叫自身來檢查左右子樹。這導致了大量的重複計算。追蹤分析:對於一個節點,我們需要檢查它的左右子樹高度差。當我們檢查左子樹時,又會對左子樹的子節點進行同樣的操作。這使得每個節點的高度被重複計算了多次。時間複雜度從 O(N) 變成了 O(N2)。例如,一個斜著的樹,會導致每個節點都被訪問多次。
正確的 PHP 解決方案 (Corrected PHP Solution):
我們需要將「計算高度」和「判斷平衡」這兩個步驟合併到一個遞歸函式中,使用後序遍歷的方式。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->next = $next;
* }
* }
*/
class Solution {
function isBalanced($root) {
return $this->checkBalanceAndHeight($root) !== -1;
}
private function checkBalanceAndHeight($node) {
if ($node === null) {
return 0; // Height of an empty tree is 0
}
$leftHeight = $this->checkBalanceAndHeight($node->left);
if ($leftHeight === -1) {
return -1; // Left subtree is unbalanced
}
$rightHeight = $this->checkBalanceAndHeight($node->right);
if ($rightHeight === -1) {
return -1; // Right subtree is unbalanced
}
if (abs($leftHeight - $rightHeight) > 1) {
return -1; // Current node is unbalanced
}
return 1 + max($leftHeight, $rightHeight);
}
}
除錯本類問題的技巧:
合併遞歸邏輯:當兩個遞歸操作(如本題中的計算高度和判斷平衡)可以同時進行時,將它們合併到一個函式中。這通常可以將時間複雜度從 O(N2) 降低到 O(N)。
善用返回值:遞歸函式不僅可以用來返回計算結果,也可以用來傳遞狀態。在這個問題中,我們用
-1
來標記子樹不平衡的狀態,這樣可以立即返回,避免不必要的計算。後序遍歷:當需要獲取子節點資訊(例如高度、節點數)來判斷父節點時,後序遍歷(左、右、中)是理想的選擇。
二叉樹 (Binary Tree)
0112. 路徑總和 (Path Sum)
問題描述:判斷二叉樹中是否存在一條從根節點到葉子節點的路徑,這條路徑上的所有節點值相加等於給定的目標和 targetSum
。
正確解題思路概述:
這是一個典型的遞歸或深度優先搜尋 (DFS) 問題。我們可以從根節點開始,在每一步遞歸中,將當前節點的值從 targetSum 中減去。當我們到達一個葉子節點時,如果剩餘的 targetSum 剛好為 0,就表示找到了一條符合條件的路徑。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function hasPathSum($root, $targetSum) {
if ($root === null) {
return $targetSum === 0;
}
$targetSum -= $root->val;
return $this->hasPathSum($root->left, $targetSum) || $this->hasPathSum($root->right, $targetSum);
}
}
除錯分析 (Debugging Analysis):
這個錯誤非常微妙,並且是遞歸問題中常見的陷阱。
錯誤點:當樹為空 (
$root === null
) 時,hasPathSum
函式返回$targetSum === 0
。這看起來合理,但在一個空樹上,我們應該直接返回false
,因為空樹沒有路徑。如果targetSum
為 0,這個錯誤的邏輯會導致它返回true
。另一個錯誤點:程式碼沒有檢查當前節點是否為葉子節點。它會無條件地向左右子樹遞歸。例如,如果
root
只有左子樹,沒有右子樹,程式碼會遞歸到null
的右子樹,然後根據第一個錯誤邏輯返回true
或false
,這會導致錯誤的結果。正確的做法是,只有在到達葉子節點時才檢查targetSum
是否為 0。
正確的 PHP 解決方案 (Corrected PHP Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function hasPathSum($root, $targetSum) {
// Correct base case: If the tree is empty, there is no path.
if ($root === null) {
return false;
}
$targetSum -= $root->val;
// Correct leaf node condition
if ($root->left === null && $root->right === null) {
return $targetSum === 0;
}
return $this->hasPathSum($root->left, $targetSum) || $this->hasPathSum($root->right, $targetSum);
}
}
除錯本類問題的技巧:
區分空節點和葉子節點:這是二叉樹問題的關鍵。
null
節點表示分支的末端,而葉子節點是左右子樹都為null
的非空節點。明確遞歸邊界:在遞歸函數中,確保你的基本情況 (base case) 是正確的。對於路徑問題,通常需要兩個基本情況:空節點和葉子節點。
畫圖追蹤:對於遞歸問題,畫一棵簡單的樹,並標註
targetSum
的變化,這能幫助你視覺化地找到錯誤。
二叉樹 (Binary Tree)
0129. 求根到葉子節點數字之和 (Sum Root to Leaf Numbers)
問題描述:給定一個二叉樹,樹中每個節點都包含一個從 0 到 9 的數字。每一條從根到葉子節點的路徑都代表一個數字。計算所有這類數字的總和。
正確解題思路概述:
這也是一個遞歸問題。我們可以從根節點開始,在遞歸過程中,將當前節點的值加入到一個累計的數字中。當到達葉子節點時,將這個累計的數字加到總和中。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
public $sum = 0;
function sumNumbers($root) {
$this->dfs($root, 0);
return $this->sum;
}
private function dfs($node, $currentNumber) {
if ($node === null) {
return;
}
$currentNumber = $currentNumber * 10 + $node->val;
if ($node->left === null && $node->right === null) {
$this->sum += $currentNumber;
return;
}
$this->dfs($node->left, $currentNumber);
$this->dfs($node->right, $currentNumber);
}
}
除錯分析 (Debugging Analysis):
這段程式碼主要問題在於使用了全域變數或類別屬性 $this->sum 來儲存總和。這在 LeetCode 的多測試案例環境中會導致錯誤。
錯誤點:LeetCode 會對同一個
Solution
類別的多個測試案例進行測試。如果第一個測試案例運行結束後$this->sum
被賦值為一個非零值,那麼下一個測試案例會在這個非零的基礎上繼續累加,而不是從 0 開始,這會導致錯誤。另一種錯誤點:這種方法不符合函數式編程的原則,使得程式碼的可讀性和可維護性較差。更好的做法是讓函數本身返回結果。
正確的 PHP 解決方案 (Corrected PHP Solution):
將總和作為遞歸函式的返回值,而不是使用全域變數。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function sumNumbers($root) {
return $this->dfs($root, 0);
}
private function dfs($node, $currentNumber) {
if ($node === null) {
return 0;
}
$currentNumber = $currentNumber * 10 + $node->val;
if ($node->left === null && $node->right === null) {
return $currentNumber;
}
$leftSum = $this->dfs($node->left, $currentNumber);
$rightSum = $this->dfs($node->right, $currentNumber);
return $leftSum + $rightSum;
}
}
除錯本類問題的技巧:
避免全域變數:在處理 LeetCode 問題時,盡量避免使用類別屬性來儲存狀態,特別是跨測試案例共享的狀態。
善用遞歸返回值:將遞歸函數的結果作為返回值傳遞,這能讓你的程式碼更具模組化,也更容易測試。
二叉樹 (Binary Tree)
0222. 完全二叉樹的節點個數 (Count Complete Tree Nodes)
問題描述:給定一棵完全二叉樹的根節點,計算它的節點總數。
正確解題思路概述:
最簡單的方法是遞歸遍歷所有節點,這在最差情況下是 O(N)。但這道題的關鍵在於**「完全二叉樹」**這個特殊性質。我們可以利用這個性質來達到更優的 O(log2N) 複雜度。
如果一棵樹是滿二叉樹,其節點數為 ,其中 h 是樹的高度。
利用這個性質,我們可以檢查左右子樹的高度是否相等。如果相等,左子樹必定是一棵滿二叉樹,可以直接計算其節點數。如果不相等,則右子樹是一棵滿二叉樹。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function countNodes($root) {
if ($root === null) {
return 0;
}
return 1 + $this->countNodes($root->left) + $this->countNodes($root->right);
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯是正確的,它確實能計算出所有節點的數量。
錯誤點:這個「錯誤」同樣是效能問題。它沒有利用完全二叉樹的性質,導致時間複雜度為 O(N)。這雖然在大多數情況下可以接受,但如果這道題要求更高的效能,就會被視為一個次優解。
正確的 PHP 解決方案 (Corrected PHP Solution):
利用完全二叉樹的性質,優化時間複雜度。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function countNodes($root) {
if ($root === null) {
return 0;
}
$leftDepth = $this->getDepth($root->left);
$rightDepth = $this->getDepth($root->right);
// If left and right depths are equal, left subtree is a full binary tree
if ($leftDepth === $rightDepth) {
return (1 << $leftDepth) + $this->countNodes($root->right);
} else {
// Otherwise, right subtree is a full binary tree
return (1 << $rightDepth) + $this->countNodes($root->left);
}
}
private function getDepth($node) {
$depth = 0;
while ($node !== null) {
$node = $node->left;
$depth++;
}
return $depth;
}
}
除錯本類問題的技巧:
善用題目性質:如果題目描述中提到了特殊性質,例如「完全二叉樹」、「二叉搜索樹」等,那麼解法很可能需要利用這些性質來進行優化。
效能意識:在看到「求總數」這類問題時,首先想到的可能是暴力遍歷。但養成習慣,思考是否有更高效的演算法。
二叉樹 (Binary Tree)
0337. 打家劫舍 III (House Robber III)
問題描述:在一個二叉樹形的社區中,每間房屋都有一定數量的財物。如果兩間相鄰的房屋在同一晚被盜,就會觸發警報。找出在不觸發警報的前提下,可以偷取的最高總金額。
正確解題思路概述:
這是一個樹形動態規劃問題,通常用遞歸來解決。對於每個節點,我們有兩種選擇:偷或不偷。
不偷當前節點:最高總金額是其左右子樹的最高總金額之和。
偷當前節點:最高總金額是當前節點的值,加上左右子樹的不偷的最高總金額之和。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function rob($root) {
if ($root === null) {
return 0;
}
// Rob the current node
$robCurrent = $root->val;
if ($root->left !== null) {
$robCurrent += $this->rob($root->left->left) + $this->rob($root->left->right);
}
if ($root->right !== null) {
$robCurrent += $this->rob($root->right->left) + $this->rob($root->right->right);
}
// Don't rob the current node
$notRobCurrent = $this->rob($root->left) + $this->rob($root->right);
return max($robCurrent, $notRobCurrent);
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯是正確的,但存在大量的重複計算,導致效能極差。
錯誤點:在計算
robCurrent
時,我們遞歸呼叫了rob
函數,這將會觸發對子節點的遞歸計算。然後,在計算notRobCurrent
時,我們又重複遞歸呼叫了rob($root->left)
和rob($root->right)
,這些計算在之前已經執行過。這會導致時間複雜度呈指數級增長。
正確的 PHP 解決方案 (Corrected PHP Solution):
使用後序遍歷,並讓遞歸函數返回一個二元組(或陣列),其中包含了**「偷當前節點的最高金額」和「不偷當前節點的最高金額」**。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function rob($root) {
$result = $this->robHelper($root);
return max($result[0], $result[1]);
}
private function robHelper($node) {
if ($node === null) {
return [0, 0]; // [rob, not_rob]
}
$left = $this->robHelper($node->left);
$right = $this->robHelper($node->right);
// 1. Rob current node: node value + max of (rob or not rob) of its grandchildren
$robCurrent = $node->val + $left[1] + $right[1];
// 2. Don't rob current node: max of (rob or not rob) of its children
$notRobCurrent = max($left[0], $left[1]) + max($right[0], $right[1]);
return [$robCurrent, $notRobCurrent];
}
}
除錯本類問題的技巧:
狀態壓縮:當一個遞歸函數需要返回多個相關值來避免重複計算時,可以將這些值打包成一個陣列或物件返回。
後序遍歷:當父節點的決策依賴於子節點的結果時,後序遍歷是最好的選擇。
避免重複計算:在遞歸中,仔細檢查是否有相同的子問題被多次求解。如果有,考慮使用動態規劃或記憶化搜索來優化。
二叉樹 (Binary Tree)
0404. 左葉子之和 (Sum of Left Leaves)
問題描述:計算所有左葉子節點的總和。
正確解題思路概述:
這是一個簡單的遞歸或遍歷問題。在遞歸遍歷樹的過程中,當一個節點的左子節點是葉子節點時,我們將其值加到總和中。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function sumOfLeftLeaves($root) {
$sum = 0;
if ($root === null) {
return 0;
}
if ($root->left !== null && $root->left->left === null && $root->left->right === null) {
$sum += $root->left->val;
}
$sum += $this->sumOfLeftLeaves($root->left);
$sum += $this->sumOfLeftLeaves($root->right);
return $sum;
}
}
除錯分析 (Debugging Analysis):
這個程式碼的邏輯在遞歸部分出錯。
錯誤點:
$sum += $this->sumOfLeftLeaves($root->left);
和$sum += $this->sumOfLeftLeaves($root->right);
這兩行遞歸調用會導致總和重複計算。每次遞歸調用都會建立一個新的$sum
變數,然後將子樹的左葉子之和返回,再加到父節點的$sum
中。追蹤分析:假設樹是
[3,9,20,null,null,15,7]
。第一次呼叫
sumOfLeftLeaves(root)
,root
是 3。root->left
(9) 是左葉子,$sum
變成 9。接著,遞歸呼叫
sumOfLeftLeaves(9)
。此時root
是 9。root->left
和root->right
都是null
,沒有左葉子,返回 0。父節點的
$sum
(9) 會加上這個 0。這段程式碼的主要問題在於,父節點已經計算了左葉子的值,但它又讓子節點去重新計算並返回,這是一種混亂的遞歸設計。
正確的 PHP 解決方案 (Corrected PHP Solution):
使用一個輔助函式,將總和作為參數傳遞,或使用返回值的遞歸。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function sumOfLeftLeaves($root) {
if ($root === null) {
return 0;
}
$sum = 0;
// Check if the left child is a leaf node
if ($root->left !== null && $root->left->left === null && $root->left->right === null) {
$sum += $root->left->val;
}
// Recursively calculate sum for left and right subtrees
$sum += $this->sumOfLeftLeaves($root->left);
$sum += $this->sumOfLeftLeaves($root->right);
return $sum;
}
}
除錯本類問題的技巧:
分清職責:讓每個函數只做一件事。在這個問題中,你可以用一個函數來判斷是否是左葉子,另一個函數來遍歷並求和。
遞歸返回值:如果一個遞歸函數需要返回值,確保所有遞歸路徑都正確地返回了預期的結果。
簡化邏輯:當你發現程式碼邏輯變得複雜或難以理解時,這通常是一個警示,表明可能有更簡單的解決方案。
二叉搜索樹 (Binary Search Tree)
0501. 二叉搜索樹中的眾數 (Find Mode in Binary Search Tree)
問題描述:找出二叉搜索樹中出現頻率最高的元素。
正確解題思路概述:
二叉搜索樹的中序遍歷會得到一個有序序列。因此,我們可以對樹進行中序遍歷,並在遍歷過程中統計每個元素的出現次數。由於是有序的,相同值的元素會連續出現,這讓統計變得非常簡單。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
private $map = [];
function findMode($root) {
$this->inorderTraversal($root);
$maxFreq = 0;
foreach ($this->map as $freq) {
$maxFreq = max($maxFreq, $freq);
}
$result = [];
foreach ($this->map as $val => $freq) {
if ($freq === $maxFreq) {
$result[] = $val;
}
}
return $result;
}
private function inorderTraversal($node) {
if ($node === null) {
return;
}
$this->inorderTraversal($node->left);
$this->map[$node->val] = ($this->map[$node->val] ?? 0) + 1;
$this->inorderTraversal($node->right);
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯是正確的,但同樣存在效能問題。
錯誤點:它沒有利用二叉搜索樹的有序性質。它使用了雜湊表來統計所有節點的頻率,這需要額外的 O(N) 空間。一個更優的解法可以利用中序遍歷的有序性質,在遍歷時就動態地追蹤當前元素和其出現次數,從而避免使用額外的雜湊表,將空間複雜度降至 O(1)。
正確的 PHP 解決方案 (Corrected PHP Solution):
利用中序遍歷的有序性,只使用幾個變數來追蹤狀態。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
private $currentVal = null;
private $currentCount = 0;
private $maxCount = 0;
private $result = [];
function findMode($root) {
$this->inorder($root);
return $this->result;
}
private function updateState($val) {
if ($val === $this->currentVal) {
$this->currentCount++;
} else {
$this->currentVal = $val;
$this->currentCount = 1;
}
if ($this->currentCount === $this->maxCount) {
$this->result[] = $val;
} elseif ($this->currentCount > $this->maxCount) {
$this->maxCount = $this->currentCount;
$this->result = [$val];
}
}
private function inorder($node) {
if ($node === null) {
return;
}
$this->inorder($node->left);
$this->updateState($node->val);
$this->inorder($node->right);
}
}
除錯本類問題的技巧:
善用特殊性質:當看到「二叉搜索樹」這類字眼時,思考如何利用其中序遍歷有序的特性來優化演算法。
空間複雜度:在面試或解題時,除了時間複雜度,也要考慮空間複雜度。避免使用不必要的輔助空間,尤其是在可以原地解決問題時。
二叉搜索樹 (Binary Search Tree)
0530. 二叉搜索樹的最小絕對差 (Minimum Absolute Difference in BST)
問題描述:找出二叉搜索樹中任意兩個節點值之間的最小絕對差。
正確解題思路概述:
利用二叉搜索樹的中序遍歷會得到一個有序的節點值序列。因此,最小絕對差只會發生在相鄰的兩個節點之間。我們只需要中序遍歷樹,並比較每個節點與其前一個節點的差值,然後更新最小值。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
private $minDiff = PHP_INT_MAX;
private $prev = null;
function getMinimumDifference($root) {
$this->inorderTraversal($root);
return $this->minDiff;
}
private function inorderTraversal($node) {
if ($node === null) {
return;
}
$this->inorderTraversal($node->left);
if ($this->prev !== null) {
$this->minDiff = min($this->minDiff, abs($node->val - $this->prev->val));
}
$this->prev = $node;
$this->inorderTraversal($node->right);
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯和正確解法非常接近,但它犯了一個類別屬性的常見錯誤。
錯誤點:在 LeetCode 的多測試案例環境中,
$this->prev
和$this->minDiff
會在一個測試案例結束後保留其值,而不會重置。當下一個測試案例運行時,它會從上一個案例的狀態開始,導致錯誤的結果。例如,第一個測試案例的minDiff
為 1,第二個案例的答案應該是 5,但因為minDiff
已經是 1,所以它會保持為 1,從而返回錯誤的答案。
正確的 PHP 解決方案 (Corrected PHP Solution):
在主函數中初始化狀態,並將狀態作為參數傳遞。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function getMinimumDifference($root) {
$minDiff = PHP_INT_MAX;
$prev = null;
$this->inorderTraversal($root, $minDiff, $prev);
return $minDiff;
}
private function inorderTraversal($node, &$minDiff, &$prev) {
if ($node === null) {
return;
}
$this->inorderTraversal($node->left, $minDiff, $prev);
if ($prev !== null) {
$minDiff = min($minDiff, abs($node->val - $prev->val));
}
$prev = $node;
$this->inorderTraversal($node->right, $minDiff, $prev);
}
}
除錯本類問題的技巧:
避免類別屬性:對於需要跨遞歸調用共享的狀態,考慮將其作為參數傳遞,並使用引用 (
&
) 來修改其值。清晰的狀態管理:在遞歸中,確保你的狀態變數(如
$prev
,$minDiff
)被正確地初始化和更新。
二叉樹 (Binary Tree)
0617. 合併二叉樹 (Merge Two Binary Trees)
問題描述:將兩棵二叉樹合併成一棵新的二叉樹。合併規則是:如果兩個節點重疊,則新節點的值是它們值的總和;否則,新節點就是非空節點。
正確解題思路概述:
這是一個簡單的遞歸問題。我們可以同時遍歷兩棵樹,從根節點開始。在每一步中,創建一個新節點,其值為兩棵樹對應節點值的總和。然後,遞歸地合併左右子樹。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function mergeTrees($root1, $root2) {
if ($root1 === null) {
return $root2;
}
if ($root2 === null) {
return $root1;
}
$root1->val += $root2->val;
$root1->left = $this->mergeTrees($root1->left, $root2->left);
$root1->right = $this->mergeTrees($root1->right, $root2->right);
return $root1;
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯在大多數情況下是正確的。
錯誤點:它沒有創建一棵新的樹,而是直接在
root1
上修改。雖然這能通過測試,但它違背了題目的本意。題目的意圖是合併為一棵新的樹,而不是原地修改。儘管這在技術上可能不是一個**「邏輯錯誤」**,但在現實世界的編程中,原地修改輸入參數可能會帶來副作用,尤其是在多執行緒環境中。
正確的 PHP 解決方案 (Corrected PHP Solution):
創建一棵新的樹來存放結果。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function mergeTrees($root1, $root2) {
if ($root1 === null && $root2 === null) {
return null;
}
if ($root1 === null) {
return $root2;
}
if ($root2 === null) {
return $root1;
}
$mergedRoot = new TreeNode($root1->val + $root2->val);
$mergedRoot->left = $this->mergeTrees($root1->left, $root2->left);
$mergedRoot->right = $this->mergeTrees($root1->right, $root2->right);
return $mergedRoot;
}
}
除錯本類問題的技巧:
閱讀題目細節:仔細閱讀題目要求,判斷是「原地修改」還是「創建新結構」。這會影響你的程式碼設計。
避免副作用:在編程中,盡量避免修改函數的輸入參數,除非這是明確要求的。這會讓你的程式碼更具可預測性。
二叉樹 (Binary Tree)
0700. 二叉搜索樹中的搜索 (Search in a Binary Search Tree)
問題描述:在給定的二叉搜索樹中,尋找一個節點,其值等於給定的 val
。如果找到,返回該節點;如果沒有,返回 null
。
正確解題思路概述:
利用二叉搜索樹的特性:左子樹的所有節點都小於根節點,右子樹的所有節點都大於根節點。這允許我們以二分查找的方式進行搜索,從而將時間複雜度從 O(N) 降至 O(logN)。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function searchBST($root, $val) {
if ($root === null || $root->val === $val) {
return $root;
}
return $this->searchBST($root->left, $val) || $this->searchBST($root->right, $val);
}
}
除錯分析 (Debugging Analysis):
這個錯誤非常經典,它忽略了二叉搜索樹的特性。
錯誤點:這段程式碼的邏輯是在對普通二叉樹進行搜索。它會無條件地同時向左右子樹進行遞歸,這導致了不必要的搜索和效能浪費。這違背了利用二叉搜索樹特性來加速搜索的初衷。
正確的 PHP 解決方案 (Corrected PHP Solution):
利用二叉搜索樹的特性來決定向左還是向右搜索。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function searchBST($root, $val) {
if ($root === null) {
return null;
}
if ($root->val === $val) {
return $root;
} elseif ($val < $root->val) {
return $this->searchBST($root->left, $val);
} else {
return $this->searchBST($root->right, $val);
}
}
}
除錯本類問題的技巧:
善用題目類型:當看到「二叉搜索樹」這類字眼時,第一反應應該是思考如何利用其有序性。
避免冗餘操作:不要同時搜索左右子樹,因為二叉搜索樹的特性保證了你只需要向一個方向搜索。
二叉樹 (Binary Tree)
0701. 二叉搜索樹中的插入操作 (Insert into a Binary Search Tree)
問題描述:在給定的二叉搜索樹中,插入一個值為 val
的新節點。保證樹中不會有重複的值。
正確解題思路概述:
這同樣利用了二叉搜索樹的特性。我們從根節點開始,比較 val 與當前節點的值。如果 val 較小,則向左子樹遞歸;如果 val 較大,則向右子樹遞歸。當到達 null 節點時,就找到了插入新節點的位置。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function insertIntoBST($root, $val) {
if ($root === null) {
return new TreeNode($val);
}
if ($val < $root->val) {
$this->insertIntoBST($root->left, $val);
} else {
$this->insertIntoBST($root->right, $val);
}
return $root;
}
}
除錯分析 (Debugging Analysis):
這個錯誤非常常見,也是新手在遞歸中經常犯的錯。
錯誤點:
$this->insertIntoBST($root->left, $val);
這行程式碼只會遞歸地尋找插入位置,但它沒有將遞歸的返回值賦給父節點。這導致新節點被創建了,但它永遠無法被正確地連接到樹上。
正確的 PHP 解決方案 (Corrected PHP Solution):
將遞歸的返回值賦給父節點。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function insertIntoBST($root, $val) {
if ($root === null) {
return new TreeNode($val);
}
if ($val < $root->val) {
$root->left = $this->insertIntoBST($root->left, $val);
} else {
$root->right = $this->insertIntoBST($root->right, $val);
}
return $root;
}
}
除錯本類問題的技巧:
遞歸返回值:在處理需要修改樹結構的遞歸問題時,確保遞歸函數返回新樹的根節點,並將其賦給父節點。
原地修改 vs. 新建:在這個問題中,我們是在原地修改樹。但要小心,有些問題可能要求你創建一個全新的樹。
二叉樹 (Binary Tree)
0968. 監控二叉樹 (Binary Tree Cameras)
問題描述:在二叉樹的節點上放置攝影機,每個攝影機可以監控其父節點、自身和子節點。找出覆蓋所有節點所需的最小攝影機數量。
正確解題思路概述:
這是一個貪心演算法問題,通常用後序遍歷來解決。思路是,從葉子節點開始向上遍歷,因為葉子節點的覆蓋難度最高。
如果一個節點的子節點沒有被監控,那麼我們必須在當前節點放置一個攝影機。
為了達到最小數量,我們應該在葉子節點的父節點上放置攝影機,這樣可以同時覆蓋多個節點。
我們可以定義三種狀態:
0
(未被監控),1
(已被監控),2
(已放置攝影機)。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
private $result = 0;
function minCameraCover($root) {
if ($this->dfs($root) === 0) {
$this->result++;
}
return $this->result;
}
private function dfs($node) {
if ($node === null) {
return 1;
}
$left = $this->dfs($node->left);
$right = $this->dfs($node->right);
if ($left === 0 || $right === 0) {
$this->result++;
return 2;
}
if ($left === 2 || $right === 2) {
return 1;
}
return 0;
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯大致正確,但它沒有處理根節點的特殊情況。
錯誤點:在
dfs
函數中,如果左右子樹都返回1
(已被監控),那麼當前節點會返回0
(未被監控)。這意味著它依賴父節點來放置攝影機。但如果這個節點是根節點,它沒有父節點可以放置攝影機。因此,在主函數minCameraCover
中,需要額外判斷根節點的狀態。
正確的 PHP 解決方案 (Corrected PHP Solution):
在主函數中對根節點進行特殊處理。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
private $result = 0;
// States: 0 = needs camera, 1 = monitored, 2 = has camera
function minCameraCover($root) {
if ($this->dfs($root) === 0) {
$this->result++;
}
return $this->result;
}
private function dfs($node) {
if ($node === null) {
return 1; // Monitored state for null node
}
$left = $this->dfs($node->left);
$right = $this->dfs($node->right);
if ($left === 0 || $right === 0) {
$this->result++;
return 2; // Place camera here
}
if ($left === 2 || $right === 2) {
return 1; // Monitored by a child's camera
}
return 0; // Not monitored, needs to be covered by parent
}
}
除錯本類問題的技巧:
貪心策略:對於這類問題,找到最佳的貪心策略是關鍵。通常是從底部開始向上思考。
狀態枚舉:清晰地定義所有可能的狀態,並在遞歸中正確地返回和處理這些狀態。
根節點的特殊性:在樹的題目中,根節點通常沒有父節點,因此需要特別處理。
二叉搜索樹 (Binary Search Tree)
1382. 將二叉搜索樹變平衡 (Balance a Binary Search Tree)
問題描述:給定一個二叉搜索樹,將其轉換為一棵平衡的二叉搜索樹。
正確解題思路概述:
這是一個兩步問題:
首先,對二叉搜索樹進行中序遍歷,得到一個有序的節點值陣列。
然後,利用這個有序陣列,像處理
0108.將有序陣列轉換為二叉搜索樹
一樣,遞歸地構造一棵平衡的二叉搜索樹。
帶有錯誤的程式碼 (Buggy Solution):
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function balanceBST($root) {
$nodes = [];
$this->inorder($root, $nodes);
return $this->buildBST($nodes, 0, count($nodes) - 1);
}
private function inorder($node, &$nodes) {
if ($node === null) {
return;
}
$this->inorder($node->left, $nodes);
$nodes[] = $node;
$this->inorder($node->right, $nodes);
}
private function buildBST(&$nodes, $left, $right) {
if ($left > $right) {
return null;
}
$mid = floor(($left + $right) / 2);
$root = $nodes[$mid];
$root->left = $this->buildBST($nodes, $left, $mid - 1);
$root->right = $this->buildBST($nodes, $mid + 1, $right);
return $root;
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯是正確的,但同樣存在效能和邊界問題。
錯誤點:
$nodes[] = $node;
這行將節點物件本身存入陣列。在buildBST
函數中,我們直接使用這些節點,並修改它們的左右子節點指針。這會導致原始樹的結構被破壞。這不僅是一個糟糕的設計,也可能在某些測試案例中導致錯誤,因為原始樹的指針可能還指向其他地方。
正確的 PHP 解決方案 (Corrected PHP Solution):
在 inorder 遍歷時只存儲節點值,然後用這些值來創建全新的節點,從而構建一棵新的樹。
/**
* Definition for a binary tree node.
* class TreeNode {
* public $val = null;
* public $left = null;
* public $right = null;
* function __construct($val = 0, $left = null, $right = null) {
* $this->val = $val;
* $this->left = $left;
* $this->right = $right;
* }
* }
*/
class Solution {
function balanceBST($root) {
$values = [];
$this->inorder($root, $values);
return $this->buildBalancedBST($values, 0, count($values) - 1);
}
private function inorder($node, &$values) {
if ($node === null) {
return;
}
$this->inorder($node->left, $values);
$values[] = $node->val;
$this->inorder($node->right, $values);
}
private function buildBalancedBST(&$values, $left, $right) {
if ($left > $right) {
return null;
}
$mid = floor(($left + $right) / 2);
$root = new TreeNode($values[$mid]);
$root->left = $this->buildBalancedBST($values, $left, $mid - 1);
$root->right = $this->buildBalancedBST($values, $mid + 1, $right);
return $root;
}
}
除錯本類問題的技巧:
區分值與引用:在 PHP 中,物件是通過引用傳遞的。如果你想保留原始結構,就不要直接修改它。
避免副作用:像這類問題,如果沒有特別要求「原地修改」,創建一個新的結構會是一個更安全、更清晰的選擇。
貪心演算法 (Greedy)
0045. 跳躍遊戲 II (Jump Game II)
問題描述:給定一個非負整數陣列 nums
,你最初位於第一個索引處。陣列中的每個元素代表你在該位置可以跳躍的最大長度。你的目標是使用最少的跳躍次數到達最後一個索引。
正確解題思路概述:
這是一個經典的貪心演算法問題。我們不需要計算從起點到每個節點的最少跳躍次數。相反,我們只需要知道每一步能到達的最遠距離。在每一步中,我們遍歷當前跳躍範圍內的所有位置,找出下一個能跳躍的最遠位置。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
/**
* @param Integer[] $nums
* @return Integer
*/
function jump($nums) {
$n = count($nums);
if ($n <= 1) {
return 0;
}
$jumps = 0;
$currentEnd = 0;
$farthest = 0;
for ($i = 0; $i < $n - 1; $i++) {
$farthest = max($farthest, $i + $nums[$i]);
if ($i == $currentEnd) {
$jumps++;
$currentEnd = $farthest;
}
}
return $jumps;
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯在大多數情況下是正確的,但存在一個嚴重的邏輯錯誤,它沒有考慮到無法到達終點的情況。
錯誤點:程式碼假設最終總能到達終點。但如果給定的陣列是
[3,2,1,0,4]
,我們從3
開始,最遠只能到索引3
,而nums[3]
是0
,無法再前進,因此永遠無法到達終點。然而,這段程式碼會繼續執行迴圈並返回一個錯誤的jumps
數。另一處潛在錯誤:儘管這段程式碼在大多數情況下是正確的,但它在
for
迴圈的邊界條件上有些模糊。當currentEnd
更新時,它可能已經超過了n - 1
,但迴圈仍然繼續。雖然不會出錯,但這是一種不夠嚴謹的寫法。
正確的 PHP 解決方案 (Corrected PHP Solution):
在貪心演算法的基礎上,加入對無法到達終點情況的判斷,並優化邊界處理。
class Solution {
/**
* @param Integer[] $nums
* @return Integer
*/
function jump($nums) {
$n = count($nums);
if ($n <= 1) {
return 0;
}
$jumps = 0;
$currentEnd = 0;
$farthest = 0;
for ($i = 0; $i < $n - 1; $i++) {
$farthest = max($farthest, $i + $nums[$i]);
// If we've reached the end of the current jump's range
if ($i == $currentEnd) {
$jumps++;
$currentEnd = $farthest;
// Check if we can reach the end
if ($currentEnd >= $n - 1) {
break;
}
}
}
return $jumps;
}
}
除錯本類問題的技巧:
分清問題類型:這類問題看似是動態規劃,但因為每次的選擇都是局部最優的(跳到能到達的最遠位置),所以更適合用貪心演算法。
邊界條件思考:在處理任何陣列問題時,始終要考慮邊界情況,例如空陣列、單一元素陣列,以及無法到達終點的情況。
狀態變數的意義:在貪心問題中,準確定義和更新狀態變數(例如
jumps
,currentEnd
,farthest
)是成功的關鍵。
貪心演算法 (Greedy)
0134. 加油站 (Gas Station)
問題描述:在一個環形路上有 n
個加油站,第 i
個加油站的汽油量為 gas[i]
,從第 i
個加油站到第 i+1
個加油站需要消耗 cost[i]
的汽油。從哪一個加油站出發,可以繞行一圈?如果不能,返回 -1。
正確解題思路概述:
這是一個非常巧妙的貪心演算法問題。我們可以從任意一個點出發,如果汽油量變為負數,表示這個起點是失敗的,我們需要將起點移到下一個位置。同時,我們還要維護一個 totalGas 總量,如果總量為非負數,那麼一定存在一個起點。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
/**
* @param Integer[] $gas
* @param Integer[] $cost
* @return Integer
*/
function canCompleteCircuit($gas, $cost) {
$n = count($gas);
$totalGas = 0;
$currentGas = 0;
$start = 0;
for ($i = 0; $i < $n; $i++) {
$diff = $gas[$i] - $cost[$i];
$totalGas += $diff;
$currentGas += $diff;
if ($currentGas < 0) {
$currentGas = 0;
$start = $i + 1;
}
}
if ($totalGas < 0) {
return -1;
} else {
return $start;
}
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯在大多數情況下是正確的,但存在一個嚴重的邏輯錯誤。
錯誤點:
$start = $i + 1;
這行程式碼沒有考慮到i + 1
可能會超過陣列的索引範圍,這在 PHP 中雖然不會報錯,但在其他語言中可能會出問題。更重要的邏輯錯誤:如果
totalGas
是非負數,則一定存在一個解。但這段程式碼返回的start
點並不一定是正確的起點。考慮這個例子:gas = [2,3,4]
,cost = [3,4,3]
。i=0
:diff = -1
,totalGas = -1
,currentGas = -1
,$currentGas
小於 0,currentGas
重置為0
,start
變成1
。i=1
:diff = -1
,totalGas = -2
,currentGas = -1
,$currentGas
小於 0,currentGas
重置為0
,start
變成2
。i=2
:diff = 1
,totalGas = -1
,currentGas = 1
。迴圈結束。
totalGas
小於 0,返回 -1。這是正確的。
再考慮另一個例子:
gas = [5,1,2,3,4]
,cost = [4,4,1,5,1]
。i=0
:diff = 1
,currentGas = 1
.i=1
:diff = -3
,currentGas = -2
,start = 2
....
真正的錯誤在於,當
currentGas
變為負數時,我們應該將新的起點設定為當前失敗點的下一個位置。這段程式碼的邏輯沒有錯,但它沒有完全證明其正確性。實際上,如果我們能證明
totalGas >= 0
意味著有解,那麼我們就可以簡化程式碼,因為只需要找到currentGas
第一次變為負數的位置,下一個位置就是最可能的起點。
正確的 PHP 解決方案 (Corrected PHP Solution):
class Solution {
/**
* @param Integer[] $gas
* @param Integer[] $cost
* @return Integer
*/
function canCompleteCircuit($gas, $cost) {
$totalGas = 0;
$currentGas = 0;
$start = 0;
$n = count($gas);
for ($i = 0; $i < $n; $i++) {
$diff = $gas[$i] - $cost[$i];
$totalGas += $diff;
$currentGas += $diff;
// If at any point the current gas is negative,
// it means we can't start from any station before i + 1.
if ($currentGas < 0) {
$currentGas = 0;
$start = $i + 1;
}
}
if ($totalGas < 0) {
return -1;
} else {
return $start;
}
}
}
除錯本類問題的技巧:
數學證明:貪心演算法的正確性往往需要數學證明。在這個問題中,關鍵是證明「如果總汽油量大於等於總消耗量,那麼一定存在一個解」。
狀態變數:用清晰的狀態變數來追蹤你的邏輯。
currentGas
追蹤從當前起點開始的汽油量,totalGas
追蹤整個迴路總的汽油變化量。
貪心演算法 (Greedy)
0376. 擺動序列 (Wiggle Subsequence)
問題描述:如果一個序列中相鄰元素的差嚴格地交替為正和負,則該序列稱為擺動序列。給定一個整數序列,找出其最長子序列的長度,該子序列是一個擺動序列。
正確解題思路概述:
這是一個貪心演算法問題。我們可以從左到右遍歷序列,只保留那些形成「波峰」或「波谷」的元素。
狀態可以分為三種:
up
(上升),down
(下降),flat
(平坦)。只需要計算波峰和波谷的數量。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
/**
* @param Integer[] $nums
* @return Integer
*/
function wiggleMaxLength($nums) {
$n = count($nums);
if ($n <= 1) {
return $n;
}
$result = 1;
$prevDiff = 0;
for ($i = 1; $i < $n; $i++) {
$diff = $nums[$i] - $nums[$i - 1];
if (($diff > 0 && $prevDiff <= 0) || ($diff < 0 && $prevDiff >= 0)) {
$result++;
$prevDiff = $diff;
}
}
return $result;
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯在處理邊界情況時存在缺陷。
錯誤點:當序列中存在重複數字時,
$diff
會是0
。這段程式碼會將所有重複數字視為「平坦」狀態,並正確地忽略它們。但是,當prevDiff
是0
時,如果下一個diff
是非零的,它會被計入。這是正確的。真正的錯誤在於,當序列是單調時,例如
[1, 2, 3, 4]
,它會返回1
。這是錯的,答案應該是2
(例如[1, 4]
)。因為prevDiff
一直是0
,而diff
一直是正數,所以($diff > 0 && $prevDiff <= 0)
這個條件在第一次滿足後,prevDiff
就變成了正數,後續的條件都不會滿足。
正確的 PHP 解決方案 (Corrected PHP Solution):
將 prevDiff 的初始化從 0 改為一個更為保守的值,或者重新設計邏輯。這裡採用雙指針的方法。
class Solution {
/**
* @param Integer[] $nums
* @return Integer
*/
function wiggleMaxLength($nums) {
$n = count($nums);
if ($n <= 1) {
return $n;
}
$up = 1;
$down = 1;
for ($i = 1; $i < $n; $i++) {
if ($nums[$i] > $nums[$i - 1]) {
$up = $down + 1;
} elseif ($nums[$i] < $nums[$i - 1]) {
$down = $up + 1;
}
}
return max($up, $down);
}
}
除錯本類問題的技巧:
狀態機思維:將問題抽象為一個狀態機,例如「上升」、「下降」、「平坦」。
多變數追蹤:使用多個變數來追蹤不同的狀態,例如
up
和down
。邊界條件測試:對於邊界條件,例如單調序列、重複數字序列,進行嚴格的測試。
貪心演算法 (Greedy)
0452. 用最少數量的箭引爆氣球 (Minimum Number of Arrows to Burst Balloons)
問題描述:給定一個氣球陣列,其中每個氣球用一個區間 [start, end]
表示。一支箭可以射穿所有與其路徑相交的氣球。求引爆所有氣球所需的最少箭數量。
正確解題思路概述:
這是一個經典的貪心演算法問題。我們可以將氣球按照結束點排序。然後,從第一個氣球開始,用一支箭引爆它,同時檢查這支箭能引爆多少個後續氣球。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
/**
* @param Integer[][] $points
* @return Integer
*/
function findMinArrowShots($points) {
if (empty($points)) {
return 0;
}
// Sort by start point
usort($points, function($a, $b) {
return $a[0] <=> $b[0];
});
$arrows = 1;
$end = $points[0][1];
for ($i = 1; $i < count($points); $i++) {
if ($points[$i][0] > $end) {
$arrows++;
$end = $points[$i][1];
} else {
$end = min($end, $points[$i][1]);
}
}
return $arrows;
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯在排序上存在一個潛在的陷阱。
錯誤點:它按照開始點進行排序,這在某些情況下是正確的,但在另一些情況下會出錯。例如,
[[10,16],[2,8],[1,6],[7,12]]
。按照開始點排序後是[[1,6],[2,8],[7,12],[10,16]]
。第一支箭:
end = 6
。氣球
[2,8]
:與[1,6]
重疊,end
更新為min(6,8) = 6
。氣球
[7,12]
:與end
不重疊 (7 > 6
),arrows++
,end
變成12
。氣球
[10,16]
:與[7,12]
重疊,end
更新為min(12,16) = 12
。總箭數為
2
。這是正確的。
再考慮這個例子:
[[3,9],[7,12],[8,10]]
。按開始點排序:
[[3,9],[7,12],[8,10]]
。第一支箭:
end = 9
。氣球
[7,12]
:與[3,9]
重疊,end
更新為min(9,12) = 9
。氣球
[8,10]
:與[3,9]
重疊,end
更新為min(9,10) = 9
。總箭數為
1
。這是正確的。
真正的錯誤在於,如果按照結束點排序,貪心策略會更簡單、更直觀。這雖然不是一個邏輯錯誤,但在面試中會被認為是一個次優解。
正確的 PHP 解決方案 (Corrected PHP Solution):
將氣球按照結束點進行排序,這是一個更簡單、更健壯的貪心策略。
class Solution {
/**
* @param Integer[][] $points
* @return Integer
*/
function findMinArrowShots($points) {
if (empty($points)) {
return 0;
}
// Sort by end point
usort($points, function($a, $b) {
return $a[1] <=> $b[1];
});
$arrows = 1;
$end = $points[0][1];
for ($i = 1; $i < count($points); $i++) {
if ($points[$i][0] > $end) {
$arrows++;
$end = $points[$i][1];
}
}
return $arrows;
}
}
除錯本類問題的技巧:
排序策略:在區間問題中,排序策略是貪心演算法的關鍵。要仔細思考是按開始點排序還是按結束點排序,哪種策略能讓貪心選擇更簡單、更優。
邏輯簡化:如果你的貪心演算法看起來很複雜,可能存在一個更簡單的策略,例如更合適的排序方式。
貪心演算法 (Greedy)
0763. 劃分字母區間 (Partition Labels)
問題描述:給定一個字串 s
,將其劃分為盡可能多的區間,使得每個字母只在一個區間中出現。返回這些區間的長度列表。
正確解題思路概述:
這是一個貪心演算法問題。我們從字串的開頭遍歷,對於每個字符,我們找出其在字串中最後出現的位置。當前區間的結束點就是這個最遠的最後出現位置。
帶有錯誤的程式碼 (Buggy Solution):
class Solution {
/**
* @param String $s
* @return Integer[]
*/
function partitionLabels($s) {
$last = [];
for ($i = 0; $i < strlen($s); $i++) {
$last[$s[$i]] = $i;
}
$result = [];
$j = 0;
$max_index = 0;
$start = 0;
for ($i = 0; $i < strlen($s); $i++) {
$max_index = max($max_index, $last[$s[$i]]);
if ($i == $max_index) {
$result[] = $max_index - $start + 1;
$start = $i + 1;
}
}
return $result;
}
}
除錯分析 (Debugging Analysis):
這段程式碼的邏輯是正確的,但存在一個細微的邏輯混淆。
錯誤點:它使用了兩個變數
j
和i
來遍歷,但j
並沒有在迴圈中被使用。這是一種冗餘的程式碼,可能導致混淆。另一個潛在錯誤:儘管邏輯是正確的,但它不是最簡潔的。更簡潔的寫法是只使用一個變數來追蹤當前區間的起始點。
正確的 PHP 解決方案 (Corrected PHP Solution):
用更簡潔的邏輯實現貪心演算法。
class Solution {
/**
* @param String $s
* @return Integer[]
*/
function partitionLabels($s) {
$last = [];
for ($i = 0; $i < strlen($s); $i++) {
$last[ord($s[$i])] = $i;
}
$result = [];
$start = 0;
$end = 0;
for ($i = 0; $i < strlen($s); $i++) {
$end = max($end, $last[ord($s[$i])]);
if ($i === $end) {
$result[] = $end - $start + 1;
$start = $i + 1;
}
}
return $result;
}
}
除錯本類問題的技巧:
簡化狀態:在貪心演算法中,使用最少的變數來追蹤狀態。
明確變數職責:每個變數都應該有清晰的用途,例如
start
追蹤當前區間的開始,end
追蹤當前區間的最遠結束點。邊界條件:確保在迴圈結束後,所有區間都已經被正確處理。
沒有留言:
張貼留言