我是一个从汽车行业转行IT的项目经理,我是Edward。今天的leetcode每日一题又刷到了DP问题,而且是hard级别的,着实伤脑筋,姑且回顾一下之前遇到的DP问题,温故而知新。
第一次碰到DP问题是经典的买卖股票的最佳时机中的第一题,难度easy:
- 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票
程序实现:
if (prices.length<=1) {
return 0;
}
int[]dp = new int[prices.length];
dp[0]=0;
int min =prices[0];
for (int i = 1; i < prices.length; i++) {
min = Math.min(min, prices[i]);
dp[i]=Math.max(dp[i-1], prices[i]-min);
}
return dp[prices.length-1];
这题甚至没必要引入数组:
if(prices.length<=1) {
return 0;
}
int min=prices[0];
int max=Integer.MIN_VALUE;
for (int i = 1; i < prices.length; i++) {
min=Math.min(min, prices[i]);
max=Math.max(max, prices[i]-min);
}
return max;
第二次遇到的是经典的打家劫舍问题改头换面版,难度easy:
面试题 17.16. 按摩师
一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
注意:本题相对原题稍作改动
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。
- 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
int len = nums.length;
if (len==0) {
return 0;
}
if (len==1) {
return nums[0];
}
int[]dp=new int[len]; //新建数组储存截止到每个数的最高金额
dp[0]=nums[0];
dp[1]=Math.max(nums[0], nums[1]);
for (int i = 2; i < dp.length; i++) {
dp[i]=Math.max(dp[i-2]+nums[i], dp[i-1]); //总结规律,可代数验证
}
return dp[len-1];
顺便做了下打家劫舍第二题,不同之处就是首尾也不能一起,难度medium:
- 打家劫舍 II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
class Solution {
public int rob(int[] nums) {
int len = nums.length;
if (len==0) {
return 0;
}
if (len==1) {
return nums[0];
}
/*
* 与rob1最大的不同在于,第一个和最后一个不能同时选,分选第一个和不选第一个两种情况取最大
*/
return Math.max(robround2(nums, 0, nums.length-2), robround2(nums, 1, nums.length-1));
}
public static int robround2(int[]nums,int start,int end) {
if (start==end) {
return nums[start];
}
//即该数组只有两个元素时
//int[]dp = new int[nums.length-1]; 两种情况无法用一个数组来满足
int fa=nums[start];
int fb=Math.max(nums[start], nums[start+1]);
for (int i = start+2; i <= end; i++) {
int fc=Math.max(fa+nums[i], fb); //用数字依次向前传递来实现
fa=fb;
fb=fc;
}
return fb;
}
}
最后是今天这一题,用grid数组同时比较两个字符串,难度hard:
- 编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int[][]dp = new int[len1+1][len2+1];
for (int i = 0; i <=len1; i++) {
dp[i][0]=i;
}
for (int j = 0; j <= len2; j++) {
dp[0][j]=j; //用dp数组的纵列和横列分别表示两个单词的坐标
}
for (int i = 1; i <=len1; i++) {
for (int j = 1; j <=len2; j++) {
//如果两个单词在同一位上的字母一样,则不需要调整,该轮的dp数值与上一轮相同
if (word1.charAt(i-1)==word2.charAt(j-1)) {
dp[i][j]=dp[i-1][j-1];
//否则取增删替三个操作的最小值 + 1
}else {
dp[i][j]=1+Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]);
}
}
}
return dp[len1][len2];
}
}
DP问题,总体来说比较抽象,最好代数分析会比较清楚。