通过编写动态规划系列题目,拓宽思路
583. Delete Operation for Two Strings
class Solution {
public:
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));//采用这种扩容的方法,可以防止一个数组为空的情况,而且可以把边界情况放到一个循环中讨论
for(int i=0;i<=word1.size();i++)
for(int j=0;j<=word2.size();j++)
{
if(i==0||j==0) continue;
if(word1[i-1]==word2[j-1])//注意这里要考虑实际位置和标号位置的对应
dp[i][j]=dp[i-1][j-1]+1;
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return word1.size()+word2.size()-2*dp[word1.size()][word2.size()];
}
};
413 Arithmetic slices 等差数列切片 子数组(连续子序列)
题意:寻找一个数组的连续子数组,使得这个子数组是等差数列。请求出这种子数组的个数。
我的思路
(1)n方复杂度,首先求累加和数组,然后根据等差数列求和充要条件,判断序列是否是等差数列,但是解雇不对
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int n=A.size();
if(n<=2) return 0;
vector<int> sum(n+1,0);
//求累加和数组
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+A[i-1];
int count=0;
for(int i=3;i<=n;i++)
{
for(int k=3;i-k>=0;k++)
{
int cursum=sum[i]-sum[i-k];
if((cursum-k*A[i-k])%(k*(k-1)/2)==0)
count++;
}
}
return count;
}
};
(2)动态规划方法
设dp[i]表示到第i1个时,前面构成的数列个数,
如果 A[i-1]-A[i-2]==A[i-2]-A[i-3] dp[i]=dp[i-1]+1+(len-2)+1,这里的len是在前面至少是3个数的时候的长度3,如果前面不构成等差序列,len=0,公式变为dp[i]=dp[i-1]+1
如果A[i-1]-A[i-2]!=A[i-2]-A[i-3] ,dp[i]=dp[i-1] 此时把len变成0.
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int n=A.size();
vector<int> dp(n+1,0);
int len=0;
for(int i=3;i<=n;i++)
{
if(A[i-1]-A[i-2]==A[i-2]-A[i-3])
{
if(len==0)//说明之前的三个不是这种序列,从现在开始重新构成3个数的等差序列
{
dp[i]=dp[i-1]+1;
len=3;//在结尾改变len
}
else//说明当前等差数列的长度在递增过程
{
dp[i]=dp[i-1]+len-1;
len++;//len自增后是当前这一片的最大长度
}
}
else
{
dp[i]=dp[i-1];
len=0;//
}
}
return dp[n];
}
};
可以把dp压缩成 一个变量:
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int n=A.size();
int dp=0;
int len=0;
for(int i=3;i<=n;i++)
{
if(A[i-1]-A[i-2]==A[i-2]-A[i-3])
{
if(len==0)
{
dp=dp+1;
len=3;
}
else
{
dp=dp+len-1;
len++;
}
}
else
{
dp=dp;
len=0;
}
}
return dp;
}
};
参考方法:
(1)dp[i]表示以i结尾的等差数列个数,如果A[i] - A[i -1] == A[i - 1] - A[i - 2],dp[i]= dp[i -1] +1;否则dp[i]=0
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, n = A.size();
vector<int> dp(n, 0);
for (int i = 2; i < n; ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
dp[i] = dp[i - 1] + 1;
}
res += dp[i];
}
return res;
}
};
上述dp可以用一个变量代替,因为每次只涉及当前值和前面的值。
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, cur = 0;
for (int i = 2; i < A.size(); ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
cur += 1;
res += cur;
} else {
cur = 0;
}
}
return res;
}
};
(2)使用公式
public class Solution {
public int numberOfArithmeticSlices(int[] A) {
int count = 0;
int sum = 0;
for (int i = 2; i < A.length; i++) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {//如果满足,长度自增,1的时候说明长度是3,2的时候说明长度是4
count++;
} else {
sum += (count + 1) * (count) / 2;//连续数列结束,把之前的数加上
count = 0;//并把count变成0
}
}//注意这种方法不会有重复,它实际分别找到数组中成片出现的最长数列,每个这样的数列不可能相连
return sum += count * (count + 1) / 2;//在长度自增过程中,并没有把最后算的数列个数加到总和中去,最后需要做补充操作。当最后不构成数列时,count为0
}
}
同样的一个实现: 只是len和count的取值不同。len为3时,表示长度为3 这使得公式与推导公式完全一样,但是最后的时候,可能len为2,不构成数列,这个时候需要加一个判断
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, len = 2, n = A.size();
for (int i = 2; i < n; ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
++len;
} else {
if (len > 2) res += (len - 1) * (len - 2) * 0.5;
len = 2;
}
}
if (len > 2) res += (len - 1) * (len - 2) * 0.5;
return res;
}
};
446. Arithmetic Slices II - Subsequence 等差数列切片 子序列(可以不连续)
646 maxmum length of pair chain pair的最大链
给定n组数,每组的第一个数小于第二个。如果两个数组,第一个组的大值小于第二个组的小值,那么构成一个链。题目要求返回最大的链的长度。
343 integer break 整数切分乘积最大
一个数可以分成几个数的和,这些数相乘的最大值是多少?
357 Count Numbers with Unique Digits
给定一个数n,在0 ≤ x < 10n中找所有的数的没有相同数字的数量
486 Predict the Winner 纸牌博弈问题
题目:有一个整型数组A,代表数值不同的纸牌排成一条线。玩家a和玩家b依次拿走每张纸牌,规定玩家a先拿,玩家b后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家a和玩家b都绝顶聪明,他们总会采用最优策略。请返回最后获胜者的分数。给定纸牌序列A及序列的大小n,请返回最后分数较高者得分数(相同则返回任意一个分数)。(获得的牌数相加)
思路:
递归推导:
//递归推导
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int sum=accumulate(nums.begin(),nums.end());
int me=p(nums,0,nums.size()-1);
if(sum-me>=me) return false;
else
return true;
}
//递归推导
int p1(vector<int>& nums,int i,int j)//i表示牌的最左边,j表示牌的最右边
{
//可以取i,也可以取j
//如果只剩一张牌,取走这个牌
if(i==j) return nums[i];
//如果剩两张牌,因为奇偶性不同最后剩得牌不一样
if(i=j-1) return max(nums[i],nums[j]);
int sum=0;
//如果我取走i,给对手的牌是i-1到j,对手的可以取走i-2或者j,他会选择剩下的牌加和小的那种情况
//所以我取走i的话,对手给我留下下面两种牌堆
//p(nums,i+2,j),p(nums,i+1,j-1);
//他他妈的一定会留较小的那个:min(p(nums,i+2,j),p(nums,i+1,j-1))
//同样道理,我也可以取走j
//留给对手的牌堆是:i到j-1.他留给我的牌堆是:min(p(nums,i+1,j-1),p(nums,i,j-2))
//但是现在决定权在我这里,所以我会选择:
return max(nums[i]+min(p(nums,i+2,j-1),p(nums,i+1,j-1)),nums[j]+min(p(nums,i+1,j-1),p(nums,i,j-2)));
}
};
改成动态规划
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int sum=std::accumulate(nums.begin(),nums.end(),0);
int n=nums.size();
if(n==1) return true;
vector<vector<int>> dp(n,vector<int>(n,0));
for(int i=n-1;i>=0;i--)
{
for(int j=i;j<n;j++)
{
if(i==j) dp[i][j]=nums[i];
else if(i+1==j) dp[i][j]=max(nums[i],nums[j]);
else
{
dp[i][j]=max(nums[i]+min(dp[i+2][j],dp[i+1][j-1]),nums[j]+min(dp[i+1][j-1],dp[i][j-2]));
}
}
}
if(sum-dp[0][n-1]<=dp[0][n-1]) return true;
else
return false;
}
};
改成一维数组:由于依赖左下方的反斜对角线,因此需要从左上至右下沿反斜对角线遍历
标准答案:
他的递归思路不太一样https://leetcode.com/problems/predict-the-winner/solution/
464Can I Win
375Guess Number Higher or Lower II
392 Is Subsequence 判断一个字符串是否是另一个字符串的子序列
494 target Sum
这道题给了我们一个数组,和一个目标值,让我们给数组中每个数字加上正号或负号,然后求和要和目标值相等,求有多少中不同的情况。
650 2 Keys Keyboard
文本里只有一个字符A,每次的操作只能是复制粘贴当前所有数字或者再粘贴一遍,求给定字符长度所需要的最小操作次数
通过推导找规律,发现dp[i]:if(k*j==i) dp[i]=min(dp[k]+dp[j]) ,就是说一个数的操作次数等于它相乘因子的操作数之和, 取sqrt找到最相近的两个因数相加即可。
class Solution {
public:
int minSteps(int n) {
//dp[i]:if(k*j==i) dp[i]=min(dp[k]+dp[j])
if(n<=1) return 0;
vector<int> dp(n+1,0);
dp[1]=0;
for(int i=2;i<=n;i++)
{
dp[i]=i;
int factor=sqrt(i);
for(int j=factor;j>0;j--)
{
if(i%j==0) {
dp[i]=dp[j]+dp[i/j];
break;
}
}
}
return dp[n];
}
};
另外一种解法:
由于2 * 2 = 2 + 2,2 * 3 > 2 + 3,4 * 4 > 4 + 4 ,而之前的那种方法,得到的两个因子,每个因子可以分别分解。这个题目相当于将一个数分解成多个因子的加和。比如30->5+6,6->2+3,就是2+3+5.所以可以有递归实现:
class Solution {
public:
int minSteps(int n) {
if (n == 1) return 0;
for (int i = 2; i < n; i++)
if (n % i == 0) return i + minSteps(n / i);
return n;
}
};
这个递归实现可以改成迭代实现:
public int minSteps(int n) {
int s = 0;
for (int d = 2; d <= n; d++) {
while (n % d == 0) {
s += d;
n /= d;
}
}
return s;
}
这样复杂度达到logn
377 Combination Sum IV
给定一个集合,每个数都不同,给定一个目标,在集合中选择数(可重复)的加和是这个目标值,问有多少种(次序不同的算不同种)?
https://discuss.leetcode.com/topic/52302/1ms-java-dp-solution-with-detailed-explanation 本文讲解了自顶向下的递归和自底向上的动态规划
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
int n=nums.size();
vector<int> com(target+1,0);
com[0]=1;
for(int i=1;i<=target;i++)
for(int j=0;j<n;j++)
{
if(i-nums[j]>=0)
com[i]+=com[i-nums[j]];//组成i的最后一个元素可以是nums中的任何一个(只要不比总和大),所以com[i]是减去任何一个(并满足要求)的所有加和
}
return com[target];
}
};
638 Shopping Offers
打折促销组合,给定需要购买的商品类型和数量,求买这些东西花费的最少的钱
309 Best Time to Buy and Sell Stock with Cooldown
可以任意的购买抛售股票,但是抛售之后的一天不能购买股票,求赚钱最多是多少?
416 Partition Equal Subset Sum把一个数组分成两组,问能不能两组的元素加和相等
376 Wiggle Subsequence 增减最长子序列
设置up[i]表示到i位置时首差值为正的摆动子序列的最大长度,down[i]表示到i位置时首差值为负的摆动子序列的最大长度
对于每一个up[i]遍历之前的down[j],当满足nums[i]>nums[j]时,选择是否更新up[i]
对于每一个down[i]遍历之前的up[j],当满足nums[i] < nums[j]时,选择是否更新down[i]
代码如下:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n=nums.size();
if(n<=1) return n;
vector<int> up(n,1);//初始值是1
vector<int> down(n,1);
for(int i=1;i<n;i++)
for(int j=0;j<i;j++)
{
if(nums[i]>nums[j])
{
up[i]=max(up[i],down[j]+1);
}
else if(nums[i]<nums[j])
{
down[i]=max(down[i],up[j]+1);
}
}
return max(up[n-1],down[n-1]);
}
};
思路2:
如果nums[i]>nums[i-1] 那么up[i]=down[i-1]+1,down[i]=down[i-1]
如果nums[i]>nums[i-1] 那么down[i]=up[i-1]+1,up[i]=up[i-1]
如果两者相等,down[i]=down[i-1] up[i]=up[i-1]
只需遍历一遍即可,注意down[0]和up[0]都是1
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n=nums.size();
if(n<=1) return n;
vector<int> up(n,0);//初始值是1
vector<int> down(n,0);
down[0]=1;
up[0]=1;
for(int i=1;i<n;i++)
{
if(nums[i]<nums[i-1] )
{
up[i]=down[i-1]+1;
down[i]=down[i-1];
}
if(nums[i]>nums[i-1])
{
down[i]=up[i-1]+1;
up[i]=up[i-1];
}
if(nums[i]==nums[i-1])
{
down[i]=down[i-1];
up[i]=up[i-1];
}
}
return max(up[n-1],down[n-1]);
}
};
贪心算法:
考虑增减曲线,我们在找点的时候,如果遇到连续升序或者连续降序的时候,一定而且只能选择这段的两个节点,当出现增减时,这些部分也包含进来。可以理解为,这个题目是将原来的节点做了一种“滤波处理”:节点的作用只描述趋势变化的增减特点,在一定时间内连续增加或减少时,我们只关心这一段的开始和结束,而忽略中间的过程。
public class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length < 2)
return nums.length;
int prevdiff = nums[1] - nums[0];
int count = prevdiff != 0 ? 2 : 1;
for (int i = 2; i < nums.length; i++) {
int diff = nums[i] - nums[i - 1];
if ((diff > 0 && prevdiff <= 0) || (diff < 0 && prevdiff >= 0)) {
count++;
prevdiff = diff;
}
}
return count;
}
}
368 Largest Divisible Subset 求最大子集,子集中任意两个数有一个能被另一个整除
264 Ugly Number II 求第n个丑数
467 Unique Substrings in Wraparound String
576 Out of Boundary Paths
322 Coin Change