題目描述
給定一個二叉搜索樹的根節點root
,返回樹中任意兩節點的差的最小值。
示例1:
輸入: root = [4, 2, 6, 1, 3, null, null]
輸出: 1
解釋: 注意,root是樹節點對象(TreeNode object),而不是數組。給定的樹可表示爲下圖:
最小的差值爲1,它是節點1和節點2的差值,也是節點3和節點2的差值。
注意:
- 二叉樹的大小範圍在
2
到100
; - 二叉樹總是有效的,每個節點的值都是整數,且不重複;
- 本題與530題相同;
思路分析
題目難度爲簡單 ,二叉搜索樹的特點爲節點坐邊的數值均小於本節點值;右子樹的值均大於本節點值,這裏需要求解任意兩節點的差的最小值,我們易知其最小值必出現在相鄰節點,故這裏直接思考:
- 根節點與左右節點的差值;
- 多個差值之間的比較,得出最小值;
這裏需要的就是遍歷樹,一個節點最多存在兩個可比較的值:root.val - root.left.val, root.right.val - root.val
,題幹中給出樹的高度有限,故我們這裏採用簡單的遞歸解法,簡單來說就是:
- 判斷當前節點是否爲空,若爲空返回
Integer.MAX_VALUE
; - 判斷當前節點的左右節點是否均不存在,若是返回
Integer.MAX_VALUE
; - 返回 當前節點的左右節點差值較小值和左右子樹的差值較小值 中的最小值,即爲輸出;
根據以上思考,我們給出核心的判斷代碼:
if (root == null || (root.left == null && root.right == null)) {
return Integer.MAX_VALUE;
}
int i1 = root.left == null ? Integer.MAX_VALUE : root.val - root.left.val;
int i2 = root.right == null ? Integer.MAX_VALUE : root.right.val - root.val;
int i3 = solutionRecursive(root.left);
int i4 = solutionRecursive(root.right);
return Math.min(Math.min(i1, i2), Math.min(i3, i4));
上面的寫法是爲了更好的理解,這裏我們進一步簡化代碼則有:
解題代碼
public static int solutionRecursive(TreeNode root) {
if (root == null || (root.left == null && root.right == null)) {
return Integer.MAX_VALUE;
}
return Math.min(Math.min((root.left == null ? Integer.MAX_VALUE
: root.val - root.left.val),
(root.right == null ? Integer.MAX_VALUE
: root.right.val - root.val)),
Math.min(solutionRecursive(root.left),
solutionRecursive(root.right))
);
}
複雜度分析
設n
爲樹的節點個數,h
爲樹的高度:
時間複雜度: 這裏對每個節點進行了一次訪問,時間複雜度爲O(n)
;
空間複雜度: 沒有藉助輔助容器,但是因是遞歸解法,內存中存在棧空間的使用,故空間複雜度爲O(h)
。
小結
在本博客類似的算法文章中提過,樹這種數據類型特別適合遞歸求解,在現實業務場景中,樹的出現更多的用在檢索需求中,這就使得樹的高度不會太高;數據庫中甚至想要提高檢索效率使用如B樹、B+樹此類的多叉樹來減少I/O,從而提升系統效率和檢索速度; 這樣的也無需求催生下的樹結構,不會太高,這樣也爲遞歸這樣的算法求解方式提供了一定的適用條件和參考價值。
這裏簡單說一下適合遞歸的問題具有的特點:
- 總問題可以細分爲若干個有限的小問題,且小問題之間並沒有相干性;
- 總問題和小問題的求解方式是相同的;
- 確保遞歸的函數棧空間不會無限增長或超出題目要求;
Github源碼
完整可運行文件請訪問GitHub。