【贪心算法题记录】376. 摆动序列

题目链接

题目描述

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。

相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

题目分析

先理解题意错误了。。以为是像那种滑动窗口的类型,结果发现可以记录不连续的元素ORZ

这题其实不需要真的删除元素,我们统计满足摆动条件的元素就可以,也就是计算prediff (nums[i]-nums[i-1])curdiff (nums[i+1]-nums[i]),然后通过比较preDiffcurDiff的大小来判断当前趋势是否是摆动的,直接想法是:两者一个小于0,一个大于0,就是摆动的趋势。但是深入思考会发现,还有这三种情况要考虑进去:

  1. 有平坡,即相同元素挨在一起,如下图所示
    在这里插入图片描述
    我们可以规定删除前面的相同元素。
  • 对于第一个2:preDiff >0,curDiff = 0,它是不满足摆动的,所以不计入结果;
  • 对于第二个和第三个2:preDiff = 0, curDiff = 0,也不满足摆动,所以也不计入结果;
  • 对于最后一个2:preDiff = 0,curDiff < 0,它是满足摆动的,我们要将它计入结果。

通过上述分析,我们发现当满足preDiff = 0,curDiff < 0时,我们也要记录结果,所以综合起来我们判断摆动的条件就是(preDiff >= 0 && curDiff < 0) || (preDiff <= 0 && curDiff > 0)

  1. 数组首位两端,例如只有[2, 5]
    因为题目中已经说了,如果只有两个不同的元素,那么摆动序列也是2。我们可以在一开始判断数组的长度,将这种情况排除在我们写的逻辑之外。

但是如果要将其与当前逻辑融合在一起的话:

  • 首端元素:假设它前面有一个相同元素,那么它的preDiff = 0,curDiff = 3,依照我们上面规定的条件,它是满足摆动的。
  • 末尾元素:我们在判断条件的时候要依据三个元素,当我们判断到最后一个元素的时候,它后面就没有另外的元素了,那么我们就默认最后一个元素是满足的,将结果result初始化为1。

通过上面的分析,我们可以写出下面的代码(没有考虑情况3):

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int curDiff = 0; // 当前一对差值
        int preDiff = 0; // 前一对差值
        int result = 1;  // 记录峰值个数,序列默认序列最右边有一个峰值
        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i + 1] - nums[i];
            // 出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                result++;
            }
            preDiff = curDiff;
        }
        return result;
    }
};
  1. 单调坡中有平坡,例如[1, 2, 2, 2, 3, 4]
    在这里插入图片描述
    如果按照我们上面的思路,到第三个2的时候,就会判断此时满足摆动,所以最终结果会记录3个,但实际上这是一个非单调递减的序列,因此只有头和尾是满足摆动的。这是因为忽略了从首端的1到第一个2的趋势是增的,而在判断最后一个2时,我们没有考虑这一点。也就是说我们的preDiff并没有保留历史信息,我们要让坡度改变的时候再去更新preDiff。因此上述代码可以改进为:
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int curDiff = 0; // 当前一对差值
        int preDiff = 0; // 前一对差值
        int result = 1;  // 记录峰值个数,序列默认序列最右边有一个峰值
        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i + 1] - nums[i];
            // 出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                result++;
                preDiff = curDiff; // 注意这里,只在摆动变化的时候更新prediff
            }
        }
        return result;
    }
};
  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图着色问是指给定一个无向图,尝试用最少的颜色对每个顶点进行染色,使得任意两个相邻的顶点颜色不同。这是一个经典的NP难问,没有有效的多项式时间算法,但是可以使用贪心算法来近似解决。 贪心算法的基本思路是:首先按照某种规则选择一个顶点进行染色,然后依次选择其它未染色的顶点,并尝试用最少的颜色进行染色。如果当前顶点的相邻顶点都已经染过色,那么就选择一个未使用的颜色进行染色,否则选择一个不与相邻顶点颜色相同的颜色进行染色。 C++代码实现如下: ```c++ #include <iostream> #include <vector> #include <algorithm> using namespace std; const int MAXN = 100; // 最大顶点数 int n, m; // n 表示顶点数,m 表示边数 vector<int> G[MAXN]; // 存储图的邻接表 int color[MAXN]; // 存储每个顶点的颜色 int greedy_coloring() { int ans = 0; for (int u = 0; u < n; ++u) { bool used[MAXN] = { false }; for (int i = 0; i < G[u].size(); ++i) { int v = G[u][i]; if (color[v] != -1) { used[color[v]] = true; } } for (int i = 0; ; ++i) { if (!used[i]) { color[u] = i; ans = max(ans, i); break; } } } return ans + 1; // 返回使用的颜色数 } int main() { cin >> n >> m; for (int i = 0; i < m; ++i) { int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } fill(color, color + n, -1); cout << "使用的颜色数:" << greedy_coloring() << endl; for (int i = 0; i < n; ++i) { cout << "顶点 " << i << " 的颜色是 " << color[i] << endl; } return 0; } ``` 该代码中,我们使用邻接表存储图,并使用一个数组 `color` 存储每个顶点的颜色,初始化为 -1 表示未染色。在 `greedy_coloring` 函数中,我们依次遍历每个顶点,对于每个未染色的顶点,我们枚举可用的颜色,选取第一个未被使用的颜色进行染色。在枚举颜色的过程中,我们需要检查相邻顶点的颜色,标记已经使用的颜色。 该算法的时间复杂度为 $O(n^2)$,因为对于每个顶点,都需要遍历其相邻顶点。当然,使用邻接表存储图可以优化到 $O(m)$ 的时间复杂度。该算法是一种近似算法,不一定能够得到最优解,但是在实际应用中效果很好。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值