动态规划-不连续最大子序列和-打家劫舍系列-1388. 3n 块披萨
2020-03-24 17:49:58
198. 打家劫舍
问题描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
问题求解:
每个屋子有两个状态,一个被偷了一个是没被偷,我们可以创建一个二维的memo来存储状态。
dp[i][0]:到第i个位置,第i个位置没有被偷的最大金额
dp[i][1]:到第i个位置,第i个位置被偷的最大金额
初始化:dp[0][0] = 0,dp[0][1] = nums[0]
转移方程:dp[i][0] = Math.max(dp[i -1][1], dp[i - 1][0])
dp[i][1] = dp[i - 1][0] + nums[i]
时间复杂度:O(n)
public int rob(int[] nums) { if (nums.length == 0) return 0; int n = nums.length; int[][] dp = new int[n][2]; dp[0][1] = nums[0]; for (int i = 1; i < n; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); dp[i][1] = dp[i - 1][0] + nums[i]; } return Math.max(dp[n - 1][0], dp[n - 1][1]); }
213. 打家劫舍 II
问题描述:
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
问题求解:
上面一题的升级版本,将问题划分成两种状态去依次求解即可。
时间复杂度:O(n)
public int rob(int[] nums) { int n = nums.length; if (n == 0) return 0; if (n == 1) return nums[0]; return Math.max(helper(Arrays.copyOfRange(nums, 0, n - 1)), helper(Arrays.copyOfRange(nums, 1, n))); } private int helper(int[] nums) { int n = nums.length; int[][] dp = new int[n][2]; dp[0][1] = nums[0]; for (int i = 1; i < n; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); dp[i][1] = dp[i - 1][0] + nums[i]; } return Math.max(dp[n - 1][0], dp[n - 1][1]); }
337. 打家劫舍 III
问题描述:
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
问题求解:
树上动归,逻辑和前面保持一致。
时间复杂度:O(n)
int res = 0; public int rob(TreeNode root) { helper(root); return res; } private int[] helper(TreeNode root) { if (root == null) return new int[]{0, Integer.MIN_VALUE}; int[] l = helper(root.left); int[] r = helper(root.right); int[] curr = new int[2]; curr[0] = Math.max(l[0], l[1]) + Math.max(r[0], r[1]); curr[1] = root.val + l[0] + r[0]; res = Math.max(res, Math.max(curr[0], curr[1])); return curr; }
1388. 3n 块披萨
问题描述:
给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨:
你挑选 任意 一块披萨。
Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨。
Bob 将会挑选你所选择的披萨顺时针方向的下一块披萨。
重复上述过程直到没有披萨剩下。
每一块披萨的大小按顺时针方向由循环数组 slices 表示。
请你返回你可以获得的披萨大小总和的最大值。
示例 1:
输入:slices = [1,2,3,4,5,6]
输出:10
解释:选择大小为 4 的披萨,Alice 和 Bob 分别挑选大小为 3 和 5 的披萨。然后你选择大小为 6 的披萨,Alice 和 Bob 分别挑选大小为 2 和 1 的披萨。你获得的披萨总大小为 4 + 6 = 10 。
示例 2:
输入:slices = [8,9,8,6,1,1]
输出:16
解释:两轮都选大小为 8 的披萨。如果你选择大小为 9 的披萨,你的朋友们就会选择大小为 8 的披萨,这种情况下你的总和不是最大的。
示例 3:
输入:slices = [4,1,2,5,8,3,1,9,7]
输出:21
示例 4:
输入:slices = [3,1,2]
输出:3
提示:
1 <= slices.length <= 500
slices.length % 3 == 0
1 <= slices[i] <= 1000
问题求解:
该问题可以转化为求不连续n / 3长度子序列的最大和问题。状态转移方程和上面的基本一致,只是多了一个取得个数的维度。
时间复杂度:O(n ^ 2)
public int maxSizeSlices(int[] slices) { int n = slices.length; return Math.max(helper(Arrays.copyOfRange(slices, 0, n - 1)), helper(Arrays.copyOfRange(slices, 1, n))); } private int helper(int[] nums) { int n = nums.length; int k = (n + 1) / 3; int[][][] dp = new int[n][k + 1][2]; dp[0][1][0] = nums[0]; for (int i = 1; i < n; i++) { for (int j = 1; j <= Math.min(k, (i + 2) / 2); j++) { dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1]); dp[i][j][1] = dp[i - 1][j - 1][0] + nums[i]; } } return Math.max(dp[n - 1][k][0], dp[n - 1][k][1]); }