打家劫舍问题

打家劫舍1

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额

分析:

  1. 状态:当前抢第几家
  2. 选择:抢或不抢
  3. dp[i]: 前i家能抢到的最高金额,转移方程 dp[i] = max(dp[i-1], dp[i-2] + val[i-2]) // 有偏移
  4. Base case: dp[0] = 0
int robber(vector<int> val) {
  int dp_i_1 = 0, dp_i_2 = 0, dp_i = 0;
  for (int i = 2; i < val.size() + 2; i++) {
    dp_i = max(dp_i_1, dp_i_2 + val[i - 2]);
    dp_i_2 = dp_i_1;
    dp_i_1 = dp_i;
  }

  return dp_i;
}

打家劫舍2: 环形数组

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。

分析:

首先,首尾房间不能同时被抢,那么只可能有三种不同情况:要么都不被抢;要么第一间房子被抢最后一间不抢;要么最后一间房子被抢第一间不抢。(后两种情况,包含了第一种情况)

int cycleRobber(vector<int> val) {
  vector<int> val1(val.begin(), val.end() - 1);
  vector<int> val2(val.begin() + 1, val.end());
  return max(robber(val1), robber(val2));
}

打家劫舍3: 二叉树

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

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

后序遍历:

int rob(TreeNode root) {
    int[] res = dp(root);
    return Math.max(res[0], res[1]);
}

/* 返回一个大小为 2 的数组 arr
arr[0] 表示不抢 root 的话,得到的最大钱数
arr[1] 表示抢 root 的话,得到的最大钱数 */
int[] dp(TreeNode root) {
    if (root == null)
        return new int[]{0, 0};
    int[] left = dp(root.left);
    int[] right = dp(root.right);
    // 抢,下家就不能抢了
    int rob = root.val + left[0] + right[0];
    // 不抢,下家可抢可不抢,取决于收益大小
    int not_rob = Math.max(left[0], left[1])
                + Math.max(right[0], right[1]);

    return new int[]{not_rob, rob};
}