牛客-模拟、枚举、贪心 2022.11.12

本文探讨了滑动窗口在解决字符串和数组操作问题中的应用,包括寻找子串中最少包含所有字母的最短长度,以及通过最小操作数使数组元素相等。此外,还涉及到了在不同场景下如何使用滑动窗口优化算法,如在处理连续1和0的计数以及循环数组问题时。

字符串

滑动窗口,写判定当前子串是否包含所有的字母

#include <bits/stdc++.h>
using namespace std;
int num[26] = {0};
bool bingo() {
    for(int i = 0; i < 26; i++) {
        if(num[i] == 0)
            return false;
    }
    return true;
}
int main() {
    memset(num, 0, sizeof(num));
    string s;
    cin >> s;
    int start = 0, res = INT_MAX;
    for(int i = 0; i < s.size(); i++) {
        num[s[i] - 'a'] += 1;
        if(bingo()) {
            res = min(res, i - start + 1);
            while(start < i) {
                num[s[start] - 'a'] -= 1;
                start += 1;
                if(!bingo())
                    break;
                res = min(res, i - start + 1);
            }
        }
    }
    cout << res << endl;
    return 0;
}

加减

对数组数据按照从小打大排序,前缀和记录相邻数据之间的差值,经过k次操作如果需要相同数据出现的次数最多,那么肯定是变化为数组中已经存在的数字,以当前数组位置作为左端点,二分求解右端点的位置。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
long long f[maxn];
long long delta[maxn];
long long k;
bool judge(int l,int r) {
    int mid = (l + r)>>1;
    //处于[l, mid]区间的所有数变成mid需要的操作数
    long long left = f[mid] * (mid - l + 1) - (delta[mid] - delta[l-1]);
    //处于[mid + 1, r]区间的所有数变成mid需要的操作数
    long long right =  (delta[r] - delta[mid]) - (r - mid) * f[mid];
    long long ans = left + right;
    return ans <= k ? true : false;
}
int main() {
    int n;
    cin >> n >> k;
    for(int i = 1; i <= n; i++)
        cin >> f[i];
    sort(f + 1, f + n + 1);
    for(int i = 1; i <= n; i++)
        delta[i] = delta[i - 1] + f[i];
    int res = 0;
    for(int i = 1; i <= n; i++) {
        int l = i, r = n;
        while(l < r) {
            int mid = (l + r + 1)>>1;
            if(judge(i, mid))
                l = mid;
            else
                r = mid - 1;
        }
        res = max(res, r - i + 1);
    }
    cout << res << endl;
    return 0;
}

牛牛的木板

第一遍写的思路是:统计连续的1和0的个数,1的个数为正数记录,0的个数为负数记录,之后遍历连续数组每个位置,以当前位置为起点,寻找最后的端点。但是总是超时,于是考虑能否二分,但是二分其实判定的函数时间复杂度也是O(n).之后再想,其实不需要做一个连续化,直接在原本数组上进行滑动窗口操作,即可判定。

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int 
     * @param m int 
     * @param a intvector 
     * @return int
     */
    int solve(int n, int m, vector<int>& a) {
        // write code here
        int res = 0, sum = 0, start = 0, end = 0;
        while(end < a.size()) {
            while(end < a.size()) {
                if(a[end] == 1)
                    sum = sum + 1;
                else {
                    if(m > 0) {
                        m -= 1;
                        sum = sum + 1;
                    } else
                        break;
                }
                end = end + 1;
            }
            res = max(res, sum);
            while(start < a.size() && m <= 0) {
                if(a[start] == 1) {
                    sum -= 1;
                } else {
                    m += 1;
                    sum -= 1;
                }
                start += 1;
            }
        }
        return res;
    }
};

丢手绢

