【leetcode刷题记录】

leetcode刷题记录

一、滑动窗口

  1. 题目介绍

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

  1. 解题思路

由于是找最小子数组,便很容易想到如果排序后用最大的一部分数求和,便能得到最小数组的长度。备考408时快速排序写的十分熟练,这里便直接用上了。

相关代码段:

int partition(int *nums, int left, int right)
{
    int mid = nums[left];
    while (left < right)
    {
        while (nums[right] >= mid && left < right)
        {
            right--; // 确保每次划分右边的数都比此次确定位置的元素值大
        }
        nums[left] = nums[right];
        while (nums[left] <= mid && left < right)
        {
            left++; // 确保每次划分左边的数都比此次确定位置的元素值小
        }
        nums[right] = nums[left];
    }
    nums[left] = mid;
    return left; // 返回划分位置
}
void Quicksort(int *nums, int left, int right)
{
    if (left >= right)
        return;
    int M = partition(nums, left, right);
    Quicksort(nums, left, M - 1);//采用递归左右分别快排
    Quicksort(nums, M + 1, right);
}
int minSubArrayLen(int target, int *nums, int numsSize)
{
    int res = 0, sum = 0;
    Quicksort(nums, 0, numsSize - 1);
    for (int i = numsSize - 1; i >= 0; i--)
    {
        sum += nums[i];
        res++;
        if (sum >= target)
        {
            break;
        }
    }
    if (sum < target)
        return 0;
    return res;
}

但在提交时,只通过了一半的测试,原因在于没有注意到题目中给出的示例:子数组 [numsl, numsl+1, …, numsr-1, numsr]子数组的元素必须在所给最初的数组中连续,而快排后改变了数组排序,可能使得原来相邻的元素分离。
再容易想到的就是用两个for循环暴力求解,但这种方法实际情况一般是无奈之举,这里并不考虑。
这里主要介绍滑动窗口的方法:滑动窗口的实质就是将暴力求解的两个for循环通过一个for循环来实现,原理是不再是像暴力求解中的枚举每种情况,而是在已符合的情况中通过移动左指针来寻找更小的子数组。
附上源码:

#include <stdint.h>
int minSubArrayLen(int target, int *nums, int numsSize)
{
    int res = INT32_MAX;
    int left = 0, sum = 0;
    for (int right = 0; right < numsSize; right++)
    {
        sum += nums[right];
        while (sum >= target)
        {
            res = res < (right - left + 1) ? res : right - left + 1;//满足条件记录一次数组长度
            sum -= nums[left++];//左指针右移
        }
    }
    return res == INT32_MAX ? 0 : res;//判断res值是否改变,若未改变,即未找到
}
  1. 相关题目
    -904.水果成篮
    最开始我把res更新语句(与right-left+1比大小)放到了while循环中,这是个逻辑错误,res的更新应该在满足条件的前提下,在最小子数组中,每次收缩时均需要sum>=target,此时更新是满足要求的,而该题中while循环条件是kind>2,循环体中不满足题目要求,不应该在此时更新res,调整后可以得到正确答案。

附上源码:

int totalFruit(int *fruits, int fruitsSize)
{
    int left = 0, right = 0;
    int trees[100000] = {0}; // 存放各种果树棵数
    int kind = 0;            // 记录果树种类
    int res = 0;
    for (; right < fruitsSize; right++)
    {
        trees[fruits[right]]++; // 该种果树棵数加一
        if (trees[fruits[right]] == 1)
            kind++;      // 第一次加入,种类加一
        while (kind > 2) // 种类大于2时收缩左边界
        {
            trees[fruits[left]]--; // 该种果树棵数减一
            if (trees[fruits[left]] == 0)
                kind--; // 不存在此类果树种类减一
            left++;
        }
        res = res > right - left + 1 ? res : right - left + 1;
    }
    return res;
}

-76.最小覆盖子串
这个题卡了我好久,主要的问题是如何判断移动窗口中是否覆盖所给字符串。最开始想到的是暴力求解,但时间开销太大。在求解方法中看到了灵茶山艾府的方法,感觉不错。将涵盖理解为cntS 中的每个字母及其出现次数,如都大于等于 cntT 中的字母出现次数,让我眼前一亮。其余的部分在最小子数组的基础上稍加修改即可。

附上源码:

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
bool is_covered(int cnt_s[], int cnt_t[])
{
    for (int i = 'A'; i <= 'Z'; i++)
    {
        if (cnt_s[i] < cnt_t[i])
        {
            return false;
        }
    }
    for (int i = 'a'; i <= 'z'; i++)
    {
        if (cnt_s[i] < cnt_t[i])
        {
            return false;
        }
    }
    return true;
}

char *minWindow(char *s, char *t)
{
    int ans_left = -1, ans_right = INT32_MAX / 2;
    int cnt_s[128] = {0}; // s 子串字母的出现次数
    int cnt_t[128] = {0}; // t 中字母的出现次数
    for (int i = 0; t[i]; i++)
    {
        cnt_t[t[i]]++; // 初始化待查询字符串中字符出现次数
    }

    int left = 0;
    for (int right = 0; s[right]; right++)
    {                      // 移动子串右端点
        cnt_s[s[right]]++; // 右端点字母移入子串
        while (is_covered(cnt_s, cnt_t))
        { // 涵盖时才能够更新左右指针以及收缩左指针
            if (right - left < ans_right - ans_left)
            {                    // 找到更短的子串
                ans_left = left; // 记录此时的左右端点
                ans_right = right;
            }
            cnt_s[s[left]]--; // 左端点字母移出子串
            left++;
        }
    }

    if (ans_left < 0)
    {
        return "";
    }
    s[ans_right + 1] = '\0';
    return s + ans_left;
}

二、总结

滑动窗口的使用需要满足一种“单调性”,如数组越大越满足条件。基础格式为右指针逐渐右移,在满足所给题目的情况下,收缩数组,即左指针右移。要注意在满足条件的情况下更新返回值(更新语句放在while函数体内还是外)。而且在最小覆盖子串题目中,第一次见到For循环中拿字符串元素当循环条件(字符类型不为’\0’时执行循环体),以及返回值是指针+整型,收获颇多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值