贪心算法
题目1:分割数组为连续子序列[659]
给你一个按 非递减顺序 排列的整数数组 nums 。
请你判断是否能在将 nums 分割成 一个或多个子序列 的同时满足下述 两个 条件:
- 每个子序列都是一个 连续递增序列(即,每个整数 恰好 比前一个整数大 1 )。
- 所有子序列的长度 至少 为 3 。
如果可以分割 nums 并满足上述条件,则返回 true ;否则,返回 false 。
方法一 贪心算法
算法思路
我们维护两个哈希表,其中一个哈希表命名为nc,用于存储数组nums中的每个元素num的个数。另一个哈希表命名为tail,用于存储以num结尾的连续子序列。
- 首先将数组nums中的每个元素作为键值存入哈希表nc中,代表了nums中每个元素的个数。
- 之后遍历数组nums,如果nums中的某个元素num的个数大于0,num+1的个数大于0以及num+2的个数大于0。可以认定合适一个长度至少为三的连续递增子序列。然后,以num+2结尾的连续递增子序列的个数可以加1,即tail[num+2]++。同时,由于已经用掉了num,num+1和num+2,因此,nc中的对应的num、num+1和num+2的个数要减掉1。
- 如果某个num的个数已经为0,那就跳过这个数,看下个数开始是否有连续的递增子序列。
- 如果某个num的个数大于0,同时,以num-1结尾的递增子序列至少有一个,那么,我们就可以把这个num添加到tail[num-1]后,形成更长长度的递增子序列(这个原来的递增子序列的长度一定是大于3的,因为只有长度大于3的递增子序列tail才会加1)。添加完之后,相应的nums[num]要减1(因为这个数已经被用过了),同时tail[num-1]也要减1(因为已经有了更长的连续递增子序列,原来的就要舍弃,毕竟题目要求至少有一个递增子序列就够了)。理所当然,tail[num+1]也要加1。
- 如果上述条件都不能满足,那自然就只能返回false了。
这里的贪心思想是尽量彼岸建立新的连续递增子序列,始终优先考虑能否在原来的基础上增加其长度。如果无法在原来的基础上增加长度,看看能否与后面的数组成连续递增子序列,如果都不满足,直接返回false。
因为如果整个数组是连续递增的,我们至少能得到一个连续递增的子序列。
代码
/*
* @lc app=leetcode.cn id=659 lang=cpp
*
* [659] 分割数组为连续子序列
*/
// @lc code=start
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
class Solution {
public:
/**
* @brief 长度为3的上升子序列
*
* @param nums
* @return true
* @return false
*/
bool isPossible(vector<int> &nums) {
unordered_map<int, int> nc, tail;
for (auto num : nums) {
nc[num]++;
}
for (auto num : nums) {
if (nc[num] == 0) {//< 被用完的数直接跳过
continue;
} else if (nc[num] > 0 && tail[num - 1] > 0) {
nc[num]--;
tail[num - 1]--;
tail[num]++;
} else if (nc[num] > 0 && nc[num + 1] > 0 && nc[num + 2] > 0) {
nc[num]--;
nc[num + 1]--;
nc[num + 2]--;
tail[num + 2]++;
} else {
return false;
}
}
return true;
}
};
题目2:买卖股票最佳时期含手续费[714]
给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
方法一 贪心算法
算法思路
这道题目有点像脑筋急转弯,这道题的贪心思想在于每一次购买股票都要有收益(收益=卖出的价格-买进的价格-手续费),如果没有收益,那这一次就不购买股票。首先,每一次交易的手续费是固定的,因此这个手续费也可以算作买进时的成本,在第一次的时候,我们手上没有股票,因此我们买入的成本就等于股票的价格加上手续费(即股票的价格+手续费)。但是这只股票我们不一定要一直留在手上,如果说我们碰到一只成本更低的股票,那么就要毫不犹豫的换掉这只股票(注意,这里说的是换掉这只股票,而不是抛掉这只股票,相当于先看上这只股票后观望一下,然后遇到成本更低的股票就忘记之前那只,看之后的股票交易有没有收益)。如果说碰到一只股票交易有利润,立马进行交易。保证每一次都有收益。
代码
/*
* @lc app=leetcode.cn id=714 lang=cpp
*
* [714] 买卖股票的最佳时机含手续费
*/
// @lc code=start
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int maxProfit(vector<int> &prices, int fee) {
int buy = prices[0] + fee; // 这里提前将手续费算入买入是的成本而不是卖出时的成本
int profit = 0;
for (int i = 1; i < prices.size(); i++) {
if (prices[i] + fee < buy) { // 如果遇到一只股票成本更低,”换成“这只股票
buy = prices[i] + fee; // 更新新的股票成本
} else if (prices[i] > buy) { // 如果有利润,抛掉这只股票
profit += prices[i] - buy; // 计算利润
buy = prices[i]; // 这里是理解的关键,问题在于,为什么这里的成本不是prices+fee?
// 因为在于如果后一个的股票价格更高,那么仍然是回到这里,以更高的价格卖出这个股票,由于已经减过一个手续费了,因此不用再减了。
// 如果后续的股票价格跌了,跌倒这个价格已经小于buy-fee之下了,那么我们可以把这只股票再”买下来”(并不是真的买下来)。
// 如果这只股票没有跌的很惨,也米有说涨,那其实可以不用考虑这只股票,反正也没有收益。
}
}
return profit;
}
};
方法二 动态规划
这道题其实还是有一定的理解难度的,因为他其实和我们现实生活中的股票交易不是一致的,它可以“反悔”。也就是说,当我看重一只股票,我不一定非要买它,当我卖出一只股票,我也不一定是一定要卖它。我可以观察,如果卖出他有收益,我马上卖(不是真的卖),如果后续股票价格高了,我也可以再按照这个价格卖。如果说后续有价格更低的股票(即价格加手续费比现在价格低的股票)我就一定抛出,购入(不是真的购入)这只股票。后续股票涨价了,我有收益,没有涨价,当我没买,利润还是没亏。如果说有一只股票它的价格也没涨,也没低到我可以我愿意换成这只股票,那我就不考虑它,反正也收益。
这道题如果没有股票两个字的话,可以看成求一个最长递增子序列。计算子序列最后一个和第一个值之间的差值,就可以得到最大利润。求最长递增子序列可以用动态规划的方法。(动态规划又是一个大坑o( ̄┰ ̄*)ゞ)。不过这里不是求最长递增子序列的长度,而是最长递增子序列中最后一个和第一个的元素。这样的话问题其实更为复杂,笔者暂时不给出求解,不过可以给出一遍参考文章(求解给定序列的最长递增子序列),有兴趣的同学可以参考。
浙公网安备 33010602011771号