给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路:其实对于每一天来说,只有买入(0)、卖出(1)或者不操作(2),我们定义dp[i][0]表示第i天买入后能获得的最大收益,dp[i][1]表示第i天卖出后能获得的最大收益,dp[i][2]表示第i天不操作能获得的最大收益,于是答案就是max(dp[len-1][1],dp[len-1][2])。但是,这样考虑就会发现没有办法处理这个第i天不操作的状态,因为第i天不操作的前一个状态可能是第i-1天买入、卖出或者不操作,此时并不能满足DP没有后效性的条件。所谓后效性是指,对于某个状态P,如何到达这个状态P对于P如何向下一个状态进行迁移是没有影响的。但是在上述思路中,如果是从第i-1天的买入到达第i天的不操作,那么他只能转移到第i+1天的卖出或者不操作,而如果是从第i-1天的卖出到达第i天的不操作,那么他只能转移到第i+1天的买入或者不操作,此时无法判断如何转移。
更换思路:其实经过上面分析我们发现,所谓的不操作状态等于在前面的时候进行操作,即第i天不操作的行为包含在前i-1天中的第j天进行操作,即在考虑了前j-1天的条件下,在第j天买入或者卖出,而答案即为ans=max(ans,dp[i][1])。然而,单单是这样看,我们还需要在对第i天进行决策时,对前i-1天遍历选择能使得受益最大的抉择。动态规划的含义变更为在dp[i][0]表示在第i天买入的前提下能获得的最大收益,dp[i][1]表示第i天卖出的情况下能获得的最大收益,第i天不操作的情况已经合并到在前i-1中的某一天下操作获得的最大收益。
首先写出转移方程:dp[i][0]=max(dp[j][1]-price[i]),dp[i][1]=max(dp[j][0]+price[i]);我们可以发现,price[i]是一个常数,所以方程可以化为dp[i][0]=max(dp[j][1])-price[i],dp[i][1]=max(dp[j][0])+price[i];于是很明显,我们可以设置两个变量:MaxSell表示前i-1天最后一个操作为卖出能够获得的最大收益,MinBuy表示前i-1天最后一个操作为买入所花费的最小代价,于是转移方程变为线性:dp[i][0]=MaxSell-price[i],dp[i][1]=MinBuy+price[i];MaxSell=max(MaxSell,dp[i][1]);MinBuy=min(MinBuy,dp[i][0]);
class Solution {
public:
int maxProfit(vector<int>& prices) {//dp[i] 规定必须在第i天进行操作
int len=prices.size(),i,MaxSell,MinBuy,ans=0;
if(!len)return 0;
int dp[len+10][2];// 对第i支股票的操作 0:买入 1:卖出 0—>1 1->0
MinBuy=dp[0][0]=-prices[0],MaxSell=dp[0][1]=0;
for(i=1;i<len;++i){
dp[i][0]=MaxSell-prices[i];
dp[i][1]=MinBuy+prices[i];
MaxSell=max(MaxSell,dp[i][1]);
MinBuy=max(MinBuy,dp[i][0]);
ans=max(ans,dp[i][1]);
}
return ans;
}
};
在分享一个一个有趣的思路,来自评论区的大佬:紫发的sakura
其实这题确实像一个脑筋急转弯,我们压根就不需要考虑那么多状态,直接贪心就可以,但是我们贪心的规则是,只有当能使得收益变大的时候,我们才进行累加更新,因此我们可以确保只要更新发生,收益一定会变大。具体方案是,当当前的价格大于前一天价格时候,直接累加差额。
证明:对于序列中的某一个子区间。
如果其最大值为Max,最小值为Min,且保证先取最小后取最大那么第一种方案就是取其差额作为子区间的最大收益。
第二种方案是,累加出所有的上升段,分两种情况考虑:
1.子区间本身就是递增的,那么正好结果同第一种方案
2.子区间本身不递增,但是可以将其划分为几个递增区间和游离元素,例如[5 1 4 3 6] 可以划分为 5 [1 4] [3 6] ,在满足先取最小值后取最大值的规则前提下,最大值Max肯定是属于某一个递增区间的,那么在第二种方案下,有两种情况:
A.从最小值到最大值满足递增,此时如果还剩下别的递增区间,按照第二种方案,结果一定会变大,如果没有其他递增区间,结果也等于第一种方案
B.从最小值到最大值不满足递增,假设有一个不满足点,从这个不满足点分开,即有两端和Sum1+Sum2>Sum。
举个例子,最小值为L,最大值为R的L~R的区间内有一点p不满足A[p]>A[p-1],此时将区间化为L~p-1、p~R,那么方案一的结果为A[R]-A[L],而方案二为A[p-1]-A[L]+A[R]-A[p]=A[R]-A[L]+(A[p-1]-A[p]),由于条件中已知不满足A[p]>A[p-1]即条件应为A[p]<=A[p-1],带入A[p-1]-A[L]+A[R]-A[p]=A[R]-A[L]+(A[p-1]-A[p])得到A[p-1]-A[L]+A[R]-A[p]=A[R]-A[L]+(A[p-1]-A[p])>=A[R]-A[L]。
class Solution {
public:
int maxProfit(vector<int>& prices) {//dp[i] 规定必须在第i天进行操作
int len=prices.size(),i,ans=0;
for(i=1;i<len;++i){
if(prices[i]>prices[i-1])ans+=prices[i]-prices[i-1];
}
return ans;
}
};
给你一个 n
行 m
列的矩阵,最开始的时候,每个单元格中的值都是 0
。
另有一个索引数组 indices
,indices[i] = [ri, ci]
中的 ri
和 ci
分别表示指定的行和列(从 0
开始编号)。
你需要将每对 [ri, ci]
指定的行和列上的所有单元格的值加 1
。
请你在执行完所有 indices
指定的增量操作后,返回矩阵中 「奇数值单元格」 的数目。
示例 1:
输入:n = 2, m = 3, indices = [[0,1],[1,1]]
输出:6
解释:最开始的矩阵是 [[0,0,0],[0,0,0]]。
第一次增量操作后得到 [[1,2,1],[0,1,0]]。
最后的矩阵是 [[1,3,1],[1,3,1]],里面有 6 个奇数。
示例 2:
输入:n = 2, m = 2, indices = [[1,1],[0,0]]
输出:0
解释:最后的矩阵是 [[2,2],[2,2]],里面没有奇数。
提示:
1 <= n <= 50
1 <= m <= 50
1 <= indices.length <= 100
0 <= indices[i][0] < n
0 <= indices[i][1] < m
思路一:简单暴力模拟即可。。
class Solution {
public:
int oddCells(int n, int m, vector<vector<int>>& indices) {
int i,j,ans=0,len=indices.size(),row,col;
bool map[n+1][m+1];
memset(map,false,sizeof(map));
for(i=0;i<len;++i){
row=indices[i][0];
col=indices[i][1];
for(j=0;j<m;++j){
map[row][j]=!map[row][j];
if(map[row][j])++ans;
else --ans;
}
for(j=0;j<n;++j){
map[j][col]=!map[j][col];
if(map[j][col])++ans;
else --ans;
}
}
return ans;
}
};
思路二:容斥原理-我们先不考虑行列相交的元素,那么每次对一行进行加一相当于给答案加一行或减一行,我们只规定对于每一行的有效和无效两种状态,那么经过多次操作后,所有行的状态只有有效和无效,而无效行的效果和对这个行没有操作是一样的,他们对答案不存在贡献,或者举例来说,一个无效列和一个有效行相交后,有效部分即为交点,而这个交点肯定不是有效行和有效列的交点,所以直接纳入该有效行的考虑范围内了。故由容斥原理可知,答案应为m(行长)*有效行数+n(列长)*有效列数-相交点数。
class Solution {
public:
bool row[110],col[110];
int oddCells(int n, int m, vector<vector<int>>& indices) {
int i,j,len=indices.size(),rows=0,cols=0,t;
for(i=0;i<len;++i){
t=indices[i][0];
row[t]=!row[t];
if(row[t])++rows;
else --rows;
t=indices[i][1];
col[t]=!col[t];
if(col[t])++cols;
else --cols;
}
return rows*m+cols*n-2*rows*cols;
}
};
给你一个非递减的 有序 整数数组,已知这个数组中恰好有一个整数,它的出现次数超过数组元素总数的 25%。
请你找到并返回这个整数
示例:
输入:arr = [1,2,2,6,6,6,6,7,10]
输出:6
提示:
1 <= arr.length <= 10^4
0 <= arr[i] <= 10^5
首先,由题目非递减有序数组有了第一个想法,直接线性扫描加统计次数是否超出25%,超出则直接返回。
class Solution {
public:
int findSpecialInteger(vector<int>& arr) {
//基本有序
int i,len=arr.size(),count=1,stand=len/4,base=arr[0];
for(i=1;i<len;++i){
if(count>stand){
return base;
}
if(base==arr[i])++count;
else{
base=arr[i];
count=0;
}
}
return base;
}
};
方法二:首先这个数组本身的是有序的,超过25%的元素,我们假设总共有n个元素,那么这个元素至少出现了n/4+1次,这样的一种元素安插在有序数组里,会导致的结果是0、n/4、n/2....这些位置上至少有一个位置上是我们要找的元素x。
反证法:如果这些位置上没有一个元素为x,那么x只可能存在与这些元素的相邻元素之间,而这些元素之间的元素个数最多为n/4个,并不能满足x个数至少为n/4+1的要求,故这些位置上至少有一个位置上为x。
因此我们可以直接在这些位置上枚举x的值,通过二分得到x占满的区间长度。这样最多需要五次二分。
class Solution {
public:
int findSpecialInteger(vector<int>& arr) {
//基本有序
int i,n=arr.size(),standard=n/4;
vector<int>::iterator p,q;
for(i=0;i<n;i+=standard){
p=lower_bound(arr.begin(),arr.end(),arr[i]);
q=upper_bound(arr.begin(),arr.end(),arr[i]);
if(q-p>standard)return arr[i];
}
return arr[0];
}
};
作为一位web开发者, 懂得怎样去规划一个页面的尺寸是很重要的。 现给定一个具体的矩形页面面积,你的任务是设计一个长度为 L 和宽度为 W 且满足以下要求的矩形的页面。要求:
1. 你设计的矩形页面必须等于给定的目标面积。 2. 宽度 W 不应大于长度 L,换言之,要求 L >= W 。 3. 长度 L 和宽度 W 之间的差距应当尽可能小。
你需要按顺序输出你设计的页面的长度 L 和宽度 W。
示例:
输入: 4 输出: [2, 2] 解释: 目标面积是 4, 所有可能的构造方案有 [1,4], [2,2], [4,1]。 但是根据要求2,[1,4] 不符合要求; 根据要求3,[2,2] 比 [4,1] 更能符合要求. 所以输出长度 L 为 2, 宽度 W 为 2。
说明:
- 给定的面积不大于 10,000,000 且为正整数。
- 你设计的页面的长度和宽度必须都是正整数。
其实这题,根据基本数学知识可以猜到答案是让最终图形尽可能贴近正方形,然后可以基于数分布的知识进行一些优化,我们设n的平方根下取整为root,很容易得知,1~root区间内的数个数一定不会少于root+1~n,因此枚举宽度比枚举长度要快。
class Solution {
public:
vector<int> constructRectangle(int area) {
int W;
W=floor(sqrt(area));
vector<int> ans;
while(W){
if(area%W==0){
ans.push_back(area/W);
ans.push_back(W);
break;
}
--W;
}
return ans;
}
};
给定两个没有重复元素的数组 nums1
和 nums2
,其中nums1
是 nums2
的子集。找到 nums1
中每个元素在 nums2
中的下一个比其大的值。
nums1
中数字 x 的下一个更大元素是指 x 在 nums2
中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于num1中的数字2,第二个数组中的下一个较大数字是3。
对于num1中的数字4,第二个数组中没有下一个更大的数字,因此输出 -1。
注意:
nums1
和nums2
中所有元素是唯一的。nums1
和nums2
的数组大小都不超过1000。
题目抽象后变为,对于nums2中的某些元素,需要返回他右端的第一个比他大的值,自然联想到数据结构-单调栈,根据题意,应运用单调递减栈解决。
class Solution {
public:
int RightMAX[10010];//单调非递增栈 RightMax[i]: i的右边第一个比它大的数是RightMax
typedef pair<int,int> Element;//位置 元素值
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> ans;
int n=nums1.size(),m=nums2.size(),i,j;
stack<Element> Monotonical;
Element element;
for(i=0;i<m;++i){
while(!Monotonical.empty()&&Monotonical.top().second<nums2[i]){
element=Monotonical.top(),Monotonical.pop();
RightMAX[element.second]=nums2[i];
}
Monotonical.push(make_pair(i,nums2[i]));
}
while(!Monotonical.empty()){
element=Monotonical.top(),Monotonical.pop();
RightMAX[element.second]=-1;
}
for(i=0;i<n;++i)ans.push_back(RightMAX[nums1[i]]);
return ans;
}
};