当前位置: 首页 > news >正文

每日算法-250404

记录今天完成的几道 LeetCode 算法题。


930. 和相同的二元子数组

题目

题目截图

思路

滑动窗口

解题过程

这道题的目标是找到和恰好等于 goal 的子数组个数。我们可以利用滑动窗口,通过一个巧妙的转换来求解:和等于 goal 的子数组数量 = (和小于等于 goal 的子数组数量) - (和小于等于 goal - 1 的子数组数量)

另一种思路是计算 (和大于等于 goal 的子数组数量) - (和大于等于 goal + 1 的子数组数量),这正是代码中采用的方法。

下面的 slidingWindow(nums, k) 函数用于计算和大于等于 k 的子数组数量:

  1. 使用 right 指针遍历数组,扩展窗口右边界,累加 sum
  2. 使用 while 循环检查当前窗口和 sum 是否大于等于 k (sum >= k)。
  3. 如果 sum >= k,说明当前以 right 为右边界的窗口和过大或正好,我们需要收缩左边界 left,直到 sum < k
  4. while 循环收缩完毕后,left 指针的位置意味着从索引 0left - 1 开始、以 right 结尾的子数组,其和都曾经满足(或在收缩前满足) >= k 的条件。因此,有 left 个这样的子数组。我们将 left 累加到结果 ret 中。

最终,slidingWindow(nums, goal) - slidingWindow(nums, goal + 1) 即为所求。

复杂度

  • 时间复杂度: O ( n ) O(n) O(n),其中 n 是数组 nums 的长度。每个元素最多被 leftright 指针访问两次。
  • 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数级别的额外空间。

Code

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        // 计算和 >= goal 的子数组个数
        int a = slidingWindowAtLeastK(nums, goal);
        // 计算和 >= goal + 1 的子数组个数
        int b = slidingWindowAtLeastK(nums, goal + 1);
        // 两者相减即为和 == goal 的子数组个数
        return a - b;
    }

    // 计算数组 nums 中和大于等于 k 的子数组的数量
    private int slidingWindowAtLeastK(int[] nums, int k) {
        // 处理 k < 0 的情况,虽然本题 nums[i] >= 0, goal >= 0,但写健壮点没坏处
        if (k < 0) {
             k = 0; // 如果 k 是负数,没有意义,可以根据题目约束调整
        }
        int ret = 0, sum = 0;
        for (int left = 0, right = 0; right < nums.length; right++) {
            sum += nums[right];
            // 当窗口和大于等于 k 时,收缩左边界
            while (left <= right && sum >= k) {
                // 注意:这里不能直接将 right - left + 1 加入结果
                // 我们需要累加的是 left 的值,表示有多少个起点可以构成 >= k 的子数组
                sum -= nums[left++];
            }
            // 此时 [left, right] 区间的和是 < k 的
            // 但对于当前 right,所有以 0 到 left-1 为起点的子数组和都 >= k
            // 所以累加 left
            ret += left;
        }
        return ret;
    }
}

2302. 统计得分小于 K 的子数组数目

题目

题目截图

思路

滑动窗口

解题过程

题目要求统计满足 sum * length < k 的子数组数量。

  1. 我们使用 right 指针遍历数组,扩展窗口右边界,同时维护窗口内的元素和 sum 以及窗口长度 len
  2. 使用 while 循环检查当前窗口 [left, right] 是否满足条件 sum * len < k
  3. 如果 sum * len >= k,说明当前窗口不满足条件,需要收缩左边界 left,同时更新 sumlen,直到窗口满足条件为止。
  4. while 循环结束后,当前窗口 [left, right] 是满足 sum * len < k 的。由于子数组越短,得分通常越小(因为元素非负),所以所有以 right 结尾,且起始位置在 [left, right] 区间内的子数组,都满足条件。
  5. 这样的子数组有 right - left + 1 个。将这个数量累加到结果 ret 中。

复杂度

  • 时间复杂度: O ( n ) O(n) O(n),其中 n 是数组 nums 的长度。每个元素最多被 leftright 指针访问两次。
  • 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数级别的额外空间(注意 sum 可能很大,使用 long 类型)。

Code

