給定一個數組,它的第 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;
}
};