秋招算法之leetcode动态规划(easy11题)
1.打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额
class Solution {
public int rob(int[] nums) {
int len = nums.length;
if(len == 0)
return 0;
int[] dp = new int[len + 1];
dp[0] = 0;
dp[1] = nums[0];
for(int i = 2; i <= len; i++) {
//dp[i-1]是偷隔壁邻居的价值,dp[i-2] + nums[i-1]是计算前一次加这一次偷下来的价值
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1]);
}
return dp[len];
}
}
337. 打家劫舍 III
难度中等558收藏分享切换为英文关注反馈
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 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.
每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷
-
当前节点选择偷时,那么两个孩子节点就不能选择偷了
-
当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
我们使用一个大小为 2 的数组来表示 int[] res = new int[2] 0 代表不偷,1 代表偷
任何一个节点能偷到的最大钱的状态可以定义为
当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
表示为公式如下
root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) + Math.max(rob(root.right)[0], rob(root.right)[1])
root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int rob(TreeNode root) {
int[] result = robInternal(root);
return Math.max(result[0], result[1]);
}
public int[] robInternal(TreeNode root) {
if (root == null) return new int[2];
int[] result = new int[2];
int[] left = robInternal(root.left); //左孩子的数组
int[] right = robInternal(root.right); //右孩子的数组
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
result[1] = left[0] + right[0] + root.val;
return result;
}
}
面试题 17.16. 按摩师
一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
**注意:**本题相对原题稍作改动
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。
示例 3:
输入: [2,1,4,5,3,1,1,3]
输出: 12
解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。
思路: 状态转移,用二维数组代表第I个位置得客人,0代表不接,1代表接
要么接要么不接,所以有两个状态,我们从中选择最大得
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);//这一单不接=前一单接或者前一单不接得最大值
dp[i][1] = dp[i - 1][0] + nums[i]; //这一单接=前一单不接+当前单得分钟
public class Solution {
public int massage(int[] nums) {
int len = nums.length;
if (len == 0) {
return 0;
}
if (len == 1) {
return nums[0];
}
// dp[i][0]:区间 [0, i] 里接受预约请求,并且下标为 i 的这一天不接受预约的最大时长
// dp[i][1]:区间 [0, i] 里接受预约请求,并且下标为 i 的这一天接受预约的最大时长
int[][] dp = new int[len][2];
dp[0][0] = 0;
dp[0][1] = nums[0];
for (int i = 1; i < len; 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[len - 1][0], dp[len - 1][1]);
}
public static void main(String[] args) {
Solution solution = new Solution();
// int[] nums = {1, 2, 3, 1};
// int[] nums = {2, 7, 9, 3, 1};
int[] nums = {2, 1, 4, 5, 3, 1, 1, 3};
int res = solution.massage(nums);
System.out.println(res);
}
}
647. 回文子串
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2:
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
思路: 主要是dp[i][j] = dp[i+1][j-1]; 外围相等的字符,得看里面字符回文状态
至于为什么要倒三角,因为我们从dp[i][j]能倒着退回上一步状态
class Solution {
public int countSubstrings(String s) {
if(s == null || s.equals("")){
return 0;
}
int n = s.length();
boolean[][] dp = new boolean[n][n];
int result = s.length();
for(int i = 0; i<n; i++) dp[i][i] = true;
for(int i = n-1; i>=0; i--){
for(int j = i+1; j<n; j++){
if(s.charAt(i) == s.charAt(j)) {
if(j-i == 1){
dp[i][j] = true;
}
else{
dp[i][j] = dp[i+1][j-1];
}
}else{
dp[i][j] = false;
}
if(dp[i][j]){
result++;
}
}
}
return result;
}
}
5. 最长回文子串
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
思路: 其实求回文子串是一样的,只不过我们多了一步要保存左右节点下标和当前回文串的长度
// 是回文串的情况下,判断是不是最长的,是就保存区间和长度
if (dp[i][j] == true && j - i + 1 > max) {
max = j - i + 1;
start = i;
end = j;
}
i代表的是字符串左区间,j代表的是字符串右区间
class Solution {
public String longestPalindrome(String s) {
int size = s.length();
if (size == 0) {
return "";
}
// 初始化
boolean[][] dp = new boolean[size][size];
for (int i = 0; i < size; i++) {
dp[i][i] = true;
}
// 填表并记录
int max = 0; // 长度
int start = 0, end = 0; // 区间
for (int i = size - 2; i >= 0; i--) {
for (int j = i + 1; j < size; j++) {
if (s.charAt(i) != s.charAt(j)) {
dp[i][j] = false;
} else if (i + 1 == j) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
// 是否能够更长
if (dp[i][j] == true && j - i + 1 > max) {
max = j - i + 1;
start = i;
end = j;
}
}
}
return s.substring(start, end + 1);
}
}
1025. 除数博弈
爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。
最初,黑板上有一个数字 N
。在每个玩家的回合,玩家需要执行以下操作:
- 选出任一
x
,满足0 < x < N
且N % x == 0
。 - 用
N - x
替换黑板上的数字N
。
如果玩家无法执行这些操作,就会输掉游戏。
只有在爱丽丝在游戏中取得胜利时才返回 True
,否则返回 False
。假设两个玩家都以最佳状态参与游戏。
示例 1:
输入:2
输出:true
解释:爱丽丝选择 1,鲍勃无法进行操作。
示例 2:
输入:3
输出:false
解释:爱丽丝选择 1,鲍勃也选择 1,然后爱丽丝无法进行操作。
思路: 确定好dp[1] dp[2],谁下这步得输赢情况,然后循环遍历然后满足是它能整除得,且你下完这一步对方是输得就可以下
if ((i % j == 0) && !dp[i - j]) {
dp[i] = true;
break;
}
public class Solution {
//0 < x < N 且 N % x == 0 。
public boolean divisorGame(int N) {
if (N == 1) {
return false;
}
// 为了不处理下标偏移,多设置 1 格
// dp[i]:黑板上的数字为 i 时,当前做出选择的人是否会赢
boolean[] dp = new boolean[N + 1];
// 最基本情况(如图所示)
dp[1] = false;
dp[2] = true;
// 以线性的方式逐步递推得到结果
for (int i = 3; i <= N; i++) { //超过3的数
for (int j = 1; j <= i / 2; j++) { //在它的一半里面找有没有可以走的步数
// 只要做出的选择的其中之一,能让对方输,在当前这一步我们就可以赢 //比如3 只有1,但是dp[2]也就是下一步是赢得可能,那么不行,只有下一步而且还是false输得情况你才能走这一步
if ((i % j == 0) && !dp[i - j]) {
dp[i] = true;
break;
}
}
}
// 根据题意:输出是 dp[N]
return dp[N];
}
}
303. 区域和检索 - 数组不可变
难度简单187收藏分享切换为英文关注反馈
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
示例:
给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
class NumArray {
/**
* 自己在刷动态规划的题,知道可以暴力,但是还是想了很久动态规划怎么实现,感觉就是拿一个数组去接收每个位置的结果,最后后一个减
* 前一个位置,得到结果
*
*/
private int[] array;
//计算出下标到起点得和,存放在下标位置
public NumArray(int[] nums) {
if(nums.length>0){
array = new int[nums.length];
/**
* 初始条件
*/
array[0] = nums[0];
for (int i = 1; i < nums.length; i++) { //i位置上从0到i的所有值之和
array[i] = array[i-1] + nums[i];
}
}
}
//只需要两个下标得值相减,就会减去前面得和留下区间和
public int sumRange(int i, int j) {
//假设i=0,j=2,则结果该是1,2,3位置之和
if(i==0){
return array[j];
}
else{
//这里一个小细节,比如i=2,j=5,直接array[j] - array[i]那么nums[i]号元素被剪掉了,这里应该将i-1
return array[j] - array[i-1];
}
}
}
剑指 Offer 42. 连续子数组的最大和
难度简单112收藏分享切换为英文关注反馈
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution {
public int maxSubArray(int[] nums) {
int res = nums[0];
for(int i = 1; i < nums.length; i++) {
nums[i] += Math.max(nums[i - 1], 0);
res = Math.max(res, nums[i]); //记录最大连续和
}
return res;
}
}
746. 使用最小花费爬楼梯
数组的每个索引作为一个阶梯,第 i
个阶梯对应着一个非负数的体力花费值 cost[i]
(索引从0开始)。
每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。
您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
示例 1:
输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
示例 2:
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。
先踏上第i-2级台阶(最小总花费dp[i-2]),再直接迈两步踏上第i级台阶(花费cost[i]),最小总花费dp[i-2] + cost[i];
先踏上第i-1级台阶(最小总花费dp[i-1]),再迈一步踏上第i级台阶(花费cost[i]),最小总花费dp[i-1] + cost[i];
则dp[i]是上面这两个最小总花费中的最小值。
因此状态转移方程是:
dp[i] = min(dp[i-2], dp[i-1]) + cost[i]。
初始条件:
最后一步踏上第0级台阶,最小花费dp[0] = cost[0]。
最后一步踏上第1级台阶有两个选择:
可以分别踏上第0级与第1级台阶,花费cost[0] + cost[1];
也可以从地面开始迈两步直接踏上第1级台阶,花费cost[1]。
最小值dp[1] = min(cost[0] + cost[1], cost[1]) = cost[1]。
class Solution {
public int minCostClimbingStairs(int[] cost) {
int[] dp=new int[cost.length];
dp[0]=cost[0];
dp[1]=cost[1];
for(int i=2;i<cost.length;i++){
dp[i]=Math.min(dp[i-1]+cost[i],dp[i-2]+cost[i]);
}
return Math.min(dp[cost.length-1],dp[cost.length-2]);
}
}
面试题 08.01. 三步问题
三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。
示例1:
输入:n = 3
输出:4
说明: 有四种走法
示例2:
输入:n = 5
输出:13
等于前面和:斐波那契数列
class Solution {
public int waysToStep(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
if(n == 3) return 4;
long [] dp = new long[n + 1];
dp[1] = 1;
dp[2] = 2;
dp[3] = 4;
for(int i =4;i <= n;i ++){
//因为三个楼梯都可以到这一层,那么这一层所需要前面得走法等于他们三个楼梯得走法分别相加
dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 3])%1000000007;
}
return (int)dp[n];
}
}
309. 最佳买卖股票时机含冷冻期
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]) 卖出
dp[i][1] = max(dp[i-1][1],dp[i-1][2]-prices[i]) 买入
dp[i][2] = dp[i-1][0] 冷冻期
### 思路上用 dp状态存三种状态,当前买入和当前卖出,当前不买不卖
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
// 特判
if (len < 2) {
return 0;
}
int[][] dp = new int[len][3];
// 初始化
dp[0][0] = 0; //当前卖出
dp[0][1] = -prices[0]; //当前买入
dp[0][2] = 0; //冷冻期
for (int i = 1; i < len; i++) {
//求第i天累计卖出最大利润,累计买入的最大利润、冷冻期内最大利润
//1. 今天卖=MAX(昨天卖,昨天买入+今天卖得钱) 主要是为了判断今天比昨天卖得多不多,不多,那今天不动,利润还是昨天得
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
//2. 今天买=MAX(昨天买入,昨天不动-今天买得钱) 主要是为了判断今天买还是昨天买花得钱少,来看看自己是不动还是买
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][2] - prices[i]);
//3. 冷冻期就等于昨天卖得利润
dp[i][2] = dp[i - 1][0];
}
return Math.max(dp[len - 1][0], dp[len - 1][2]);
}
}