class Solution {
    public long countSubarrays(int[] nums, long k) {
        long ret = 0;
        long sum = 0;
        // int len = 0; // 可以直接用 right - left + 1 计算长度
        for (int left = 0, right = 0; right < nums.length; right++) {
            sum += nums[right];
            long len = right - left + 1; // 当前窗口长度
            // 当窗口得分不满足条件时,收缩左边界
            while (left <= right && sum * len >= k) {
                sum -= nums[left++];
                len = right - left + 1; // 更新长度
                // len--; // 之前的写法也可以,但直接计算更清晰
            }
            // 此时窗口 [left, right] 满足条件 sum * len < k
            // 所有以 right 结尾,起点在 [left, right] 内的子数组都满足
            ret += (right - left + 1); // 或者 ret += len;
        }
        return ret;
    }
}

3258. 统计满足 K 约束的子字符串数量 I

题目

题目截图

思路

滑动窗口 + 计数数组

解题过程

题目要求统计满足“0 的数量不超过 k” 且 “1 的数量不超过 k” 的子字符串数量。

  1. 使用 right 指针遍历字符串,扩展窗口右边界。
  2. 使用一个大小为 2 的数组 hash 来记录当前窗口内 ‘0’ 和 ‘1’ 的数量。hash[s[right] - '0']++ 更新计数。
  3. 使用 while 循环检查当前窗口 [left, right] 是否满足约束条件。窗口不合法当 hash[0] > k hash[1] > k 时。
  4. 如果窗口不合法,收缩左边界 left,并在 hash 数组中减去 s[left] 对应的计数 (hash[s[left++] - '0']--),直到窗口重新满足约束条件。
  5. while 循环结束后,当前窗口 [left, right] 是满足约束条件的(即 hash[0] <= khash[1] <= k)。
  6. 所有以 right 结尾,且起始位置在 [left, right] 区间内的子字符串,都满足约束条件。
  7. 这样的子字符串有 right - left + 1 个。将这个数量累加到结果 ret 中。

复杂度

  • 时间复杂度: O ( n ) O(n) O(n),其中 n 是字符串 s 的长度。每个字符最多被 leftright 指针访问两次。
  • 空间复杂度: O ( 1 ) O(1) O(1)hash 数组的大小是常数 2。

Code

class Solution {
    public int countKConstraintSubstrings(String ss, int k) {
        char[] s = ss.toCharArray();
        int ret = 0;
        int[] hash = new int[2];
        for (int left = 0, right = 0; right < s.length; right++) {
            hash[s[right] - '0']++;
            while (hash[0] > k && hash[1] > k) {
                hash[s[left++] - '0']--;
            }
            ret += (right - left + 1);
        }
        return ret;
    }
}

641. 设计循环双端队列

题目

题目截图

思路

使用数组模拟循环双端队列

解题过程

我们可以使用一个固定大小的数组来模拟循环双端队列的行为。

  • elem: 用于存储队列元素的数组。
  • length: 队列的容量 (即数组大小 k)。
  • size: 队列中当前存储的元素数量。
  • head: 指向队首元素的索引。
  • tail: 指向队尾元素的下一个可用位置的索引。

循环的关键:
使用模运算 % length 来实现索引的循环。

  • 尾部插入后,tail = (tail + 1) % length
  • 头部删除后,head = (head + 1) % length
  • 头部插入时,需要向前移动 headhead = (head - 1 + length) % length(加 length 是为了处理 head 为 0 时减 1 变成负数的情况)。
  • 尾部删除时,需要向前移动 tailtail = (tail - 1 + length) % length

重要方法实现:

  • insertFront(): 如果队列未满,计算新的 head 索引 (head - 1 + length) % length,在该位置插入元素,并增加 size
  • insertLast(): 如果队列未满,在当前的 tail 索引处插入元素,更新 tail 索引 (tail + 1) % length,并增加 size
  • deleteFront(): 如果队列非空,更新 head 索引 (head + 1) % length,并减少 size
  • deleteLast(): 如果队列非空,更新 tail 索引 (tail - 1 + length) % length,并减少 size
  • getFront(): 如果队列非空,返回 elem[head]
  • getRear(): 如果队列非空,返回队尾元素。队尾元素实际存储在 tail 指针的前一个位置,即 elem[(tail - 1 + length) % length]
  • isEmpty(): 检查 size == 0
  • isFull(): 检查 size == length

复杂度

  • 时间复杂度: 构造方法和所有操作方法(插入、删除、获取、判断空/满)都是 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( k ) O(k) O(k),其中 k 是队列的容量,用于存储队列元素的数组。

