【LeetCode】Sama的个人记录_30

 

 

【Q337】(md) 打家劫舍
 
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。
这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。
 
如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
 
示例 :

输入: [3,2,3,null,3,null,1]

  3
 / \    
2   3
 \   \ 
  3   1

输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

>>> 立即想到DFS递归
>>> 遍历到某个节点时,小偷有两种选择:1.偷,return本节点的钱财加上孙子节点的钱财; 2.不偷,return两个儿子节点的钱财
>>> 取最大值return
// 【解法一:递归】
class Solution {
    public int rob(TreeNode root) {
        return DFS(root);
    }

    private int DFS(TreeNode node){
        if(node == null){
            return 0;
        }
        return Math.max(DFS(node.left) + DFS(node.right), node.val +
                (node.left == null ? 0 : DFS(node.left.left) + DFS(node.left.right)) +
                (node.right == null ? 0 : DFS(node.right.left) + DFS(node.right.right)));
    }
}
>>> 理解【递归-记忆化递归-动态规划】的内在联系后,立马想到两种优化方式:
>>> 	1.记忆重复子问题
>>>     2.减少栈的使用(极端条件下变成DP)
>>> 这里显然出现了"重复计算子问题",在计算本节点时,递归到了儿子和孙子;而计算儿子时,又递归到了孙子和孙孙子 --- 孙子被计算了两次甚至更多
//【解法二:记忆化递归】
// 这里使用的不是记忆化数组,而是一个记忆化哈希表(节点对象作为key)
class Solution {
    public int rob(TreeNode root) {
        return DFS(root);
    }

    private Map<TreeNode, Integer> memoMap = new HashMap<>();
    private int DFS(TreeNode node){
        if(node == null){
            return 0;
        }
        if(memoMap.containsKey(node)){
            return memoMap.get(node);
        }
        int max =  Math.max(DFS(node.left) + DFS(node.right), node.val +
                (node.left == null ? 0 : DFS(node.left.left) + DFS(node.left.right)) +
                (node.right == null ? 0 : DFS(node.right.left) + DFS(node.right.right)));
        memoMap.put(node, max);
        return max;
    }
}
>>> 我们能不能转化为完全不使用栈的DP呢(函数的调用会消耗大量性能)?
>>> 事实上,递归栈不能被完全避免(这是一个二叉树啊!!!怎么可能脱离DFS??!!)
>>> 但是,我们至少可以保证,除了必要的DFS遍历语句,其他地方不再使用递归栈 --- 这也被称为"树形DP"
//【解法三:树形DP】
//每个节点拥有一个记录状态的dp数组:dp[0]的含义是该节点不偷,可以获得到最大钱财;dp[1]的含义是该节点被偷,获得的最大钱财
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[2];
        }
        int[] dp = new int[2];
        
        int[] dpL = DFS(node.left);
        int[] dpR = DFS(node.right);
        
        dp[0] = Math.max(dpL[0], dpL[1]) + Math.max(dpR[0], dpR[1]);	// 该节点不偷,则儿子节点可以偷也可以不偷(取最每个儿子的最大值)
        dp[1] = node.val + dpL[0] + dpR[0];								// 该节点偷,则儿子节点不能偷
        
        return dp;
    }
}

 

 

【Q696】(ez) 计算二进制子串
 
给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。

重复出现的子串要计算它们出现的次数。
 
示例 :

输入: “00110011”
输出: 6
解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。

class Solution {
    /**
     * 核心思路:cur记录当前数字(0/1)的连续串长度,last记录前一个数字(1/0)的连续串长度,当last>=cur时候,res++
     *
     * 举个例子一看便知:
     * s = "1110001100"
     * last 0003333322
     * cur  1231231212
     * res  0001234567
     */
    public int countBinarySubstrings(String s) {
        int res = 0;
        int last = 0;
        int cur = 1;
        for(int i = 1; i < s.length(); i++){
            if(s.charAt(i) == s.charAt(i - 1)){
                cur++;
            }else{
                last = cur;
                cur = 1;
            }
            if(last >= cur){
                res++;
            }
        }
        return res;
    }
    // 误区:栈(需要双栈才行); 追赶法(类似括号匹配,但不适用)
    // 这两种方法不可行的根源在于:这不是类似于【括号匹配】的左右括号相消的思路,比如001100,11不能和00相消———之后的00还会用到11
}

 
 

 

 

 

 

 

 

 

 

 
 

 

 

Qs from https://leetcode-cn.com
♥ loli suki
♠ end

你可能感兴趣的:(Leetcode)