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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[Leetcode][第337题][JAVA][打家劫舍3][递归][动态规划]

發(fā)布時(shí)間:2023/12/10 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [Leetcode][第337题][JAVA][打家劫舍3][递归][动态规划] 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

【問題描述】[中等]

【解答思路】

1. 動態(tài)規(guī)劃

第 1 步:狀態(tài)定義
dp[node][j] :這里 node 表示一個(gè)結(jié)點(diǎn),以 node 為根結(jié)點(diǎn)的樹,并且規(guī)定了 node 是否偷取能夠獲得的最大價(jià)值。

j = 0 表示 node 結(jié)點(diǎn)不偷取;
j = 1 表示 node 結(jié)點(diǎn)偷取。
第 2 步: 推導(dǎo)狀態(tài)轉(zhuǎn)移方程
根據(jù)當(dāng)前結(jié)點(diǎn)偷或者不偷,就決定了需要從哪些子結(jié)點(diǎn)里的對應(yīng)的狀態(tài)轉(zhuǎn)移過來。

如果當(dāng)前結(jié)點(diǎn)不偷,左右子結(jié)點(diǎn)偷或者不偷都行,選最大者;
如果當(dāng)前結(jié)點(diǎn)偷,左右子結(jié)點(diǎn)均不能偷。
(狀態(tài)轉(zhuǎn)移方程的表述有點(diǎn)復(fù)雜,請大家直接看代碼。)

第 3 步: 初始化
一個(gè)結(jié)點(diǎn)都沒有,空節(jié)點(diǎn),返回 0,對應(yīng)后序遍歷時(shí)候的遞歸終止條件;

第 4 步: 輸出
在根結(jié)點(diǎn)的時(shí)候,返回兩個(gè)狀態(tài)的較大者。

第 5 步: 思考優(yōu)化空間
優(yōu)化不了。
時(shí)間復(fù)雜度:O(N) 空間復(fù)雜度:O(N)

public class Solution {// 樹的后序遍歷public int rob(TreeNode root) {int[] res = dfs(root);return Math.max(res[0], res[1]);}private int[] dfs(TreeNode node) {if (node == null) {return new int[]{0, 0};}// 分類討論的標(biāo)準(zhǔn)是:當(dāng)前結(jié)點(diǎn)偷或者不偷// 由于需要后序遍歷,所以先計(jì)算左右子結(jié)點(diǎn),然后計(jì)算當(dāng)前結(jié)點(diǎn)的狀態(tài)值int[] left = dfs(node.left);int[] right = dfs(node.right);// dp[0]:以當(dāng)前 node 為根結(jié)點(diǎn)的子樹能夠偷取的最大價(jià)值,規(guī)定 node 結(jié)點(diǎn)不偷// dp[1]:以當(dāng)前 node 為根結(jié)點(diǎn)的子樹能夠偷取的最大價(jià)值,規(guī)定 node 結(jié)點(diǎn)偷int[] dp = new int[2];dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);dp[1] = node.val + left[0] + right[0];return dp;} }
2. 遞歸

使用爺爺、兩個(gè)孩子、4 個(gè)孫子來說明問題
首先來定義這個(gè)問題的狀態(tài)
爺爺節(jié)點(diǎn)獲取到最大的偷取的錢數(shù)呢