Code

class MyCircularDeque {

    private int[] elem; // 存储元素的数组
    private int capacity; // 队列容量 (即 k)
    private int size;   // 当前元素数量
    private int head;   // 队首元素索引
    private int tail;   // 队尾元素下一个插入位置的索引

    public MyCircularDeque(int k) {
        capacity = k;
        elem = new int[capacity];
        size = 0;
        head = 0; // 初始时 head 和 tail 可以在任意位置,只要它们的关系正确
        tail = 0; // 通常 head=0, tail=0 表示空队列
    }

    public boolean insertFront(int value) {
        if (isFull()) {
            return false;
        }
        // 计算新的 head 位置,注意处理负数取模
        head = (head - 1 + capacity) % capacity;
        elem[head] = value;
        size++;
        return true;
    }

    public boolean insertLast(int value) {
        if (isFull()) {
            return false;
        }
        // 在当前 tail 位置插入
        elem[tail] = value;
        // 计算新的 tail 位置
        tail = (tail + 1) % capacity;
        size++;
        return true;
    }

    public boolean deleteFront() {
        if (isEmpty()) {
            return false;
        }
        // head 后移一位
        head = (head + 1) % capacity;
        size--;
        return true;
    }

    public boolean deleteLast() {
        if (isEmpty()) {
            return false;
        }
        // tail 前移一位
        tail = (tail - 1 + capacity) % capacity;
        size--;
        return true;
    }

    public int getFront() {
        if (isEmpty()) {
            return -1;
        }
        return elem[head];
    }

    public int getRear() {
        if (isEmpty()) {
            return -1;
        }
        // 最后一个元素在 tail 的前一个位置
        int lastElementIndex = (tail - 1 + capacity) % capacity;
        return elem[lastElementIndex];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return size == capacity;
    }
}

/**
 * Your MyCircularDeque object will be instantiated and called as such:
 * MyCircularDeque obj = new MyCircularDeque(k);
 * boolean param_1 = obj.insertFront(value);
 * boolean param_2 = obj.insertLast(value);
 * boolean param_3 = obj.deleteFront();
 * boolean param_4 = obj.deleteLast();
 * int param_5 = obj.getFront();
 * int param_6 = obj.getRear();
 * boolean param_7 = obj.isEmpty();
 * boolean param_8 = obj.isFull();
 */
http://www.dtcms.com/a/111866.html

相关文章:

  • 南京大学与阿里云联合启动人工智能人才培养合作计划,已将通义灵码引入软件学院课程体系
  • Swift LeetCode 246 题解:中心对称数(Strobogrammatic Number)
  • Maven的下载配置及在Idea中的配置
  • 【云计算互联网络】 专线、VPN与云网关技术对比
  • Vue2 组件创建与使用
  • TDengine 中的视图
  • Spring Boot 可扩展脱敏框架设计全解析 | 注解+策略模式+模板方法模式实战
  • Python Requests 库终极指南
  • Redis-13.在Java中操作Redis-Spring Data Redis使用方式-操作哈希类型的数据
  • 免费内网穿透方法
  • LocaDate、LocalTime、LocalDateTime
  • 如何设计好一张表
  • LLM 性能优化有哪些手段?
  • 软件工程面试题(二十七)
  • 硬件电路(23)-输入隔离高低电平有效切换电路
  • MYOJ_4342:(洛谷P1087)[NOIP 2004 普及组] FBI 树(二叉树实操,递归提高)
  • SQL Server数据库异常-[SqlException (0x80131904): 执行超时已过期] 操作超时问题及数据库日志已满的解决方案
  • Arduino示例代码讲解:Ping
  • c语言学习16——内存函数
  • 面向对象(2)
  • 多模态技术概述(一)
  • Visio | 将(.vsdx)导出为更清楚/高质量的图片(.png) | 在Word里面的visio图
  • 从感光sensor到显示屏,SOC并非唯一多样
  • 手动将ModelScope的模型下载到本地
  • Eclipse怎么创建java项目
  • 前端快速入门学习2-HTML
  • 编写实现一个简易的域名服务器
  • 长龙通信机CAN数据查看(工具版)
  • AI Agent设计模式一:Chain
  • 出现次数最多的子树元素和——深度优先搜索