普通视图

发现新文章,点击刷新页面。
今天 — 2025年10月18日LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:贪心 + 排序(清晰题解)

作者 lcbin
2025年10月18日 07:24

方法一:贪心 + 排序

我们不妨对数组 $\textit{nums}$ 排序,然后从左到右考虑每个元素 $x$。

对于第一个元素,我们可以贪心地将其变为 $x - k$,这样可以使得 $x$ 尽可能小,给后续的元素留下更多的空间。我们用变量 $\textit{pre}$ 当前使用到的元素的最大值,初始化为负无穷大。

对于后续的元素 $x$,我们可以贪心地将其变为 $\min(x + k, \max(x - k, \textit{pre} + 1))$。这里的 $\max(x - k, \textit{pre} + 1)$ 表示我们尽可能地将 $x$ 变得更小,但不能小于 $\textit{pre} + 1$,如果存在该值,且小于 $x + k$,我们就可以将 $x$ 变为该值,不重复元素数加一,然后我们更新 $\textit{pre}$ 为该值。

遍历结束,我们就得到了不重复元素的最大数量。

###python

class Solution:
    def maxDistinctElements(self, nums: List[int], k: int) -> int:
        nums.sort()
        ans = 0
        pre = -inf
        for x in nums:
            cur = min(x + k, max(x - k, pre + 1))
            if cur > pre:
                ans += 1
                pre = cur
        return ans

###java

