題目描述(簡單難度)
給定一個sum
,判斷是否有一條從根節點到葉子節點的路徑,該路徑上所有數字的和等於sum
。
解法一 遞歸
這道題其實和 111 題 是一樣的,大家可以先看 111 題 的分析,這道題無非是把 111 題 遞歸傳遞的depth
改爲了sum
的傳遞。
如果不仔細分析題目,代碼可能會寫成下邊的樣子。
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
return hasPathSumHelper(root, sum);
}
private boolean hasPathSumHelper(TreeNode root, int sum) {
if (root == null) {
return sum == 0;
}
return hasPathSumHelper(root.left, sum - root.val) || hasPathSumHelper(root.right, sum - root.val);
}
看起來沒什麼問題,並且對於題目給的樣例也是沒問題的。但是對於下邊的樣例:
3
/ \
9 20
/ / \
8 15 7
sum = 12
當某個子樹只有一個孩子的時候,就會出問題了,可以看 111 題 的分析。
所以代碼需要寫成下邊的樣子。
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
return hasPathSumHelper(root, sum);
}
private boolean hasPathSumHelper(TreeNode root, int sum) {
//到達葉子節點
if (root.left == null && root.right == null) {
return root.val == sum;
}
//左孩子爲 null
if (root.left == null) {
return hasPathSumHelper(root.right, sum - root.val);
}
//右孩子爲 null
if (root.right == null) {
return hasPathSumHelper(root.left, sum - root.val);
}
return hasPathSumHelper(root.left, sum - root.val) || hasPathSumHelper(root.right, sum - root.val);
}
解法二 BFS
同樣的,我們可以利用一個隊列對二叉樹進行層次遍歷。同時還需要一個隊列,保存當前從根節點到當前節點已經累加的和。BFS
的基本框架不用改變,參考 102 題。只需要多一個隊列,進行細微的改變即可。
public boolean hasPathSum(TreeNode root, int sum) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
Queue<Integer> queueSum = new LinkedList<Integer>();
if (root == null)
return false;
queue.offer(root);
queueSum.offer(root.val);
while (!queue.isEmpty()) {
int levelNum = queue.size(); // 當前層元素的個數
for (int i = 0; i < levelNum; i++) {
TreeNode curNode = queue.poll();
int curSum = queueSum.poll();
if (curNode != null) {
//判斷葉子節點是否滿足了條件
if (curNode.left == null && curNode.right == null && curSum == sum) {
return true;
}
//當前節點和累計的和加入隊列
if (curNode.left != null) {
queue.offer(curNode.left);
queueSum.offer(curSum + curNode.left.val);
}
if (curNode.right != null) {
queue.offer(curNode.right);
queueSum.offer(curSum + curNode.right.val);
}
}
}
}
return false;
}
解法三 DFS
解法一其實本質上就是做了DFS
,我們知道DFS
可以用棧去模擬。對於這道題,我們可以像解法二的BFS
一樣,再增加一個棧,去保存從根節點到當前節點累計的和就可以了。
這裏的話,用DFS
裏的中序遍歷,參考 94 題。
public boolean hasPathSum(TreeNode root, int sum) {
Stack<TreeNode> stack = new Stack<>();
Stack<Integer> stackSum = new Stack<>();
TreeNode cur = root;
int curSum = 0;
while (cur != null || !stack.isEmpty()) {
// 節點不爲空一直壓棧
while (cur != null) {
stack.push(cur);
curSum += cur.val;
stackSum.push(curSum);
cur = cur.left; // 考慮左子樹
}
// 節點爲空,就出棧
cur = stack.pop();
curSum = stackSum.pop();
//判斷是否滿足條件
if (curSum == sum && cur.left == null && cur.right == null) {
return true;
}
// 考慮右子樹
cur = cur.right;
}
return false;
}
但是之前講了,對於這種利用棧完全模擬遞歸的思路,對時間複雜度和空間複雜度並沒有什麼提高。只是把遞歸傳遞的參數root
和sum
,本該由計算機自動的壓棧出棧,由我們手動去壓棧出棧了。
所以我們能不能提高一下,比如省去sum
這個棧?讓我們來分析以下。參考 這裏 。
我們如果只用一個變量curSum
來記錄根節點到當前節點累計的和,有節點入棧就加上節點的值,有節點出棧就減去節點的值。
比如對於下邊的樹,我們進行中序遍歷。
3
/ \
9 20
/ \
8 15
curSum = 0
3 入棧, curSum = 3,3
9 入棧, curSum = 12,3 -> 9
8 入棧, curSum = 20, 3 -> 9 -> 8
8 出棧, curSum = 12, 3 -> 9
9 出棧, curSum = 3,
15 入棧, curSum = 18, 3 -> 9 -> 15
此時路徑是 3 -> 9 -> 15
,和應該是 27
。但我們得到的是 18
,少加了 9
。
原因就是我們進行的是中序遍歷,當我們還沒訪問右邊的節點的時候,根節點已經出棧了,再訪問右邊節點的時候,curSum
就會少一個根節點的值。
所以,我們可以用後序遍歷,先訪問左子樹,再訪問右子樹,最後訪問根節點。再看一下上邊的問題。
3
/ \
9 20
/ \
8 15
curSum = 0
3 入棧, curSum = 3,3
9 入棧, curSum = 12,3 -> 9
8 入棧, curSum = 20, 3 -> 9 -> 8
8 出棧, curSum = 12, 3 -> 9
15 入棧, curSum = 27, 3 -> 9 -> 15
此時路徑 3 -> 9 -> 15
對應的 curSum
就是正確的了。
用棧實現後序遍歷,比中序遍歷要複雜一些。當訪問到根節點的時候,它的右子樹可能訪問過了,那就把根節點輸出。它的右子樹可能沒訪問過,我們需要去遍歷它的右子樹。所以我們要用一個變量pre
保存上一次遍歷的節點,用來判斷當前根節點的右子樹是否已經遍歷完成。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> toVisit = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
while (cur != null || !toVisit.isEmpty()) {
while (cur != null) {
toVisit.push(cur); // 添加根節點
cur = cur.left; // 遞歸添加左節點
}
cur = toVisit.peek(); // 已經訪問到最左的節點了
// 在不存在右節點或者右節點已經訪問過的情況下,訪問根節點
if (cur.right == null || cur.right == pre) {
toVisit.pop();
result.add(cur.val);
pre = cur;
cur = null;
} else {
cur = cur.right; // 右節點還沒有訪問過就先訪問右節點
}
}
return result;
}
有了上邊的後序遍歷,對於這道題,代碼就很好改了。
public boolean hasPathSum(TreeNode root, int sum) {
Stack<TreeNode> toVisit = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
int curSum = 0; //記錄當前的累計的和
while (cur != null || !toVisit.isEmpty()) {
while (cur != null) {
toVisit.push(cur); // 添加根節點
curSum += cur.val;
cur = cur.left; // 遞歸添加左節點
}
cur = toVisit.peek(); // 已經訪問到最左的節點了
//判斷是否滿足條件
if (curSum == sum && cur.left == null && cur.right == null) {
return true;
}
// 在不存在右節點或者右節點已經訪問過的情況下,訪問根節點
if (cur.right == null || cur.right == pre) {
TreeNode pop = toVisit.pop();
curSum -= pop.val; //減去出棧的值
pre = cur;
cur = null;
} else {
cur = cur.right; // 右節點還沒有訪問過就先訪問右節點
}
}
return false;
}
總
這道題還是在考二叉樹的遍歷,DFS
,BFS
。解法三通過後序遍歷節省了sum
棧,蠻有意思的。
更多詳細通俗題解詳見 leetcode.wang 。