滑动窗口,由于顺逆时针,所以数组元素要存储两遍,进行前后拼接形成循环

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    cin >> n;
    vector<int> p;
    int sum = 0;
    for(int i = 0; i < n; i++) {
        int a;
        cin >> a;
        p.emplace_back(a);
        sum = sum + a;
    }
    int res = INT_MIN;
    for(int i = 0; i < n; i++)
        p.emplace_back(p[i]);
    int start = 0, end = 0, sum1 = 0;
    while(end < p.size()) {
        while(end < p.size()) {
            sum1 = sum1 + p[end];
            res = max(res, min(sum - sum1, sum1));
            end += 1;
            if(sum1 * 2 >= sum)
                break;
        }
        res = max(res, min(sum - sum1, sum1));
        while(start < p.size() && sum1 * 2 >= sum) {
            sum1 -= p[start];
            start += 1;
        }
    }
    cout << res << endl;
    return 0;
}

网的算法入门班通常会围绕模拟枚举贪心这三种基础但重要的算法思想展开,尤其是在秋季训练营中,这类内容往往被安排在第一章或早期阶段,以便为新手打下扎实的基础。虽然无法直接提供2021秋季算法入门班的具体题目链接或完整题集,但可以结合常见的典型习题及解析方式来帮助理解这些算法的应用。 ### 模拟类题目 模拟类题目通常是根据题意进行一步步操作模拟,比如处理队列、栈的操作,或者按照某种规则进行状态更新的问题。 **示例题目:** - 题目描述:给定一个整数序列和一个操作次数 k,每次操作将数组中的每个元素替换为其后一个元素的值(最后一个元素变为第一个元素)。 - 解法思路:使用循环和数组拷贝即可实现,也可以通过数学推导优化成 O(n) 的时间复杂度。 - 示例代码: ```cpp #include <bits/stdc++.h> using namespace std; void rotateArray(vector<int>& nums, int k) { int n = nums.size(); vector<int> temp = nums; for (int i = 0; i < n; ++i) { nums[(i + k) % n] = temp[i]; } } int main() { vector<int> nums = {1, 2, 3, 4, 5}; int k = 2; rotateArray(nums, k); for (int num : nums) cout << num << " "; return 0; } ``` ### 枚举类题目 枚举类问题的核心是找出所有可能的情况,并从中筛选出满足条件的解。常见于子数组、子字符串、排列组合等问题。 **示例题目:** - 题目描述:给定一个整数数组,求出所有连续子数组的和,并找出最大值。 - 解法思路:暴力枚举所有子数组起点和终点,计算其和并维护最大值。 - 更优解法可使用 Kadane 算法实现 O(n) 时间复杂度[^1]。 - 示例代码(暴力枚举): ```cpp #include <bits/stdc++.h> using namespace std; int maxSubArraySum(int arr[], int n) { int max_sum = INT_MIN; for (int i = 0; i < n; ++i) { int current_sum = 0; for (int j = i; j < n; ++j) { current_sum += arr[j]; max_sum = max(max_sum, current_sum); } } return max_sum; } int main() { int arr[] = {-2, -1, -3, 4, -1, 2, 1, -5, 4}; int n = sizeof(arr) / sizeof(arr[0]); cout << "Maximum subarray sum is " << maxSubArraySum(arr, n) << endl; return 0; } ``` ### 贪心类题目 贪心算法的关键在于每一步都选择当前状态下最优的选择,希望最终结果全局最优。这类问题常用于活动选择、任务调度、硬币找零等场景。 **示例题目:** - 题目描述:给定一组物品的价值与重量,以及背包的最大承重,求能装入背包的最大总价值(每种物品可取部分)。 - 解法思路:计算每个物品的单位重量价值,按从高到低排序,优先选取性价比高的物品。 - 示例代码: ```cpp #include <bits/stdc++.h> using namespace std; struct Item { int value, weight; double ratio; }; bool compare(Item a, Item b) { return a.ratio > b.ratio; } double fractionalKnapsack(int capacity, vector<Item>& items) { sort(items.begin(), items.end(), compare); double totalValue = 0.0; for (const auto& item : items) { if (capacity == 0) break; double take = min((double)item.weight, (double)capacity); totalValue += take * item.ratio; capacity -= take; } return totalValue; } int main() { vector<Item> items = {{60, 10}, {100, 20}, {120, 30}}; int capacity = 50; for (auto& item : items) item.ratio = (double)item.value / item.weight; cout << "Maximum value in Knapsack: " << fractionalKnapsack(capacity, items) << endl; return 0; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值