东莞网站推广方式上海何鹏seo
记录今天完成的几道 LeetCode 算法题。
930. 和相同的二元子数组
题目
思路
滑动窗口
解题过程
这道题的目标是找到和恰好等于
goal
的子数组个数。我们可以利用滑动窗口,通过一个巧妙的转换来求解:和等于goal
的子数组数量 = (和小于等于goal
的子数组数量) - (和小于等于goal - 1
的子数组数量)。另一种思路是计算 (和大于等于
goal
的子数组数量) - (和大于等于goal + 1
的子数组数量),这正是代码中采用的方法。下面的
slidingWindow(nums, k)
函数用于计算和大于等于k
的子数组数量:
- 使用
right
指针遍历数组,扩展窗口右边界,累加sum
。- 使用
while
循环检查当前窗口和sum
是否大于等于k
(sum >= k
)。- 如果
sum >= k
,说明当前以right
为右边界的窗口和过大或正好,我们需要收缩左边界left
,直到sum < k
。- 在
while
循环收缩完毕后,left
指针的位置意味着从索引0
到left - 1
开始、以right
结尾的子数组,其和都曾经满足(或在收缩前满足)>= k
的条件。因此,有left
个这样的子数组。我们将left
累加到结果ret
中。最终,
slidingWindow(nums, goal) - slidingWindow(nums, goal + 1)
即为所求。
复杂度
- 时间复杂度: O ( n ) O(n) O(n),其中 n 是数组
nums
的长度。每个元素最多被left
和right
指针访问两次。 - 空间复杂度: 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// 所以累加 leftret += left;}return ret;}
}
2302. 统计得分小于 K 的子数组数目
题目
思路
滑动窗口
解题过程
题目要求统计满足
sum * length < k
的子数组数量。
- 我们使用
right
指针遍历数组,扩展窗口右边界,同时维护窗口内的元素和sum
以及窗口长度len
。- 使用
while
循环检查当前窗口[left, right]
是否满足条件sum * len < k
。- 如果
sum * len >= k
,说明当前窗口不满足条件,需要收缩左边界left
,同时更新sum
和len
,直到窗口满足条件为止。- 当
while
循环结束后,当前窗口[left, right]
是满足sum * len < k
的。由于子数组越短,得分通常越小(因为元素非负),所以所有以right
结尾,且起始位置在[left, right]
区间内的子数组,都满足条件。- 这样的子数组有
right - left + 1
个。将这个数量累加到结果ret
中。
复杂度
- 时间复杂度: O ( n ) O(n) O(n),其中 n 是数组
nums
的长度。每个元素最多被left
和right
指针访问两次。 - 空间复杂度: 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” 的子字符串数量。
- 使用
right
指针遍历字符串,扩展窗口右边界。- 使用一个大小为 2 的数组
hash
来记录当前窗口内 ‘0’ 和 ‘1’ 的数量。hash[s[right] - '0']++
更新计数。- 使用
while
循环检查当前窗口[left, right]
是否满足约束条件。窗口不合法当hash[0] > k
或hash[1] > k
时。- 如果窗口不合法,收缩左边界
left
,并在hash
数组中减去s[left]
对应的计数 (hash[s[left++] - '0']--
),直到窗口重新满足约束条件。- 当
while
循环结束后,当前窗口[left, right]
是满足约束条件的(即hash[0] <= k
且hash[1] <= k
)。- 所有以
right
结尾,且起始位置在[left, right]
区间内的子字符串,都满足约束条件。- 这样的子字符串有
right - left + 1
个。将这个数量累加到结果ret
中。
复杂度
- 时间复杂度: O ( n ) O(n) O(n),其中 n 是字符串
s
的长度。每个字符最多被left
和right
指针访问两次。 - 空间复杂度: 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
。- 头部插入时,需要向前移动
head
,head = (head - 1 + length) % length
(加length
是为了处理head
为 0 时减 1 变成负数的情况)。- 尾部删除时,需要向前移动
tail
,tail = (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();*/