Coding Practice,48天强训(29)

这几天好不容易想到五一假期能够有时间多赶下学习进度,结果婚礼的一堆事儿又忙的焦头烂额,计划总是赶不上变化,一步一步慢慢来吧。

Topic 1:排序子序列(模拟 + 贪心)

排序子序列_牛客笔试题_牛客网

#include <bits/stdc++.h>
using namespace std;

int main() 
{
    int n; cin >> n;
    vector<int> A(n);
    for(int i = 0; i < n; i++) cin >> A[i];

    int count = 1; // 至少一段
    int trend = 0; // 0: 未定, 1: 递增, -1: 递减

    for(int i = 1; i < n; i++) //从1开始,0被视作第1段
    {
        if(A[i] > A[i - 1]) // 如果后一个>前一个
        {
            if(trend == -1) //之前的趋势如果是递减的
            {
                count++;//要从新记一段了
                trend = 0;//趋势变为均势
            } 
            else //之前的趋势是均势或者递增的
            {
                trend = 1;//维持在这个组内,维持趋势为递增即可
            }
        } 
        else if(A[i] < A[i - 1]) // 如果后一个<前一个
        {
            if(trend == 1) //之前的趋势如果是递增的
            {
                count++;
                trend = 0;
            } 
            else 
            {
                trend = -1;
            }
        }
        // 如果相等,trend 保持不变
    }

    cout << count << endl;
    return 0;
}

比较简单的归纳,遇到了变化点就判断和上一次趋势的比较,如果趋势为均势or相同的趋势,归纳到一堆,否则就另起炉灶;


Topic 2:消减整数(bfs, 归纳法优化)

C-消减整数_牛客小白月赛32

用BFS能做,但是用贪心和归纳更快

两条行动路径,我要找最短的肯定优先选2a,但是2a又需要满足最后结果恰好为0,那么也就是

(y-(n1*2a)- (n2*2*2a)-(n3*2*2*2a)- …)=0,这无数个2a组成了结果,这一坨n都可以简化为一个大N

=> y-N*2a= 0, 也就是y减N个2a恰好等于0

=> y % 2a = 0,那么当y % 2a等于0时,最后结果就能恰好为0,只是操作了N次而已
即y满足y%2a=0 时,优选选择-2a操作

#include <bits/stdc++.h>
using namespace std;


int minSteps(int &h)
{
    int res = 0, a = 1;
    while(h)
    {
        h -= a;
        res++;
        if(h % (a * 2) == 0) a *= 2;
    }
    return res;
}

int main() 
{
    int t, h;
    cin >> t;
    while (t--) 
    {
        cin >> h;
        cout << minSteps(h) << endl;
    }
    return 0;
}

Topic 3:最长上升子序列(二)(dp 贪心 二分)

最长上升子序列(二)_牛客题霸_牛客网

对应LeetCode:300.最长递增子序列

class Solution {
public:
    int LIS(vector<int>& a) 
    {
        int n = a.size();
        if (n == 0) return 0;
        vector<int> dp(n, 1);// dp[i]:下标为i时最长子序列的长度 dp[i] = max(dp[i], dp[j] + 1)   dp[j]:0 - j 之间最长严格上升序列的长度 j < i
        int res = 1;
        for (int i = 1; i < n; ++i)// 从下标1开始,dp[0]之前被初始化为1 
        {
            for (int j = 0; j < i; ++j) 
            {
                if (a[i] > a[j]) dp[i] = max(dp[i], dp[j] + 1);
            }
            res = max(res, dp[i]);
        }
        return res;
    }
};

最初的样子,但是会超时,所以要用贪心加二分来优化

我们维护一个数组 dp

完成的dp表示最长的上升子序列

dp[i]表示:长度为i + 1的上升子序列最小结尾元素,也可以把dp[0]特殊处理让下标对应

子序列要严格上升,所以结尾元素越小,越容易接上更大的数,形成更长的序列;所以我们希望:在所有可能长度的上升子序列中,结尾尽量小

比如14和17,此时就取4

因为长度相同的情况下4后还能接567,而7只能接>7的

那么以14756举例,我创建一个dp容器,然后开始遍历原数组a{1,4,7,5,6},如果遍历的元素比dp中储存的最后一个元素大,意味着可以插入这个dp,构成最长的上升子序列

1 插入;4 插入;7 插入; 5,5这里要处理了,如果比7大的话,正常插入即可,但是5比7小,此时把5作为dp[2]的值,因为相比于1 4 7 而言 1 4 5同样的长度,但结尾数字更小,然后继续遍历,6 插入;最后dp中存着 1 4 5 6,此时dp的size()就是最终最大的上升子序列元素个数了;

class Solution {
public:
    int LIS(vector<int>& a) 
    {
        vector<int> dp; 
        // dp[i]: 长度为 i+1 的上升子序列的最小结尾元素。
        // 这个数组不一定是实际的子序列,它是用来辅助计算长度的。

        for (const auto& e : a) // 遍历输入数组a的每个元素e
        {
            // 在dp中二分查找第一个 >= e 的位置
            // 目的是:找到e在dp中可以接在谁后面,或替换谁
            auto it = lower_bound(dp.begin(), dp.end(), e); 

            // 如果e比dp中所有元素都大,说明e可以扩展出一个更长的子序列
            if (it == dp.end()) dp.push_back(e); 

            // 否则,用e替换掉第一个 >= e 的位置,保持序列结尾尽可能小
            else *it = e;  
        }

        return dp.size();  // dp的长度就是最长上升子序列的长度
    }
};

代码的重点在于lower_bound:

auto it = lower_bound(begin, end, x); —— 在一个有序数组(非降序)中,找到第一个大于等于给定值 x”的元素位置。

也可以自己手写:

int my_lower_bound(const vector<int>& a, int x) 
{
    int left = 0, right = a.size(); // 注意:right 是 a.size(),不是 a.size() - 1
    while (left < right) 
    {
        int mid = left + (right - left) / 2;
        if (a[mid] < x) 
        {
            left = mid + 1;
        } 
        else 
        {
            right = mid;
        }
    }
    return left; // 返回的是第一个 >= x 的下标(可能是 a.size(),表示没有)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值