日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

还要打家劫舍

發布時間:2024/4/18 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 还要打家劫舍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

思路

這道題目和 198.打家劫舍,213.打家劫舍II也是如出一轍,只不過這個換成了樹。

對于樹的話,首先就要想到遍歷方式,前中后序(深度優先搜索)還是層序遍歷(廣度優先搜索)。

本題一定是要后序遍歷,因為通過遞歸函數的返回值來做下一步計算

與198.打家劫舍,213.打家劫舍II一樣,關鍵是要討論當前節點搶還是不搶。

如果搶了當前節點,兩個孩子就不能搶,如果沒搶當前節點,就可以考慮搶左右孩子(注意這里說的是“考慮”

暴力遞歸

代碼如下:

class Solution { public:int rob(TreeNode* root) {if (root == NULL) return 0;if (root->left == NULL && root->right == NULL) return root->val;// 偷父節點int val1 = root->val;if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 跳過root->left,相當于不考慮左孩子了if (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 跳過root->right,相當于不考慮右孩子了// 不偷父節點int val2 = rob(root->left) + rob(root->right); // 考慮root的左右孩子return max(val1, val2);} }; * 時間復雜度:O(n^2) 這個時間復雜度不太標準,也不容易準確化,例如越往下的節點重復計算次數就越多 * 空間復雜度:O(logn) 算上遞推系統棧的空間

當然以上代碼超時了,這個遞歸的過程中其實是有重復計算了。

我們計算了root的四個孫子(左右孩子的孩子)為頭結點的子樹的情況,又計算了root的左右孩子為頭結點的子樹的情況,計算左右孩子的時候其實又把孫子計算了一遍。

記憶化遞推

所以可以使用一個map把計算過的結果保存一下,這樣如果計算過孫子了,那么計算孩子的時候可以復用孫子節點的結果。

代碼如下:

class Solution { public:unordered_map<TreeNode* , int> umap; // 記錄計算過的結果int rob(TreeNode* root) {if (root == NULL) return 0;if (root->left == NULL && root->right == NULL) return root->val;if (umap[root]) return umap[root]; // 如果umap里已經有記錄則直接返回// 偷父節點int val1 = root->val;if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 跳過root->leftif (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 跳過root->right// 不偷父節點int val2 = rob(root->left) + rob(root->right); // 考慮root的左右孩子umap[root] = max(val1, val2); // umap記錄一下結果return max(val1, val2);} }; * 時間復雜度:O(n) * 空間復雜度:O(logn) 算上遞推系統棧的空間

動態規劃

在上面兩種方法,其實對一個節點 投與不投得到的最大金錢都沒有做記錄,而是需要實時計算。

而動態規劃其實就是使用狀態轉移容器來記錄狀態的變化,這里可以使用一個長度為2的數組,記錄當前節點偷或不偷所得到的最大金錢。

1、 確定遞歸函數的參數和返回值

這里我們要求一個節點 偷與不偷的兩個狀態所得到的金錢,那么返回值就是一個長度為2的數組。

參數為當前節點,代碼如下:

vector<int> robTree(TreeNode* cur)

其實這里的返回數組就是dp數組。

所以dp數組(dp table)以及下標的含義:

下標為0記錄不偷該節點所得到的最大金錢,下標為1記錄偷該節點所得到的最大金錢。

所以本題dp數組就是一個長度為2的數組!

那么有同學可能疑惑,長度為2的數組怎么標記樹中每個節點的狀態呢?

別忘了在遞歸的過程中,系統棧會保存每一層遞歸的參數。

如果還不理解的話,就接著往下看,看到代碼就理解了哈。

2. 確定終止條件

在遍歷的過程中,如果遇到空結點的話,很明顯,無論偷還是不偷都是0,所以就返回

if (cur == NULL) return vector<int>{0, 0};

這也相當于dp數組的初始化

3. 確定遍歷順序

首先明確的是使用后序遍歷。 因為通過遞歸函數的返回值來做下一步計算。

通過遞歸左節點,得到左節點偷與不偷的金錢。

通過遞歸右節點,得到右節點偷與不偷的金錢。

代碼如下:

// 下標0:不偷,下標1:偷 vector<int> left = robTree(cur->left); // 左 vector<int> right = robTree(cur->right); // 右 // 中

4. 確定單層遞歸的邏輯

如果是偷當前節點,那么左右孩子就不能偷

val1 = cur->val + left[0] + right[0];

如果對下標含義不理解就在回顧一下dp數組的含義

如果不偷當前節點,那么左右孩子就可以偷,至于到底偷不偷一定是選一個最大的val2 = max(left[0], left[1]) + max(right[0], right[1]);

最后當前節點的狀態就是{val2, val1}; 即:{不偷當前節點得到的最大金錢,偷當前節點得到的最大金錢}

代碼如下:

vector<int> left = robTree(cur->left); // 左 vector<int> right = robTree(cur->right); // 右// 偷cur int val1 = cur->val + left[0] + right[0]; // 不偷cur int val2 = max(left[0], left[1]) + max(right[0], right[1]); return {val2, val1};

5. 舉例推導dp數組

以示例1為例,dp數組狀態如下:(注意用后序遍歷的方式推導

最后頭結點就是 取下標0 和 下標1的最大值就是偷得的最大金錢。

遞歸三部曲與動規五部曲分析完畢,C++代碼如下:

/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*///左孩子準備兩個數組dp[0],dp[1],然后返回來告訴父親,不選我得到dp[0],選我得到dp[1]//右孩子也是如此 class Solution { public:// 長度為2的數組,0:不偷,1:偷vector<int> traversal(TreeNode* cur){if(cur==nullptr) return {0,0};vector<int> left=traversal(cur->left);vector<int> right=traversal(cur->right);// 偷curint val1=cur->val+left[0]+right[0];// 不偷curint val2=max(left[0],left[1])+max(right[0],right[1]);return {val2,val1};}int rob(TreeNode* root) {vector<int> res=traversal(root);return max(res[0],res[1]);} }; * 時間復雜度:O(n) 每個節點只遍歷了一次 * 空間復雜度:O(logn) 算上遞推系統棧的空間

總結

這道題是樹形DP的入門題目,所謂樹形DP就是在樹上進行遞歸公式的推導。

所以樹形DP也沒有那么神秘!

只不過平時我們習慣了在一維數組或者二維數組上推導公式,一下子換成了樹,就需要對樹的遍歷方式足夠了解!

總結

以上是生活随笔為你收集整理的还要打家劫舍的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。