一、贪心算法基础
先来了解一下「贪心算法」的问题需要满足的条件:
最优子结构:规模较大的问题的解由规模较小的子问题的解组成,规模较大的问题的解只由其中一个规模较小的子问题的解决定;
无后效性:后面阶段的求解不会修改前面阶段已经计算好的结果;
贪心选择性质:从局部最优解可以得到全局最优解。
1 题目
2 解题思路
从左到右枚举卖出价格 prices[i]\textit{prices}[i]prices[i],那么要想获得最大利润,我们需要知道第 iii 天之前,股票价格的最小值是什么,也就是从 prices[0]\textit{prices}[0]prices[0] 到 prices[i−1]\textit{prices}[i-1]prices[i−1] 的最小值,把它作为买入价格,这可以用一个变量 minPrice\textit{minPrice}minPrice 维护。
3 code
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans=0;
int min_price=prices[0];
for(int p:prices){
ans=max(ans,p-min_price);
// 维护一个最小值
min_price=min(min_price,p);
}
return ans;
}
};
三、55.跳跃游戏
1 题目
2 解题思路
(1)使用动态规划的方法:从后往前,状态枚举,for (int i = n - 2; i >= 0; --i)
(2)状态计算:int furthestJump = min(i + nums[i], n - 1); // 当前位置能够到达的最远位置
for (int j = i + 1; j <= furthestJump; ++j
(3)状态转移:if (dp[j]) { // 如果从 j 位置可以跳到最后一个位置
dp[i] = true; // 则从 i 位置也可以跳到最后一个位置
break; // 不需要再检查后面的位置
3 code
class Solution {
public:
bool canJump(vector<int>& nums) {
int n =nums.size();
// 状态定义,非常重要
vector<bool> dp(n,false);
// 最后一个位置可以跳到自己
dp[n-1]=true;
for(int i=n-2;i>=0;--i){
// 当前位置能够到达的最远位置
int futhestJump=min(i+nums[i],n-1);
// 注意:i+1 <=
for (int j = i + 1; j <= futhestJump; j++) {
if (dp[j]) { // 如果从 j 位置可以跳到最后一个位置
dp[i] = true; // 则从 i 位置也可以跳到最后一个位置
break; // 不需要再检查后面的位置
}
}
}
return dp[0];
}
};
四、45.跳跃游戏②
1 题目
2 解题思路
采用动态规划方法
(1)状态表示
dp[i]表示为到达i位置最短的跳数。怎么理解?即从0到i-1位置,判断是否可以跳到该位置,然后选取其中最短跳数的dp[j]。注意这个j肯定是小于i的。那么如何判断是否可以跳到该位置呢?即 nums[j]+j>i即可。最后比较比较各个dp[i]前的dp[j]的大小关系即可。
(2)状态转移方程,if(nums[j]+j>i) dp[i]=min(dp[i],dp[j]+1);
(3)初始化,dp[0]=0;
3 code
class Solution {
public:
int jump(vector<int>& nums) {
// 状态定义
int n =nums.size();
vector<int> dp(n,INT_MAX);
// 初始化
dp[0]=0;
// 状态转移
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[j]+j>=i){
dp[i]=min(dp[i],dp[j]+1);
}
}
}
return dp[n-1];
}
};
五、763.划分字母区间
1 题目
2 解题思路
(1)同一个字母只能出现在同一个片段,显然同一个字母的第一次出现的下标位置和最后一次出现的下标位置必须出现在同一个片段。
(2)因此在遍历字符串过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界{当你出现的下标和自己当前的位置相等时就认为是最远边界分割点},说明这个边界就是分割点了。【隐含的含义就是此时前面出现过所有字母都已经达到了最远的边界了】
图解举例:【引用代码随想录Carl的图画的太好让人理解了,也很好的表达了本题的含义】
(3)实现步骤:
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
在得到每个字母最后一次出现的下标位置之后,可以使用贪心的方法将字符串划分为尽可能多的片段具体步骤如下:
使用哈希表数组统计每一个字符最后出现的位置 hash[s[i]−′a′]=i;
遍历字符,并更新字符的最远出现下标,如果找到字符出现的最远边界,当最远边界等于当前字符位置时说明可以划分了
返回结果 result。
3 code
class Solution {
public:
vector<int> partitionLabels(string s) {
vector<int>result;
// 定义哈希表统计字符出现的最远位置
int hash[26]={0};
for(int i=0;i<s.size();i++){
hash[s[i]-'a']=i;
}
// 找到字符出现的最远边界,当最远边界等于当前字符位置时,说明可以划分了
int right=INT_MIN;
int left=0;
for(int i=0;i<s.size();i++){
right=max(right,hash[s[i]-'a']);
if(right==i){
result.push_back(right-left+1);
left=i+1;
}
}
return result;
}
};