二叉樹翻轉是一道比較簡單,但是又非常有意思的演算法問題,當然了,對於應屆生或是正在學習演算法的學生來說,這道題顯然是非常簡單的,但對於有著多年技術經驗的工程師,可能就不一定能寫的出來(我指的是白板面試這類在紙類介質上進行書寫的情況。)
1. 題目
話不多說,我們直接看題:
Invert a binary tree.
4
/
2 7
/ /
1 3 6 9
to
4
/
7 2
/ /
9 6 3 1複製程式碼
這個問題的文字描述,簡單說來就是將二叉樹的左右子樹全部翻轉過來,使原二叉樹的左子樹稱為新二叉樹的右子樹。
這個問題看起來是不是很簡單?但有趣的是,有人是這樣評價這個問題:
Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so fuck off.
產生這種問題的原因可能是多樣的,不過無論是什麼原因,對於一個應屆生或是正在學習演算法的學生來說,都是必須要忽略的,因為如果不會寫演算法對應的程式碼,我們又如何判斷你是真的懂這個演算法?總不能讓我相信你的一面之詞不是?
2. 解決程式碼
//這是遞迴方法
TreeNode* invertTree(TreeNode* root) {
if (!root) return nullptr;
else {
TreeNode *temp = root->left;
root->left = root->right;
root->right = temp;
}
invertTree(root->left);
invertTree(root->right);
return root;
}
//進行遞迴簡化
TreeNode* invertTree(TreeNode* root) {
if (root) {
invertTree(root->left);
invertTree(root->right);
std::swap(root->left, root->right);
}
return root;
}
//非遞迴形式@chammika
TreeNode* invertTree(TreeNode* root) {
std::stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* p = stk.top();
stk.pop();
if (p) {
stk.push(p->left);
stk.push(p->right);
std::swap(p->left, p->right);
}
}
return root;
}複製程式碼
3. 解題思路
這一道題,一眼看過去,我們立刻就能得到的資訊是:
- 這是一個二叉樹,與二叉樹有關的演算法基本都與遞迴相關聯。
- 這個二叉樹是以連結串列形式實現的。
- 翻轉,將左子樹變為右子樹,右子樹變為左子樹。
- 翻轉的操作僅需改變雙親指標。
簡化我們所得到的資訊,即得到,我們需要通過遞迴來改變雙親的指標以實現翻轉操作。
其實這道題算的上是一道應用分治思想的典型題了,我們按照如下步驟進行演算法的編寫:
- 分解問題:二叉樹的翻轉涉及到左右子樹,故而我們可以採用二分的方式進行分解,先進行左子樹的遍歷翻轉,再進行右子樹的遍歷翻轉。
- 求解問題:當我們一次遍歷到某一為nullptr的葉結點時,返回上一非空結點,交換這一非空結點的左右子樹,此為遞迴時的出口。
- 合併問題:尋著遞迴鏈向上回溯,針對每一個分解後的問題進行合併(無相關程式碼,由遞迴性質決定)。
綜上,我們可以看到,先設計一個遞迴出口,何時能夠讓遞迴終止if (!root) return nullptr;
此為出口的一種,我們也可以將遞迴出口設定在最後,此時我們需要設定的是滿足什麼條件才能開始遞迴if (root) { /* ... */ }
條件不滿足時,我們可以開始遞迴的回溯return root
。
總的來說,這道題還是非常簡單的(手動滑稽)。