  • 首先要明確相鄰的節(jié)點(diǎn)不能偷,也就是爺爺選擇偷,兒子就不能偷了,但是孫子可以偷
  • 二叉樹只有左右兩個(gè)孩子,一個(gè)爺爺最多 2 個(gè)兒子,4 個(gè)孫子
    根據(jù)以上條件,我們可以得出單個(gè)節(jié)點(diǎn)的錢該怎么算
    4 個(gè)孫子偷的錢 + 爺爺?shù)腻X VS 兩個(gè)兒子偷的錢 哪個(gè)組合錢多,就當(dāng)做當(dāng)前節(jié)點(diǎn)能偷的最大錢數(shù)。這就是動態(tài)規(guī)劃里面的最優(yōu)子結(jié)構(gòu)
    由于是二叉樹,這里可以選擇計(jì)算所有子節(jié)點(diǎn)
  • 4 個(gè)孫子投的錢加上爺爺?shù)腻X如下
    int method1 = root.val + rob(root.left.left) + rob(root.left.right) + rob(root.right.left) + rob(root.right.right)
    兩個(gè)兒子偷的錢如下
    int method2 = rob(root.left) + rob(root.right);
    挑選一個(gè)錢數(shù)多的方案則
    int result = Math.max(method1, method2);

    2.1暴力遞歸

    public int rob(TreeNode root) {if (root == null) return 0;int money = root.val;if (root.left != null) {money += (rob(root.left.left) + rob(root.left.right));}if (root.right != null) {money += (rob(root.right.left) + rob(root.right.right));}return Math.max(money, rob(root.left) + rob(root.right)); }

    2.2 記憶化遞歸
    針對2.1中速度太慢的問題,經(jīng)過分析其實(shí)現(xiàn),我們發(fā)現(xiàn)爺爺在計(jì)算自己能偷多少錢的時(shí)候,同時(shí)計(jì)算了 4 個(gè)孫子能偷多少錢,也計(jì)算了 2 個(gè)兒子能偷多少錢。這樣在兒子當(dāng)爺爺時(shí),就會產(chǎn)生重復(fù)計(jì)算一遍孫子節(jié)點(diǎn)。

    于是乎我們發(fā)現(xiàn)了一個(gè)動態(tài)規(guī)劃的關(guān)鍵優(yōu)化點(diǎn)

    我們這一步針對重復(fù)子問題進(jìn)行優(yōu)化,我們在做斐波那契數(shù)列時(shí),使用的優(yōu)化方案是記憶化,但是之前的問題都是使用數(shù)組解決的,把每次計(jì)算的結(jié)果都存起來,下次如果再來計(jì)算,就從緩存中取,不再計(jì)算了,這樣就保證每個(gè)數(shù)字只計(jì)算一次。
    由于二叉樹不適合拿數(shù)組當(dāng)緩存,我們這次使用哈希表來存儲結(jié)果,TreeNode 當(dāng)做 key,能偷的錢當(dāng)做 value

    public int rob(TreeNode root) { //hashmap 記憶化HashMap<TreeNode, Integer> memo = new HashMap<>();return robInternal(root, memo); }public int robInternal(TreeNode root, HashMap<TreeNode, Integer> memo) {if (root == null) return 0;if (memo.containsKey(root)) return memo.get(root);int money = root.val;if (root.left != null) {money += (robInternal(root.left.left, memo) + robInternal(root.left.right, memo));}if (root.right != null) {money += (robInternal(root.right.left, memo) + robInternal(root.right.right, memo));}int result = Math.max(money, robInternal(root.left, memo) + robInternal(root.right, memo));///!!!memo.put(root, result);return result; }

    【總結(jié)】

    1. 動態(tài)規(guī)劃流程

    第 1 步:設(shè)計(jì)狀態(tài)
    第 2 步:狀態(tài)轉(zhuǎn)移方程
    第 3 步:考慮初始化
    第 4 步:考慮輸出
    第 5 步:考慮是否可以狀態(tài)壓縮

    2.遞歸 遞推公式要找準(zhǔn) 后用記憶化搜索優(yōu)化 這題不能用BFS,層次遍歷不符合題解

    轉(zhuǎn)載鏈接:https://leetcode-cn.com/problems/house-robber-iii/solution/san-chong-fang-fa-jie-jue-shu-xing-dong-tai-gui-hu/
    轉(zhuǎn)載鏈接:https://leetcode-cn.com/problems/house-robber-iii/solution/shu-xing-dp-ru-men-wen-ti-by-liweiwei1419/

    總結(jié)

    以上是生活随笔為你收集整理的[Leetcode][第337题][JAVA][打家劫舍3][递归][动态规划]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。