class Solution {
    public int maxDistinctElements(int[] nums, int k) {
        Arrays.sort(nums);
        int n = nums.length;
        int ans = 0, pre = Integer.MIN_VALUE;
        for (int x : nums) {
            int cur = Math.min(x + k, Math.max(x - k, pre + 1));
            if (cur > pre) {
                ++ans;
                pre = cur;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int maxDistinctElements(vector<int>& nums, int k) {
        ranges::sort(nums);
        int ans = 0, pre = INT_MIN;
        for (int x : nums) {
            int cur = min(x + k, max(x - k, pre + 1));
            if (cur > pre) {
                ++ans;
                pre = cur;
            }
        }
        return ans;
    }
};

###go

func maxDistinctElements(nums []int, k int) (ans int) {
sort.Ints(nums)
pre := math.MinInt32
for _, x := range nums {
cur := min(x+k, max(x-k, pre+1))
if cur > pre {
ans++
pre = cur
}
}
return
}

###ts

function maxDistinctElements(nums: number[], k: number): number {
    nums.sort((a, b) => a - b);
    let [ans, pre] = [0, -Infinity];
    for (const x of nums) {
        const cur = Math.min(x + k, Math.max(x - k, pre + 1));
        if (cur > pre) {
            ++ans;
            pre = cur;
        }
    }
    return ans;
}

时间复杂度 $O(n \times \log n)$,空间复杂度 $O(\log n)$。其中 $n$ 为数组 $\textit{nums}$ 的长度。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈 😄~

每日一题-执行操作后不同元素的最大数量🟡

2025年10月18日 00:00

给你一个整数数组 nums 和一个整数 k

你可以对数组中的每个元素 最多 执行 一次 以下操作:

  • 将一个在范围 [-k, k] 内的整数加到该元素上。

返回执行这些操作后,nums 中可能拥有的不同元素的 最大 数量。

 

示例 1:

输入: nums = [1,2,2,3,3,4], k = 2

输出: 6

解释:

对前四个元素执行操作,nums 变为 [-1, 0, 1, 2, 3, 4],可以获得 6 个不同的元素。

示例 2:

输入: nums = [4,4,4,4], k = 1

输出: 3

解释:

nums[0] 加 -1,以及对 nums[1] 加 1,nums 变为 [3, 5, 4, 4],可以获得 3 个不同的元素。

 

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 0 <= k <= 109

3397. 执行操作后不同元素的最大数量

作者 stormsunshine
2024年12月22日 13:15

解法

思路和算法

为了计算不同元素的最大数量,应将数组 $\textit{nums}$ 按升序排序,然后从小到大依次考虑每个元素执行操作之后的值。

排序之后,数组 $\textit{nums}$ 中的最小元素为 $\textit{nums}[0]$,将最小元素 $\textit{nums}[0]$ 更新后的值记为 $x_0$,则根据贪心策略,$x_0$ 应取可能的最小值,即 $x_0 = \textit{nums}[0] - k$。理由如下:将所有元素执行操作之后的最小值记为 $x_0$,如果 $x_0 > \textit{nums}[0] - k$,则将 $x_0$ 的值更新为 $\textit{nums}[0] - k$ 之后,所有元素执行操作之后的最小值更小,一定不会产生新的重复元素,不同元素的数量一定不变或增加。

确定 $x_0$ 之后,将次小元素 $\textit{nums}[1]$ 更新后的值记为 $x_1$,则 $\textit{nums}[1] - k \le x_1 \le \textit{nums}[1] + k$。由于 $x_0 = \textit{nums}[0] - k$ 且 $\textit{nums}[0] \le \textit{nums}[1]$,因此 $x_1 \ge x_0$,为了使不同元素的数量最大,$x_1$ 应取范围 $[\textit{nums}[1] - k, \textit{nums}[1] + k]$ 中的大于等于 $x_0 + 1$ 的最小值。理由如下。

  1. 如果 $x_1 = x_0$,则不同元素的数量不变,只有当 $x_1 > x_0$ 时才能使不同元素的数量增加。

  2. 如果 $x_1$ 的值大于可能的最小值,则将 $x_1$ 的值更新为可能的最小值之后,其余元素的取值范围更大,因此不同元素的最大数量不变或增加,不可能减少。

因此 $x_1 = \min(\max(\textit{nums}[1] - k, x_0 + 1), \textit{nums}[1] + k)$。

确定 $x_0$ 和 $x_1$ 之后,数组 $\textit{nums}$ 中的其余元素执行操作之后的值可以使用相同的方法确定。计算得到数组 $\textit{nums}$ 中的所有元素执行操作之后的值,即可得到不同元素的最大数量。

代码

###Java

class Solution {
    public int maxDistinctElements(int[] nums, int k) {
        int distinct = 0;
        Arrays.sort(nums);
        int prev = Integer.MIN_VALUE;
        for (int num : nums) {
            int curr = Math.min(Math.max(num - k, prev + 1), num + k);
            if (curr > prev) {
                distinct++;
                prev = curr;
            }
        }
        return distinct;
    }
}

###C#

public class Solution {
    public int MaxDistinctElements(int[] nums, int k) {
        int distinct = 0;
        Array.Sort(nums);
        int prev = int.MinValue;
        foreach (int num in nums) {
            int curr = Math.Min(Math.Max(num - k, prev + 1), num + k);
            if (curr > prev) {
                distinct++;
                prev = curr;
            }
        }
        return distinct;
    }
}

复杂度分析

  • 时间复杂度:$O(n \log n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。排序的时间是 $O(n \log n)$,排序之后需要遍历数组一次。

  • 空间复杂度:$O(\log n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。排序的递归调用栈空间是 $O(\log n)$。

从小到大贪心(Python/Java/C++/Go)

作者 endlesscheng
2024年12月22日 12:05

一个现实中的例子:

  • 军训的某一天,同学们在操场上。现在教官吹响了口哨,同学们集合,排成一排。对于最靠左的同学 $A$,他需要尽量往左移动,给其他同学腾出位置。$A$ 旁边的同学,可以紧挨着 $A$。依此类推。

把 $\textit{nums}$ 视作 $n$ 个同学在一维数轴中的位置,从最左边的同学($\textit{nums}$ 的最小值)开始思考。

设最左边的同学的位置为 $a$,他尽量往左移,位置变成 $a-k$。

$\textit{nums}$ 的次小值 $b$ 呢?这位同学也尽量往左移:

  • 比如 $a=4,b=6,k=3$,那么 $a$ 变成 $a-k=1$,$b$ 变成 $b-k=3$。
  • 比如 $a=4,b=4,k=3$,那么 $a$ 变成 $a'=a-k=1$,$b$ 变成 $b-k=1$ 就和 $a'$ 一样了,可以稍微大一点(紧挨着 $a'$),把 $b$ 变成 $a'+1=2$。

一般地,$b$ 变成

$$
\max(b-k,a'+1)
$$

但这不能超过 $b+k$,所以最终要变成

$$
\min(\max(b-k,a'+1),b+k)
$$

相当于让 $a'+1$ 落在 $[b-k,b+k]$ 中,若超出范围则修正。

第三小的数也同理,通过前一个数可以算出当前元素能变成多少。

最后答案为 $\textit{nums}$ 中的不同元素个数。我们可以在修改的同时统计,如果发现当前元素修改后的值,比上一个元素修改后的值大,那么答案加一。

为方便计算,把 $\textit{nums}$ 从小到大排序。排序后,从左到右遍历数组,就相当于从最左边的人开始计算了。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def maxDistinctElements(self, nums: List[int], k: int) -> int:
        nums.sort()
        ans = 0
        pre = -inf  # 记录每个人左边的人的位置
        for x in nums:
            x = min(max(x - k, pre + 1), x + k)
            if x > pre:
                ans += 1
                pre = x
        return ans

###java

class Solution {
    public int maxDistinctElements(int[] nums, int k) {
        Arrays.sort(nums);
        int ans = 0;
        int pre = Integer.MIN_VALUE; // 记录每个人左边的人的位置
        for (int x : nums) {
            x = Math.min(Math.max(x - k, pre + 1), x + k);
            if (x > pre) {
                ans++;
                pre = x;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int maxDistinctElements(vector<int>& nums, int k) {
        ranges::sort(nums);
        int ans = 0;
        int pre = INT_MIN; // 记录每个人左边的人的位置
        for (int x : nums) {
            x = clamp(pre + 1, x - k, x + k); // min(max(x - k, pre + 1), x + k)
            if (x > pre) {
                ans++;
                pre = x;
            }
        }
        return ans;
    }
};

###go

func maxDistinctElements(nums []int, k int) (ans int) {
slices.Sort(nums)
pre := math.MinInt // 记录每个人左边的人的位置
for _, x := range nums {
x = min(max(x-k, pre+1), x+k)
if x > pre {
ans++
pre = x
}
}
return
}

优化

什么情况下,可以直接返回 $n$?

先考虑 $\textit{nums}$ 所有元素都相同的情况(同学们都挤在一起)。我们可以把元素 $x$ 变成 $[x-k,x+k]$ 中的整数,这一共有 $2k+1$ 个。如果 $2k+1 \ge n$,就可以让所有元素互不相同。

如果 $\textit{nums}$ 有不同元素,当 $2k+1 \ge n$ 时,更加可以让所有元素互不相同。

所以只要 $2k+1 \ge n$,就可以直接返回 $n$。

###py

class Solution:
    def maxDistinctElements(self, nums: List[int], k: int) -> int:
        if k * 2 + 1 >= len(nums):
            return len(nums)

        nums.sort()
        ans = 0
        pre = -inf  # 记录每个人左边的人的位置
        for x in nums:
            x = min(max(x - k, pre + 1), x + k)
            if x > pre:
                ans += 1
                pre = x
        return ans

###java

class Solution {
    public int maxDistinctElements(int[] nums, int k) {
        int n = nums.length;
        if (k * 2 + 1 >= n) {
            return n;
        }

        Arrays.sort(nums);
        int ans = 0;
        int pre = Integer.MIN_VALUE; // 记录每个人左边的人的位置
        for (int x : nums) {
            x = Math.min(Math.max(x - k, pre + 1), x + k);
            if (x > pre) {
                ans++;
                pre = x;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int maxDistinctElements(vector<int>& nums, int k) {
        int n = nums.size();
        if (k * 2 + 1 >= n) {
            return n;
        }

        ranges::sort(nums);
        int ans = 0;
        int pre = INT_MIN; // 记录每个人左边的人的位置
        for (int x : nums) {
            x = clamp(pre + 1, x - k, x + k); // min(max(x - k, pre + 1), x + k)
            if (x > pre) {
                ans++;
                pre = x;
            }
        }
        return ans;
    }
};

###go

func maxDistinctElements(nums []int, k int) (ans int) {
n := len(nums)
if k*2+1 >= n {
return n
}

slices.Sort(nums)
pre := math.MinInt // 记录每个人左边的人的位置
for _, x := range nums {
x = min(max(x-k, pre+1), x+k)
if x > pre {
ans++
pre = x
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log n)$,其中 $n$ 是 $\textit{nums}$ 的长度。瓶颈在排序上。
  • 空间复杂度:$\mathcal{O}(1)$。忽略排序的栈开销。

专题训练

见下面贪心题单的「§1.1 从最小/最大开始贪心」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

昨天以前LeetCode 每日一题题解

每日一题-执行操作后的最大 MEX🟡

2025年10月16日 00:00

给你一个下标从 0 开始的整数数组 nums 和一个整数 value

在一步操作中,你可以对 nums 中的任一元素加上或减去 value

  • 例如,如果 nums = [1,2,3]value = 2 ,你可以选择 nums[0] 减去 value ,得到 nums = [-1,2,3]

数组的 MEX (minimum excluded) 是指其中数组中缺失的最小非负整数。

  • 例如,[-1,2,3] 的 MEX 是 0 ,而 [1,0,3] 的 MEX 是 2

返回在执行上述操作 任意次 后,nums 的最大 MEX

 

示例 1:

输入:nums = [1,-10,7,13,6,8], value = 5
输出:4
解释:执行下述操作可以得到这一结果:
- nums[1] 加上 value 两次,nums = [1,0,7,13,6,8]
- nums[2] 减去 value 一次,nums = [1,0,2,13,6,8]
- nums[3] 减去 value 两次,nums = [1,0,2,3,6,8]
nums 的 MEX 是 4 。可以证明 4 是可以取到的最大 MEX 。

示例 2:

输入:nums = [1,-10,7,13,6,8], value = 7
输出:2
解释:执行下述操作可以得到这一结果:
- nums[2] 减去 value 一次,nums = [1,-10,0,13,6,8]
nums 的 MEX 是 2 。可以证明 2 是可以取到的最大 MEX 。

 

提示:

  • 1 <= nums.length, value <= 105
  • -109 <= nums[i] <= 109

3ms,晚上突然想到的解法

作者 zcandyyj
2023年3月19日 21:57

Problem: 6321. 执行操作后的最大 MEX

[TOC]

思路

思路看代码,我就举个例子

比如数组为[1,-10,7,13,6,8],value=5,变为正数并取余之后为[1,0,2,3,1,3],对应的flag数组为[1,2,1,2,0],找到的出现次数最少的数的频率,用min记录为0,此时的num为数组的索引4,所以最终返回为4

比如数组为[1,-10,7,13,6,8],value=7,变为正数并取余之后为[1,4,0,6,6,1],对应的flag数组为[1,2,0,0,1,0,2],找到的出现次数最少的数的频率,用min记录为0,此时的num为数组的索引2,所以最终返回为2

Code

###Java


class Solution {
    public int findSmallestInteger(int[] nums, int value) {
        //flag只用来保存取余之后的值的个数,也就是[0,value-1]
        int[] flag=new int[value];
        //暂存nums[i]用
        int temp;
        //遍历nums数组,将每个数数都和value取余,然后存放在flag中
        for(int i=0;i<nums.length;i++){
           temp=nums[i];
           //当nums[i]<0的时候,有两种情况
           //一种是value的倍数,取余直接为0,不能和不为0的合并,因为0+value直接溢出flag数组了
           //第二种取余不为0,直接加上value就行
           //当nums[i]>0的时候,直接取余就好
           //当nums[i]==0的时候,直接记录在flag中
           if(temp<0){
               if(temp%value==0){
                   temp=0;
               }else{
                   temp=temp%value+value;
               }
           }else if(temp>0){
               temp=temp%value;
           }
           flag[temp]++;
        }
        int num=0;
        int min=Integer.MAX_VALUE;
        //找到[0,value-1]中出现次数最少的数的频率,用min记录
        //并使用num记录出现次数最少的数
        for(int i=0;i<flag.length;i++){
           if(flag[i]<min){
               min=flag[i];
               num=i;
           }
        }
        //如果min为0,说明num就是我们要找的答案,否则num+value*min就是我们要找的答案
        if(min==0){
            return num;
        }else{
            return num+value*min;
        }

    }

}

贪心

作者 tsreaper
2023年3月19日 12:08

解法:贪心

思维第一步

看到“任一元素加上或减去 value”,而且“可以操作任意次”,马上反馈出等价条件:

数 $x$ 可以变成任意满足 y mod value == x mod value 的数 $y$。

因此,我们可以把整个序列按元素取模的值分成 value 类,每一类元素都不会因为操作而变成其他类别里的元素。

思维第二步

既然我们要让“缺失的最小非负整数”最大,设第 $i$ 类里有 $k$ 个数,那么它们需要变成 $i, v + i, 2v + i, \cdots (k - 1)v + i$ 才能让 mex 尽可能大。

举个例子帮助大家理解,假设 $v = 5$,第 $2$ 类里有 $3$ 个数,那么它们需要变成 $2, 7, 12$ 才能让 mex 尽可能大。如果变成别的,比如 $2, 12, 17$,那因为缺失了 $7$(而且其他类别的数也没法变成 $7$),那 mex 至多就是 $7$ 了。如果是 $2, 7, 12$,那 mex 还有可能是 $17$,肯定这样做更优。

最终处理

既然我们已经确定了每个数都要变成什么,那完成变化以后,直接计算变化后序列的 mex 就是答案。

复杂度 $\mathcal{O}(n)$。

参考代码(c++)

###c++

class Solution {
public:
    int findSmallestInteger(vector<int>& nums, int v) {
        int n = nums.size();
        // vis[i] 表示变化后的序列里是否出现过元素 i
        // 答案肯定不超过 n
        // 因为最好情况就是 nums = [0, 1, ..., n - 1],这样 mex = n
        bool vis[n + 1];
        memset(vis, 0, sizeof(vis));
        
        // cnt[i] 表示第 i 类元素目前有几个
        int cnt[v];
        memset(cnt, 0, sizeof(cnt));
        for (int x : nums) {
            // t 是元素 x 所属的类别
            int t = (x % v + v) % v;
            // y 是元素 x 应该变成什么数
            int y = cnt[t] * v + t;
            if (y < n) vis[y] = true;
            cnt[t]++;
        }
        
        // 计算变化后序列的 mex
        for (int i = 0; i <= n; i++) if (!vis[i]) return i;
        return -1;
    }
};

同余分组(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2023年3月19日 12:06

下文记 $m=\textit{value}$。

由于同一个数可以加减任意倍的 $m$,我们可以先把每个 $\textit{nums}[i]$ 变成与 $\textit{nums}[i]$ 关于模 $m$ 同余的最小非负整数,以备后用。关于同余的介绍,请看 模运算的世界:当加减乘除遇上取模

本题有负数,根据这篇文章中的公式,我们可以把每个 $\textit{nums}[i]$ 变成

$$
(\textit{nums}[i]\bmod m + m)\bmod m
$$

从而保证取模结果在 $[0,m)$ 中。

例如 $\textit{nums}=[1,-6,-4,3,5]$,$m=3$,取模后变成 $[1,0,2,0,2]$。

然后枚举答案:

  • 有没有与 $0$ 关于模 $m$ 同余的数?有,我们消耗掉一个 $0$。
  • 有没有与 $1$ 关于模 $m$ 同余的数?有,我们消耗掉一个 $1$。
  • 有没有与 $2$ 关于模 $m$ 同余的数?有,我们消耗掉一个 $2$。
  • 有没有与 $3$ 关于模 $m$ 同余的数?有,我们消耗掉一个 $0$。这个取模后等于 $0$ 的数,可以继续操作,变成 $3$。
  • 有没有与 $4$ 关于模 $m$ 同余的数?也就是看是否还有 $1$,没有,那么答案等于 $4$。

怎么知道还有没有剩余元素?用一个哈希表 $\textit{cnt}$ 统计 $(\textit{nums}[i]\bmod m + m) \bmod m$ 的个数。

本题视频讲解,欢迎点赞关注~

写法一

class Solution:
    def findSmallestInteger(self, nums: List[int], m: int) -> int:
        cnt = Counter(x % m for x in nums)
        mex = 0
        while cnt[mex % m]:
            cnt[mex % m] -= 1
            mex += 1
        return mex
class Solution {
    public int findSmallestInteger(int[] nums, int m) {
        int[] cnt = new int[m];
        for (int x : nums) {
            cnt[(x % m + m) % m]++; // 保证取模结果在 [0, m) 中
        }

        int mex = 0;
        while (cnt[mex % m]-- > 0) {
            mex++;
        }
        return mex;
    }
}
class Solution {
    public int findSmallestInteger(int[] nums, int m) {
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int x : nums) {
            cnt.merge((x % m + m) % m, 1, Integer::sum);
        }

        int mex = 0;
        while (cnt.merge(mex % m, -1, Integer::sum) >= 0) {
            mex++;
        }
        return mex;
    }
}
class Solution {
public:
    int findSmallestInteger(vector<int>& nums, int m) {
        unordered_map<int, int> cnt;
        for (int x : nums) {
            cnt[(x % m + m) % m]++; // 保证取模结果在 [0, m) 中
        }

        int mex = 0;
        while (cnt[mex % m]-- > 0) {
            mex++;
        }
        return mex;
    }
};
int findSmallestInteger(int* nums, int numsSize, int m) {
    int* cnt = calloc(m, sizeof(int));
    for (int i = 0; i < numsSize; i++) {
        cnt[(nums[i] % m + m) % m]++; // 保证取模结果在 [0, m) 中
    }

    int mex = 0;
    while (cnt[mex % m]-- > 0) {
        mex++;
    }

    free(cnt);
    return mex;
}
func findSmallestInteger(nums []int, m int) (mex int) {
cnt := map[int]int{}
for _, x := range nums {
cnt[(x%m+m)%m]++ // 保证取模结果在 [0, m) 中
}

for cnt[mex%m] > 0 {
cnt[mex%m]--
mex++
}
return
}
var findSmallestInteger = function(nums, m) {
    const cnt = new Map();
    for (const x of nums) {
        const v = (x % m + m) % m; // 保证取模结果在 [0, m) 中
        cnt.set(v, (cnt.get(v) ?? 0) + 1);
    }

    let mex = 0;
    while ((cnt.get(mex % m) ?? 0) > 0) {
        cnt.set(mex % m, cnt.get(mex % m) - 1);
        mex++;
    }
    return mex;
};
use std::collections::HashMap;

impl Solution {
    pub fn find_smallest_integer(nums: Vec<i32>, m: i32) -> i32 {
        let mut cnt = HashMap::new();
        for x in nums {
            // 保证取模结果在 [0, m) 中
            *cnt.entry((x % m + m) % m).or_insert(0) += 1;
        }

        for mex in 0.. {
            if let Some(c) = cnt.get_mut(&(mex % m)) {
                if *c > 0 {
                    *c -= 1;
                    continue;
                }
            }
            return mex;
        }
        unreachable!()
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。由于加多少个数,就只能减多少个数,所以第二个循环至多循环 $\mathcal{O}(n)$ 次。
  • 空间复杂度:$\mathcal{O}(\min(n,m))$。哈希表中至多有 $\mathcal{O}(\min(n,m))$ 个元素。

写法二

lc2598-c.png{:width=500px}

此外,把哈希表换成数组更快。

class Solution:
    def findSmallestInteger(self, nums: List[int], m: int) -> int:
        cnt = [0] * m
        for x in nums:
            cnt[x % m] += 1

        i = cnt.index(min(cnt))
        return m * cnt[i] + i
class Solution {
    public int findSmallestInteger(int[] nums, int m) {
        int[] cnt = new int[m];
        for (int x : nums) {
            cnt[(x % m + m) % m]++;
        }

        int i = 0;
        for (int j = 1; j < m; j++) {
            if (cnt[j] < cnt[i]) {
                i = j;
            }
        }

        return m * cnt[i] + i;
    }
}
class Solution {
public:
    int findSmallestInteger(vector<int>& nums, int m) {
        vector<int> cnt(m);
        for (int x : nums) {
            cnt[(x % m + m) % m]++;
        }

        int i = ranges::min_element(cnt) - cnt.begin();
        return m * cnt[i] + i;
    }
};
int findSmallestInteger(int* nums, int numsSize, int m) {
    int* cnt = calloc(m, sizeof(int));
    for (int i = 0; i < numsSize; i++) {
        cnt[(nums[i] % m + m) % m]++;
    }

    int i = 0;
    for (int j = 1; j < m; j++) {
        if (cnt[j] < cnt[i]) {
            i = j;
        }
    }

    int ans = m * cnt[i] + i;
    free(cnt);
    return ans;
}
func findSmallestInteger(nums []int, m int) int {
cnt := make([]int, m)
for _, x := range nums {
cnt[(x%m+m)%m]++
}

i := 0
for j := 1; j < m; j++ {
if cnt[j] < cnt[i] {
i = j
}
}

return m*cnt[i] + i
}
var findSmallestInteger = function(nums, m) {
    const cnt = Array(m).fill(0);
    for (const x of nums) {
        cnt[(x % m + m) % m]++;
    }

    let i = 0;
    for (let j = 1; j < m; j++) {
        if (cnt[j] < cnt[i]) {
            i = j;
        }
    }

    return m * cnt[i] + i;
};
impl Solution {
    pub fn find_smallest_integer(nums: Vec<i32>, m: i32) -> i32 {
        let mut cnt = vec![0; m as usize];
        for x in nums {
            cnt[((x % m + m) % m) as usize] += 1;
        }

        let mut i = 0;
        for j in 1..m as usize {
            if cnt[j] < cnt[i] {
                i = j;
            }
        }

        m * cnt[i] + i as i32
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n+m)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(m)$。

相似题目

见下面数学题单的「§1.9 同余」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

检测相邻递增子数组 II

2025年10月10日 10:43

方法一:一次遍历

思路与算法

我们对数组 $\textit{nums}$ 进行一次遍历,在遍历的过程中,用 $\textit{cnt}$ 和 $\textit{precnt}$ 分别记录到当前位置严格递增的子数组的长度,以及上一个严格递增子数组的长度。

初始时,$\textit{cnt}$ 的值为 $1$,$\textit{precnt}$ 的值为 $0$。当遍历到 $\textit{nums}[i]$ 时,如果大于 $\textit{nums}[i-1]$,则 $\textit{cnt}$ 自增 $1$;否则子数组不再递增,将 $\textit{cnt}$ 的值赋予 $\textit{precnt}$,并将 $\textit{cnt}$ 置为 $1$。

满足题目要求的两个相邻子数组有两种情况:

  1. 前一个子数组属于 $\textit{precnt}$,后一个子数组属于 $\textit{cnt}$,$k$ 值为 $\min(\textit{precnt}, \textit{cnt})$。

  2. 两个子数组都属于 $\textit{cnt}$,$k$ 值为 $\lfloor \dfrac{\textit{cnt}}{2} \rfloor$,其中 $\lfloor \cdot \rfloor$ 表示向下取整。

根据这两种情况,不断更新 $k$ 的最大值即可。

代码

###C++

class Solution {
public:
    int maxIncreasingSubarrays(vector<int>& nums) {
        int n = nums.size();
        int cnt = 1, precnt = 0, ans = 0;
        for (int i = 1; i < n; ++i) {
            if (nums[i] > nums[i - 1]) {
                ++cnt;
            }
            else {
                precnt = cnt;
                cnt = 1;
            }
            ans = max(ans, min(precnt, cnt));
            ans = max(ans, cnt / 2);
        }
        return ans;
    }
};

###Python

class Solution:
    def maxIncreasingSubarrays(self, nums: List[int]) -> int:
        n = len(nums)
        cnt, precnt, ans = 1, 0, 0
        for i in range(1, n):
            if nums[i] > nums[i - 1]:
                cnt += 1
            else:
                precnt, cnt = cnt, 1
            ans = max(ans, min(precnt, cnt))
            ans = max(ans, cnt // 2)
        return ans

###C#

public class Solution {
    public int MaxIncreasingSubarrays(IList<int> nums) {
        int n = nums.Count;
        int cnt = 1, precnt = 0, ans = 0;
        for (int i = 1; i < n; ++i) {
            if (nums[i] > nums[i - 1]) {
                ++cnt;
            } else {
                precnt = cnt;
                cnt = 1;
            }
            ans = Math.Max(ans, Math.Min(precnt, cnt));
            ans = Math.Max(ans, cnt / 2);
        }
        return ans;
    }
}

###Go

func maxIncreasingSubarrays(nums []int) int {
    n := len(nums)
    cnt, precnt, ans := 1, 0, 0
    for i := 1; i < n; i++ {
        if nums[i] > nums[i-1] {
            cnt++
        } else {
            precnt = cnt
            cnt = 1
        }
        ans = max(ans, min(precnt, cnt))
        ans = max(ans, cnt/2)
    }
    return ans
}

###Python

class Solution:
    def maxIncreasingSubarrays(self, nums: List[int]) -> int:
        n = len(nums)
        cnt, precnt, ans = 1, 0, 0
        for i in range(1, n):
            if nums[i] > nums[i - 1]:
                cnt += 1
            else:
                precnt = cnt
                cnt = 1
            ans = max(ans, min(precnt, cnt))
            ans = max(ans, cnt // 2)
        return ans

###C

int maxIncreasingSubarrays(int* nums, int numsSize) {
    int cnt = 1, precnt = 0, ans = 0;
    for (int i = 1; i < numsSize; ++i) {
        if (nums[i] > nums[i - 1]) {
            ++cnt;
        } else {
            precnt = cnt;
            cnt = 1;
        }
        int min_val = precnt < cnt ? precnt : cnt;
        ans = ans > min_val ? ans : min_val;
        ans = ans > cnt / 2 ? ans : cnt / 2;
    }
    return ans;
}

###JavaScript

var maxIncreasingSubarrays = function(nums) {
    const n = nums.length;
    let cnt = 1, precnt = 0, ans = 0;
    for (let i = 1; i < n; ++i) {
        if (nums[i] > nums[i - 1]) {
            ++cnt;
        } else {
            precnt = cnt;
            cnt = 1;
        }
        ans = Math.max(ans, Math.min(precnt, cnt));
        ans = Math.max(ans, Math.floor(cnt / 2));
    }
    return ans;
};

###TypeScript

function maxIncreasingSubarrays(nums: number[]): number {
    const n = nums.length;
    let cnt = 1, precnt = 0, ans = 0;
    for (let i = 1; i < n; ++i) {
        if (nums[i] > nums[i - 1]) {
            ++cnt;
        } else {
            precnt = cnt;
            cnt = 1;
        }
        ans = Math.max(ans, Math.min(precnt, cnt));
        ans = Math.max(ans, Math.floor(cnt / 2));
    }
    return ans;
}

###Rust

impl Solution {
    pub fn max_increasing_subarrays(nums: Vec<i32>) -> i32 {
        let n = nums.len();
        let mut cnt = 1;
        let mut precnt = 0;
        let mut ans = 0;
        
        for i in 1..n {
            if nums[i] > nums[i - 1] {
                cnt += 1;
            } else {
                precnt = cnt;
                cnt = 1;
            }
            ans = ans.max(precnt.min(cnt));
            ans = ans.max(cnt / 2);
        }
        
        ans
    }
}

复杂度分析

  • 时间复杂度:$O(n)$。

  • 空间复杂度:$O(1)$。

每日一题-检测相邻递增子数组 II🟡

2025年10月15日 00:00

给你一个由 n 个整数组成的数组 nums ,请你找出 k最大值,使得存在 两个 相邻 且长度为 k严格递增 子数组。具体来说,需要检查是否存在从下标 ab (a < b) 开始的 两个 子数组,并满足下述全部条件:

  • 这两个子数组 nums[a..a + k - 1]nums[b..b + k - 1] 都是 严格递增 的。
  • 这两个子数组必须是 相邻的,即 b = a + k

返回 k最大可能 值。

子数组 是数组中的一个连续 非空 的元素序列。

 

示例 1:

输入:nums = [2,5,7,8,9,2,3,4,3,1]

输出:3

解释:

  • 从下标 2 开始的子数组是 [7, 8, 9],它是严格递增的。
  • 从下标 5 开始的子数组是 [2, 3, 4],它也是严格递增的。
  • 这两个子数组是相邻的,因此 3 是满足题目条件的 最大 k 值。

示例 2:

输入:nums = [1,2,3,4,4,4,4,5,6,7]

输出:2

解释:

  • 从下标 0 开始的子数组是 [1, 2],它是严格递增的。
  • 从下标 2 开始的子数组是 [3, 4],它也是严格递增的。
  • 这两个子数组是相邻的,因此 2 是满足题目条件的 最大 k 值。

 

提示:

  • 2 <= nums.length <= 2 * 105
  • -109 <= nums[i] <= 109

枚举

作者 tsreaper
2024年11月10日 12:11

解法:枚举

这种一前一后两个子数组的题,一般考虑枚举分界点(比如第二个子数组的开头)。

维护 $f(i)$ 表示以第 $i$ 个元素为结尾,最长的单调递增子数组是多长;$g(i)$ 表示以第 $i$ 个元素为开头,最长的单调递增子数组是多长。那么我们枚举第二个子数组的开头 $b$,说明第一个子数组的结尾就是 $(b - 1)$,答案就是 $\max(\min(f(b - 1), g(b)))$。

复杂度 $\mathcal{O}(n)$。

参考代码(c++)

###cpp

class Solution {
public:
    int maxIncreasingSubarrays(vector<int>& nums) {
        int n = nums.size();
        int f[n + 2], g[n + 2];
        f[0] = 0; g[n + 1] = 0;
        // 计算以第 i 个元素为结尾,最长的单调递增子数组是多长
        for (int i = 1; i <= n; i++) {
            if (i > 1 && nums[i - 2] < nums[i - 1]) f[i] = f[i - 1] + 1;
            else f[i] = 1;
        }
        // 计算以第 i 个元素为开头,最长的单调递增子数组是多长
        for (int i = n; i > 0; i--) {
            if (i < n && nums[i - 1] < nums[i]) g[i] = g[i + 1] + 1;
            else g[i] = 1;
        }
        int ans = 0;
        // 枚举第二个子数组的开头
        for (int i = 1; i <= n; i++) ans = max(ans, min(f[i - 1], g[i]));
        return ans;
    }
};

典中典之前后缀分解

作者 mipha-2022
2024年11月10日 12:04

Problem: 3350. 检测相邻递增子数组 II

思路

这类拆分成 两段不相交子数组或者子序列题,套路都是一样的:

  • 前后缀分解
  • 枚举分割点

比如这题:

  • left[i]i 作为末尾的最长严格递增子数组长度
  • right[i]i 作为起始的最长严格递增子数组长度
  • 然后枚举分割点i,判断left[i-1]right[i]哪个值更小,就是对应的k[i]值,然后就能找出max(k)
        # left[i] 以 i 末尾最长严格递增子数组长度
        n = len(nums)
        left = [0] * n

        last = nums[0] - 1
        t = 0
        for i,num in enumerate(nums):
            if num > last:
                t += 1
            else:
                t = 1
            left[i] = t
            last = num

优化

通常会把求right和枚举分隔点两步合并,防止被卡常:

        # right[i] 同上,顺便枚举
        last = nums[-1] + 1
        t = 0
        res = 0
        for i in range(n-1,0,-1):
            if nums[i] < last:
                t += 1
            else:
                t = 1
            last = nums[i]
            
            res = max(res,min(left[i-1],t))

更多题目模板总结,请参考2023年度总结与题目分享

Code

###Python3

class Solution:
    def maxIncreasingSubarrays(self, nums: List[int]) -> int:
        '''
        前后缀分解dp
        '''
        # left[i] 以 i 末尾最长严格递增子数组长度
        n = len(nums)
        left = [0] * n

        last = nums[0] - 1
        t = 0
        for i,num in enumerate(nums):
            if num > last:
                t += 1
            else:
                t = 1
            left[i] = t
            last = num

        # right[i] 同上,顺便枚举
        last = nums[-1] + 1
        t = 0
        res = 0
        for i in range(n-1,0,-1):
            if nums[i] < last:
                t += 1
            else:
                t = 1
            last = nums[i]
            
            res = max(res,min(left[i-1],t))

        return res

O(n) 一次遍历,简洁写法(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2024年11月10日 12:03

遍历 $\textit{nums}$,寻找严格递增段(子数组)。

设当前严格递增段的长度为 $\textit{cnt}$,上一个严格递增段的长度为 $\textit{preCnt}$。

答案有两种情况:

  • 两个子数组属于同一个严格递增段,那么 $k$ 最大是 $\left\lfloor\dfrac{\textit{cnt}}{2}\right\rfloor$。
  • 两个子数组分别属于一对相邻的严格递增段,那么 $k$ 最大是 $\min(\textit{preCnt}, \textit{cnt})$。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def maxIncreasingSubarrays(self, nums: List[int]) -> int:
        ans = pre_cnt = cnt = 0
        for i, x in enumerate(nums):
            cnt += 1
            if i == len(nums) - 1 or x >= nums[i + 1]:  # i 是严格递增段的末尾
                ans = max(ans, cnt // 2, min(pre_cnt, cnt))
                pre_cnt = cnt
                cnt = 0
        return ans

###java

class Solution {
    public int maxIncreasingSubarrays(List<Integer> nums) {
        int ans = 0;
        int preCnt = 0;
        int cnt = 0;
        for (int i = 0; i < nums.size(); i++) {
            cnt++;
            // i 是严格递增段的末尾
            if (i == nums.size() - 1 || nums.get(i) >= nums.get(i + 1)) {
                ans = Math.max(ans, Math.max(cnt / 2, Math.min(preCnt, cnt)));
                preCnt = cnt;
                cnt = 0;
            }
        }
        return ans;
    }
}

###java

class Solution {
    public int maxIncreasingSubarrays(List<Integer> nums) {
        Integer[] a = nums.toArray(Integer[]::new); // 转成数组处理,更快
        int ans = 0;
        int preCnt = 0;
        int cnt = 0;
        for (int i = 0; i < a.length; i++) {
            cnt++;
            // i 是严格递增段的末尾
            if (i == a.length - 1 || a[i] >= a[i + 1]) {
                ans = Math.max(ans, Math.max(cnt / 2, Math.min(preCnt, cnt)));
                preCnt = cnt;
                cnt = 0;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int maxIncreasingSubarrays(vector<int>& nums) {
        int ans = 0, pre_cnt = 0, cnt = 0;
        for (int i = 0; i < nums.size(); i++) {
            cnt++;
            if (i == nums.size() - 1 || nums[i] >= nums[i + 1]) { // i 是严格递增段的末尾
                ans = max({ans, cnt / 2, min(pre_cnt, cnt)});
                pre_cnt = cnt;
                cnt = 0;
            }
        }
        return ans;
    }
};

###c

#define MIN(a, b) ((b) < (a) ? (b) : (a))
#define MAX(a, b) ((b) > (a) ? (b) : (a))

int maxIncreasingSubarrays(int* nums, int numsSize) {
    int ans = 0, pre_cnt = 0, cnt = 0;
    for (int i = 0; i < numsSize; i++) {
        cnt++;
        if (i == numsSize - 1 || nums[i] >= nums[i + 1]) { // i 是严格递增段的末尾
            ans = MAX(ans, MAX(cnt / 2, MIN(pre_cnt, cnt)));
            pre_cnt = cnt;
            cnt = 0;
        }
    }
    return ans;
}

###go

func maxIncreasingSubarrays(nums []int) (ans int) {
preCnt, cnt := 0, 0
for i, x := range nums {
cnt++
if i == len(nums)-1 || x >= nums[i+1] { // i 是严格递增段的末尾
ans = max(ans, cnt/2, min(preCnt, cnt))
preCnt = cnt
cnt = 0
}
}
return
}

###js

var maxIncreasingSubarrays = function(nums) {
    let ans = 0, preCnt = 0, cnt = 0;
    for (let i = 0; i < nums.length; i++) {
        cnt++;
        if (i === nums.length - 1 || nums[i] >= nums[i + 1]) { // i 是严格递增段的末尾
            ans = Math.max(ans, Math.floor(cnt / 2), Math.min(preCnt, cnt));
            preCnt = cnt;
            cnt = 0;
        }
    }
    return ans;
};

###rust

impl Solution {
    pub fn max_increasing_subarrays(nums: Vec<i32>) -> i32 {
        let mut ans = 0;
        let mut pre_cnt = 0;
        let mut cnt = 0;
        for i in 0..nums.len() {
            cnt += 1;
            if i == nums.len() - 1 || nums[i] >= nums[i + 1] { // i 是严格递增段的末尾
                ans = ans.max(cnt / 2).max(pre_cnt.min(cnt));
                pre_cnt = cnt;
                cnt = 0;
            }
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

专题训练

见下面双指针题单的「六、分组循环」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

每日一题-检测相邻递增子数组 I🟢

2025年10月14日 00:00

给你一个由 n 个整数组成的数组 nums 和一个整数 k,请你确定是否存在 两个 相邻 且长度为 k严格递增 子数组。具体来说,需要检查是否存在从下标 ab (a < b) 开始的 两个 子数组,并满足下述全部条件:

  • 这两个子数组 nums[a..a + k - 1]nums[b..b + k - 1] 都是 严格递增 的。
  • 这两个子数组必须是 相邻的,即 b = a + k

如果可以找到这样的 两个 子数组,请返回 true;否则返回 false

子数组 是数组中的一个连续 非空 的元素序列。

 

示例 1:

输入:nums = [2,5,7,8,9,2,3,4,3,1], k = 3

输出:true

解释:

  • 从下标 2 开始的子数组为 [7, 8, 9],它是严格递增的。
  • 从下标 5 开始的子数组为 [2, 3, 4],它也是严格递增的。
  • 两个子数组是相邻的,因此结果为 true

示例 2:

输入:nums = [1,2,3,4,4,4,4,5,6,7], k = 5

输出:false

 

提示:

  • 2 <= nums.length <= 100
  • 1 <= 2 * k <= nums.length
  • -1000 <= nums[i] <= 1000

双滑窗达到O(n)时间复杂度

作者 huan-xin-27
2024年11月11日 13:57

Problem: 3349. 检测相邻递增子数组 I

[TOC]

思路

根据滑动窗口的最小值的解题方法,我们可以用同样的方法来解决本题,前几天的每日一题也是可以用相同的办法这么做长度为 K 的子数组的能量值 II

做题方法:

  • 维护两个相邻的滑动窗口,每一个窗口功能都一样:用于维护长度为k的子数组中的最小值(这里不懂可以做一下滑动窗口最大值,可以说是最经典的滑窗题了)
  • 如果滑动窗口的长度为k,那么就说明该子数组是一直递增的,那么如果有两个相邻的滑动窗口的大小都为k,则说明两段都是递增的,return true;即可

复杂度

  • 时间复杂度: $O(N)$
  • 空间复杂度: $O(N)$

Code

###C++

class Solution {
public:
    bool hasIncreasingSubarrays(vector<int>& nums, int k) {
        deque<int> q1,q2;
        for(int i=0,j=k;j<nums.size();j++,i++){
            if(q1.size()&&q1.front()<i-k+1)
                q1.pop_front();
            if(q2.size()&&q2.front()<j-k+1)
                q2.pop_front();
            while(q1.size()&&nums[q1.back()]>=nums[i])
                q1.pop_back();
            while(q2.size()&&nums[q2.back()]>=nums[j])
                q2.pop_back();
            q1.push_back(i);
            q2.push_back(j);
            if(q1.size()==k&&q2.size()==k)
                return true;
        }
        return false;
    }
};


//可以这样,使用滑动窗口来做这个题,维护两个滑窗,然后判断每次
//滑动的时候判断当前窗口的大小是否等于k,如果两个窗口都等于
//k,那么说明可以构成连续的两个相邻严格递增子数组了

3349. 检测相邻递增子数组 I

作者 stormsunshine
2024年11月10日 18:33

解法一

思路和算法

两个相邻的长度为 $k$ 的子数组等价于一个长度为 $2k$ 的子数组。遍历数组 $\textit{nums}$ 中的每个长度为 $2k$ 的子数组,判断该子数组是否满足前 $k$ 个元素与后 $k$ 个元素分别严格递增。如果找到符合要求的子数组,则返回 $\text{true}$;如果未找到符合要求的子数组,则返回 $\text{false}$。

代码

###Java

class Solution {
    public boolean hasIncreasingSubarrays(List<Integer> nums, int k) {
        int n = nums.size();
        for (int i = 0; i + 2 * k <= n; i++) {
            if (isIncreasingSubarray(nums, i, i + k - 1) && isIncreasingSubarray(nums, i + k, i + 2 * k - 1)) {
                return true;
            }
        }
        return false;
    }

    public boolean isIncreasingSubarray(List<Integer> nums, int start, int end) {
        for (int i = start; i < end; i++) {
            if (nums.get(i) >= nums.get(i + 1)) {
                return false;
            }
        }
        return true;
    }
}

###C#

public class Solution {
    public bool HasIncreasingSubarrays(IList<int> nums, int k) {
        int n = nums.Count;
        for (int i = 0; i + 2 * k <= n; i++) {
            if (IsIncreasingSubarray(nums, i, i + k - 1) && IsIncreasingSubarray(nums, i + k, i + 2 * k - 1)) {
                return true;
            }
        }
        return false;
    }

    public bool IsIncreasingSubarray(IList<int> nums, int start, int end) {
        for (int i = start; i < end; i++) {
            if (nums[i] >= nums[i + 1]) {
                return false;
            }
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度:$O(nk)$,其中 $n$ 是数组 $\textit{nums}$ 的长度,$k$ 是给定的整数。需要遍历的长度为 $2k$ 的子数组的数量是 $O(n)$,每个子数组的遍历时间是 $O(k)$。

  • 空间复杂度:$O(1)$。

解法二

思路和算法

数组 $\textit{nums}$ 的长度是 $n$。将数组 $\textit{nums}$ 拆分成一个或多个最长严格递增子数组,满足每个最长严格递增子数组都非空且任意两个相邻最长严格递增子数组合并之后的新子数组不是严格递增子数组。当数组 $\textit{nums}$ 中存在两个相邻的长度为 $k$ 的严格递增子数组时,有如下两种情况。

  • 两个长度为 $k$ 的严格递增子数组合并之后仍是严格递增子数组,则数组 $\textit{nums}$ 中存在长度至少为 $2k$ 的严格递增子数组。

  • 两个长度为 $k$ 的严格递增子数组合并之后不是严格递增子数组,则数组 $\textit{nums}$ 中存在两个相邻最长严格递增子数组的长度分别至少为 $k$。

为了判断是否存在两个相邻的长度为 $k$ 的严格递增子数组,需要计算每个最长严格递增子数组的长度,分别考虑两种情况。

用 $\textit{maxIncreasing}$ 表示两个相邻严格递增子数组的最大长度,初始时 $\textit{maxIncreasing} = 0$。从左到右遍历数组 $\textit{nums}$,遍历过程中维护上一个最长严格递增子数组的长度 $\textit{prevIncreasing}$ 与当前最长严格递增子数组的长度 $\textit{currIncreasing}$,初始时 $\textit{prevIncreasing} = \textit{currIncreasing} = 0$。对于遍历到的每个下标 $i$,执行如下操作。

  1. 将 $\textit{currIncreasing}$ 的值增加 $1$。

  2. 当 $i = n - 1$ 或 $\textit{nums}[i] \ge \textit{nums}[i + 1]$ 时,下标 $i$ 是一个最长严格递增子数组的结束下标,因此需要更新答案并更新 $\textit{maxIncreasing}$、$\textit{prevIncreasing}$ 与 $\textit{currIncreasing}$。

    1. 如果两个相邻严格递增子数组合并之后仍是严格递增子数组,则两个相邻严格递增子数组的最大长度分别是 $\Big\lfloor \dfrac{\textit{currIncreasing}}{2} \Big\rfloor$,使用 $\Big\lfloor \dfrac{\textit{currIncreasing}}{2} \Big\rfloor$ 更新 $\textit{maxIncreasing}$。

    2. 如果两个相邻严格递增子数组合并之后不是严格递增子数组,则两个相邻严格递增子数组的最大长度分别是 $\min(\textit{prevIncreasing}, \textit{currIncreasing})$,使用 $\min(\textit{prevIncreasing}, \textit{currIncreasing})$ 更新 $\textit{maxIncreasing}$。

    3. 由于一个最长严格递增子数组遍历结束,因此将 $\textit{prevIncreasing}$ 的值更新为 $\textit{currIncreasing}$,将 $\textit{currIncreasing}$ 的值更新为 $0$。

遍历结束之后,如果 $\textit{maxIncreasing} \ge k$ 则返回 $\text{true}$,如果 $\textit{maxIncreasing} < k$ 则返回 $\text{false}$。

代码

###Java

class Solution {
    public boolean hasIncreasingSubarrays(List<Integer> nums, int k) {
        int maxIncreasing = 0;
        int n = nums.size();
        int prevIncreasing = 0, currIncreasing = 0;
        for (int i = 0; i < n && maxIncreasing < k; i++) {
            currIncreasing++;
            if (i == n - 1 || nums.get(i) >= nums.get(i + 1)) {
                maxIncreasing = Math.max(maxIncreasing, currIncreasing / 2);
                maxIncreasing = Math.max(maxIncreasing, Math.min(prevIncreasing, currIncreasing));
                prevIncreasing = currIncreasing;
                currIncreasing = 0;
            }
        }
        return maxIncreasing >= k;
    }
}

###C#

public class Solution {
    public bool HasIncreasingSubarrays(IList<int> nums, int k) {
        int maxIncreasing = 0;
        int n = nums.Count;
        int prevIncreasing = 0, currIncreasing = 0;
        for (int i = 0; i < n && maxIncreasing < k; i++) {
            currIncreasing++;
            if (i == n - 1 || nums[i] >= nums[i + 1]) {
                maxIncreasing = Math.Max(maxIncreasing, currIncreasing / 2);
                maxIncreasing = Math.Max(maxIncreasing, Math.Min(prevIncreasing, currIncreasing));
                prevIncreasing = currIncreasing;
                currIncreasing = 0;
            }
        }
        return maxIncreasing >= k;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要遍历数组一次。

  • 空间复杂度:$O(1)$。

每日一题-从魔法师身上吸取的最大能量🟡

2025年10月10日 00:00

在神秘的地牢中,n 个魔法师站成一排。每个魔法师都拥有一个属性,这个属性可以给你提供能量。有些魔法师可能会给你负能量,即从你身上吸取能量。

你被施加了一种诅咒,当你从魔法师 i 处吸收能量后,你将被立即传送到魔法师 (i + k) 处。这一过程将重复进行,直到你到达一个不存在 (i + k) 的魔法师为止。

换句话说,你将选择一个起点,然后以 k 为间隔跳跃,直到到达魔法师序列的末端,在过程中吸收所有的能量

给定一个数组 energy 和一个整数k,返回你能获得的 最大 能量。

 

示例 1:

输入: energy = [5,2,-10,-5,1], k = 3

输出: 3

解释:可以从魔法师 1 开始,吸收能量 2 + 1 = 3。

示例 2:

输入: energy = [-2,-3,-1], k = 2

输出: -1

解释:可以从魔法师 2 开始,吸收能量 -1。

 

提示:

  • 1 <= energy.length <= 105
  • -1000 <= energy[i] <= 1000
  • 1 <= k <= energy.length - 1

 

❌
❌