普通视图

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

每日一题-执行操作后字典序最小的字符串🟡

2025年10月19日 00:00

给你一个字符串 s 以及两个整数 ab 。其中,字符串 s 的长度为偶数,且仅由数字 09 组成。

你可以在 s 上按任意顺序多次执行下面两个操作之一:

  • 累加:将  a 加到 s 中所有下标为奇数的元素上(下标从 0 开始)。数字一旦超过 9 就会变成 0,如此循环往复。例如,s = "3456"a = 5,则执行此操作后 s 变成 "3951"
  • 轮转:将 s 向右轮转 b 位。例如,s = "3456"b = 1,则执行此操作后 s 变成 "6345"

请你返回在 s 上执行上述操作任意次后可以得到的 字典序最小 的字符串。

如果两个字符串长度相同,那么字符串 a 字典序比字符串 b 小可以这样定义:在 ab 出现不同的第一个位置上,字符串 a 中的字符出现在字母表中的时间早于 b 中的对应字符。例如,"0158” 字典序比 "0190" 小,因为不同的第一个位置是在第三个字符,显然 '5' 出现在 '9' 之前。

 

示例 1:

输入:s = "5525", a = 9, b = 2
输出:"2050"
解释:执行操作如下:
初态:"5525"
轮转:"2555"
累加:"2454"
累加:"2353"
轮转:"5323"
累加:"5222"
累加:"5121"
轮转:"2151"
累加:"2050"
无法获得字典序小于 "2050" 的字符串。

示例 2:

输入:s = "74", a = 5, b = 1
输出:"24"
解释:执行操作如下:
初态:"74"
轮转:"47"
累加:"42"
轮转:"24"
无法获得字典序小于 "24" 的字符串。

示例 3:

输入:s = "0011", a = 4, b = 2
输出:"0011"
解释:无法获得字典序小于 "0011" 的字符串。

 

提示:

  • 2 <= s.length <= 100
  • s.length 是偶数
  • s 仅由数字 09 组成
  • 1 <= a <= 9
  • 1 <= b <= s.length - 1

枚举轮转到最左边的下标 + 裴蜀定理(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2025年10月3日 10:35

:题干中的「数字一旦超过 $9$ 就会变成 $0$」的意思是,数字 $x$ 加上 $a$ 后,会变成 $(x+a)\bmod 10$。

不轮转

从特殊到一般,先考虑只累加、不轮转的情况。

为了让字典序尽量小,第一个奇数下标 $1$ 上的数字 $s_1$,越小越好。一旦我们确定了 $s_1$ 的最终值,就确定了一共累加的值。由于所有奇数下标都要累加同一个数,所以也就确定了其余奇数下标的值。

那么,$s_1$ 最小可以是多少?可以是 $0$ 吗?

比如 $s_1 = 5$,$a=2$。不断累加 $a$,$s_1$ 的变化情况如下:

$$
5\to 7\to 9\to 1\to 3\to 5\to \cdots
$$

这种情况 $s_1$ 只能是奇数,最小是 $1$。

而如果 $s_1 = 5$,$a=3$,$s_1$ 的变化情况如下:

$$
5\to 8\to 1\to 4\to 7\to 0\to 3\to 6\to 9\to 2\to 5 \cdots
$$

这种情况 $s_1$ 可以变成 $[0,9]$ 中的任意整数,最小是 $0$。

一般地,设累加操作执行了 $k\ (k\ge 0)$ 次,那么 $s_1$ 变成

$$
r = (s_1 + ak)\bmod 10
$$

也就是 $s_1 + ak$ 减去若干个 $10$ 等于 $r$,即

$$
s_1 + ak - 10q = r
$$

变形得

$$
ak - 10q = r-s_1
$$

裴蜀定理 指出,该方程有解,当且仅当 $r-s_1$ 是 $g = \gcd(a,10)$ 的倍数,即

$$
r \equiv s_1 \pmod g
$$

其中 $\equiv$ 是同余符号,详细解释见 模运算的世界:当加减乘除遇上取模

上式表明,$s_1$ 通过累加操作变成的数,必须与 $s_1$ 关于模 $g$ 同余,所以 $s_1$ 可以变成的最小值为

$$
s_1\bmod g
$$

从 $s_1$ 到 $s_1\bmod g$,一共要累加的值为

$$
s_1\bmod g - s_1 + 10
$$

其中 $+10$ 保证减法结果非负。

枚举轮转到最左边的下标

例如 $s=\texttt{012345}$,$b=4$,执行轮转操作,得到

$$
\texttt{012345}\to\texttt{234501}\to\texttt{450123}\to\texttt{012345}\to\cdots
$$

只有 $s_0,s_2,s_4$ 可以轮转到最左边。

类似上文的思路,根据裴蜀定理,可以轮转到最左边的下标,必须是 $\textit{step} = \gcd(b,n)$ 的倍数,其中 $n$ 是 $s$ 的长度。

枚举 $i = 0,\textit{step},2\cdot \textit{step}, 3\cdot \textit{step},\dots$ 作为轮转到最左边的下标。

分类讨论:

  • 如果 $\gcd(b,n)$ 是偶数,无论如何轮转,我们只能对奇数下标执行累加操作。
  • 如果 $\gcd(b,n)$ 是奇数,轮转一次后,原来的偶数下标变成奇数下标。可以先轮转一次,执行累加,再轮转到我们想要的位置。可以视作我们拥有了「对偶数下标执行累加操作」的能力。

###py

class Solution:
    def findLexSmallestString(self, s: str, a: int, b: int) -> str:
        s = list(map(int, s))
        n = len(s)
        step = gcd(b, n)
        g = gcd(a, 10)
        ans = [inf]

        def modify(start: int) -> None:
            ch = t[start]  # 最靠前的数字,越小越好
            # ch 可以变成的最小值为 ch%g
            # 例如 ch=5,g=2,那么 ch+2+2+2(模 10)后变成 1,不可能变得更小
            # 从 ch 到 ch%g,需要增加 inc(循环中会 %10 保证结果在 [0,9] 中)
            inc = ch % g - ch
            if inc:  # 优化:inc 为 0 时,t[j] 不变,无需执行 for 循环
                for j in range(start, n, 2):
                    t[j] = (t[j] + inc) % 10

        for i in range(0, n, step):      
            t = s[i:] + s[:i]  # 轮转
            modify(1)  # 累加操作(所有奇数下标)
            if step % 2:  # 能对偶数下标执行累加操作
                modify(0)  # 累加操作(所有偶数下标)
            ans = min(ans, t)

        return ''.join(map(str, ans))

###java

class Solution {
    public String findLexSmallestString(String S, int a, int b) {
        char[] s = S.toCharArray();
        int n = s.length;
        char[] t = new char[n];
        int step = gcd(b, n);
        int g = gcd(a, 10);
        String ans = null;

        for (int i = 0; i < n; i += step) {
            // t = s[i,n) + s[0,i)
            System.arraycopy(s, i, t, 0, n - i);
            System.arraycopy(s, 0, t, n - i, i);

            modify(t, 1, g); // 累加操作(所有奇数下标)
            if (step % 2 > 0) { // 能对偶数下标执行累加操作
                modify(t, 0, g); // 累加操作(所有偶数下标)
            }

            String str = new String(t);
            if (ans == null || str.compareTo(ans) < 0) {
                ans = str;
            }
        }

        return ans;
    }

    private void modify(char[] t, int start, int g) {
        int ch = t[start] - '0'; // 最靠前的数字,越小越好
        // ch 可以变成的最小值为 ch%g
        // 例如 ch=5,g=2,那么 ch+2+2+2(模 10)后变成 1,不可能变得更小
        // 从 ch 到 ch%g,需要增加 inc,其中 +10 保证 inc 非负(循环中会 %10 保证结果在 [0,9] 中)
        int inc = ch % g - ch + 10;
        for (int j = start; j < t.length; j += 2) {
            t[j] = (char) ('0' + (t[j] - '0' + inc) % 10);
        }
    }

    private int gcd(int a, int b) {
        while (a != 0) {
            int tmp = a;
            a = b % a;
            b = tmp;
        }
        return b;
    }
}

###cpp

class Solution {
public:
    string findLexSmallestString(string s, int a, int b) {
        int n = s.size();
        int step = gcd(b, n);
        int g = gcd(a, 10);
        string ans;

        for (int i = 0; i < n; i += step) {
            string t = s.substr(i) + s.substr(0, i); // 轮转

            auto modify = [&](int start) -> void {
                int ch = t[start] - '0'; // 最靠前的数字,越小越好
                // ch 可以变成的最小值为 ch%g
                // 例如 ch=5,g=2,那么 ch+2+2+2(模 10)后变成 1,不可能变得更小
                // 从 ch 到 ch%g,需要增加 inc,其中 +10 保证 inc 非负(循环中会 %10 保证结果在 [0,9] 中)
                int inc = ch % g - ch + 10;
                for (int j = start; j < n; j += 2) {
                    t[j] = '0' + (t[j] - '0' + inc) % 10;
                }
            };

            modify(1); // 累加操作(所有奇数下标)
            if (step % 2) { // 能对偶数下标执行累加操作
                modify(0); // 累加操作(所有偶数下标)
            }

            if (ans.empty() || t < ans) {
                ans = move(t);
            }
        }

        return ans;
    }
};

###c

int gcd(int a, int b) {
    while (a) {
        int tmp = a;
        a = b % a;
        b = tmp;
    }
    return b;
}

void modify(char* t, int n, int start, int g) {
    int ch = t[start] - '0'; // 最靠前的数字,越小越好
    // ch 可以变成的最小值为 ch%g
    // 例如 ch=5,g=2,那么 ch+2+2+2(模 10)后变成 1,不可能变得更小
    // 从 ch 到 ch%g,需要增加 inc,其中 +10 保证 inc 非负(循环中会 %10 保证结果在 [0,9] 中)
    int inc = ch % g - ch + 10;
    for (int j = start; j < n; j += 2) {
        t[j] = '0' + (t[j] - '0' + inc) % 10;
    }
}

char* findLexSmallestString(char* s, int a, int b) {
    int n = strlen(s);
    int step = gcd(b, n);
    int g = gcd(a, 10);

    char* ans = malloc((n + 1) * sizeof(char));
    ans[0] = CHAR_MAX;
    ans[1] = '\0';

    char* t = malloc((n + 1) * sizeof(char));
    t[n] = '\0';

    for (int i = 0; i < n; i += step) {
        // t = s[i,n) + s[0,i)
        strncpy(t, s + i, n - i);
        strncpy(t + n - i, s, i);

        modify(t, n, 1, g); // 累加操作(所有奇数下标)
        if (step % 2) { // 能对偶数下标执行累加操作
            modify(t, n, 0, g); // 累加操作(所有偶数下标)
        }

        if (strcmp(t, ans) < 0) {
            strcpy(ans, t);
        }
    }

    free(t);
    return ans;
}

###go

func findLexSmallestString(s string, a int, b int) string {
n := len(s)
step := gcd(b, n)
g := gcd(a, 10)
var ans []byte

for i := 0; i < n; i += step {
t := []byte(s[i:] + s[:i]) // 轮转
modify := func(start int) {
ch := t[start] - '0' // 最靠前的数字,越小越好
// ch 可以变成的最小值为 ch%g
// 例如 ch=5,g=2,那么 ch+2+2+2(模 10)后变成 1,不可能变得更小
// 从 ch 到 ch%g,需要增加 inc,其中 +10 保证 inc 非负(循环中会 %10 保证结果在 [0,9] 中)
inc := ch%byte(g) + 10 - ch
for j := start; j < n; j += 2 {
t[j] = '0' + (t[j]-'0'+inc)%10
}
}
modify(1) // 累加操作(所有奇数下标)
if step%2 > 0 { // 能对偶数下标执行累加操作
modify(0) // 累加操作(所有偶数下标)
}
if ans == nil || bytes.Compare(t, ans) < 0 {
ans = t
}
}

return string(ans)
}

func gcd(a, b int) int {
for a != 0 {
a, b = b%a, a
}
return b
}

###js

var findLexSmallestString = function(s, a, b) {
    const arr = s.split('').map(ch => parseInt(ch));
    const n = arr.length;
    const step = gcd(b, n);
    const g = gcd(a, 10);
    let ans = null;

    function modify(t, start) {
        const ch = t[start]; // 最靠前的数字,越小越好
        // ch 可以变成的最小值为 ch%g
        // 例如 ch=5,g=2,那么 ch+2+2+2(模 10)后变成 1,不可能变得更小
        // 从 ch 到 ch%g,需要增加 inc(循环中会 %10 保证结果在 [0,9] 中)
        let inc = ch % g - ch + 10;
        if (inc === 0) { // 优化:inc 为 0 时,t[j] 不变,无需执行 for 循环
            return;
        }
        for (let j = start; j < n; j += 2) {
            t[j] = (t[j] + inc) % 10;
        }
    }

    for (let i = 0; i < n; i += step) {
        const t = arr.slice(i).concat(arr.slice(0, i)); // 轮转
        modify(t, 1); // 累加操作(所有奇数下标)
        if (step % 2) { // 能对偶数下标执行累加操作
            modify(t, 0); // 累加操作(所有偶数下标)
        }
        if (ans === null || compareArray(t, ans) < 0) {
            ans = t;
        }
    }

    return ans.join('');
};

function gcd(a, b) {
    while (a) {
        [a, b] = [b % a, a];
}
return b;
}

function compareArray(a, b) {
    const n = a.length;
    for (let i = 0; i < n; i++) {
        if (a[i] !== b[i]) {
            return a[i] - b[i];
        }
    }
    return 0;
}

###rust

impl Solution {
    pub fn find_lex_smallest_string(s: String, a: i32, b: i32) -> String {
        let n = s.len();
        let step = gcd(b, n as i32) as usize;
        let g = gcd(a, 10) as u8;
        let mut ans = vec![u8::MAX];

        let modify = |t: &mut [u8], start: usize| {
            let ch = t[start] - b'0'; // 最靠前的数字,越小越好
            // ch 可以变成的最小值为 ch%g
            // 例如 ch=5,g=2,那么 ch+2+2+2(模 10)后变成 1,不可能变得更小
            // 从 ch 到 ch%g,需要增加 inc,其中 +10 保证 inc 非负(循环中会 %10 保证结果在 [0,9] 中)
            let inc = ch % g + 10 - ch;
            for j in (start..n).step_by(2) {
                t[j] = b'0' + (t[j] - b'0' + inc) % 10;
            }
        };

        for i in (0..n).step_by(step) {
            let mut t = format!("{}{}", &s[i..], &s[..i]).into_bytes(); // 轮转
            modify(&mut t, 1); // 累加操作(所有奇数下标)
            if step % 2 != 0 { // 能对偶数下标执行累加操作
                modify(&mut t, 0); // 累加操作(所有偶数下标)
            }
            ans = ans.min(t);
        }

        unsafe { String::from_utf8_unchecked(ans) }
    }
}

fn gcd(mut a: i32, mut b: i32) -> i32 {
    while a != 0 {
        (a, b) = (b % a, a);
    }
    b
}

复杂度分析

  • 时间复杂度:$\mathcal{O}\left(\dfrac{n^2}{\gcd(b,n)}\right)$,其中 $n$ 是 $s$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

:还可以枚举奇数下标累加值,枚举偶数下标累加值,然后用类似 最小表示法 的思想,计算在固定累加值的情况下,轮转后的最小字典序。时间复杂度 $\mathcal{O}(D^2n)$,$D=10$。

分类题单

如何科学刷题?

  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站@灵茶山艾府

[Python3/Java/C++/Go] 一题双解:BFS 暴力模拟 & 枚举

作者 lcbin
2023年3月19日 09:15

方法一:BFS

本题数据规模较小,我们可以考虑使用 BFS 暴力搜索所有可能的状态,然后取字典序最小的状态即可。

我们定义队列 $q$,初始时将字符串 $s$ 入队,定义一个哈希表 $vis$,用于记录字符串是否已经出现过,另外定义一个字符串 $ans$,用于记录答案。

然后,我们不断从队列中取出字符串,将其与答案 $ans$ 进行比较,如果当前字符串字典序更小,则更新答案。然后我们对该字符串进行累加和轮转操作,得到新的字符串,如果新的字符串没有出现过,则将其入队,并更新 $vis$。一直重复上述操作,直到队列为空。

class Solution:
    def findLexSmallestString(self, s: str, a: int, b: int) -> str:
        q = deque([s])
        vis = {s}
        ans = s
        while q:
            s = q.popleft()
            if ans > s:
                ans = s
            t1 = ''.join([str((int(c) + a) % 10) if i & 1 else c for i, c in enumerate(s)])
            t2 = s[-b:] + s[:-b]
            for t in (t1, t2):
                if t not in vis:
                    vis.add(t)
                    q.append(t)
        return ans
class Solution {
    public String findLexSmallestString(String s, int a, int b) {
        Deque<String> q = new ArrayDeque<>();
        q.offer(s);
        Set<String> vis = new HashSet<>();
        vis.add(s);
        String ans = s;
        int n = s.length();
        while (!q.isEmpty()) {
            s = q.poll();
            if (ans.compareTo(s) > 0) {
                ans = s;
            }
            char[] cs = s.toCharArray();
            for (int i = 1; i < n; i += 2) {
                cs[i] = (char) (((cs[i] - '0' + a) % 10) + '0');
            }
            String t1 = String.valueOf(cs);
            String t2 = s.substring(n - b) + s.substring(0, n - b);
            for (String t : List.of(t1, t2)) {
                if (vis.add(t)) {
                    q.offer(t);
                }
            }
        }
        return ans;
    }
}
class Solution {
public:
    string findLexSmallestString(string s, int a, int b) {
        queue<string> q{{s}};
        unordered_set<string> vis{{s}};
        string ans = s;
        int n = s.size();
        while (!q.empty()) {
            s = q.front();
            q.pop();
            ans = min(ans, s);
            string t1 = s;
            for (int i = 1; i < n; i += 2) {
                t1[i] = (t1[i] - '0' + a) % 10 + '0';
            }
            string t2 = s.substr(n - b) + s.substr(0, n - b);
            for (auto& t : {t1, t2}) {
                if (!vis.count(t)) {
                    vis.insert(t);
                    q.emplace(t);
                }
            }
        }
        return ans;
    }
};
func findLexSmallestString(s string, a int, b int) string {
q := []string{s}
vis := map[string]bool{s: true}
ans := s
n := len(s)
for len(q) > 0 {
s = q[0]
q = q[1:]
if ans > s {
ans = s
}
t1 := []byte(s)
for i := 1; i < n; i += 2 {
t1[i] = byte((int(t1[i]-'0')+a)%10 + '0')
}
t2 := s[n-b:] + s[:n-b]
for _, t := range []string{string(t1), t2} {
if !vis[t] {
vis[t] = true
q = append(q, t)
}
}
}
return ans
}

方法二:枚举

我们观察发现,对于累加操作,数字最多累加 $10$ 次,就会回到原来的状态;对于轮转操作,字符串最多轮转 $n$ 次,也会回到原来的状态。

因此,轮转操作最多产生 $n$ 种状态,如果轮转位数 $b$ 为偶数,累加操作只会对奇数位数字产生影响,因此总共产生 $n \times 10$ 种状态;如果轮转位数 $b$ 为奇数,累加操作既会对奇数位数字产生影响,也会对偶数位数字产生影响,因此总共产生 $n \times 10 \times 10$ 种状态。

所以,我们直接枚举所有的字符串状态,取字典序最小的状态即可。

class Solution:
    def findLexSmallestString(self, s: str, a: int, b: int) -> str:
        ans = s
        n = len(s)
        s = list(s)
        for _ in range(n):
            s = s[-b:] + s[:-b]
            for j in range(10):
                for k in range(1, n, 2):
                    s[k] = str((int(s[k]) + a) % 10)
                if b & 1:
                    for p in range(10):
                        for k in range(0, n, 2):
                            s[k] = str((int(s[k]) + a) % 10)
                        t = ''.join(s)
                        if ans > t:
                            ans = t
                else:
                    t = ''.join(s)
                    if ans > t:
                        ans = t
        return ans
class Solution {
    public String findLexSmallestString(String s, int a, int b) {
        int n = s.length();
        String ans = s;
        for (int i = 0; i < n; ++i) {
            s = s.substring(b) + s.substring(0, b);
            char[] cs = s.toCharArray();
            for (int j = 0; j < 10; ++j) {
                for (int k = 1; k < n; k += 2) {
                   cs[k] = (char) (((cs[k] - '0' + a) % 10) + '0');
                }
                if ((b & 1) == 1) {
                    for (int p = 0; p < 10; ++p) {
                        for (int k = 0; k < n; k += 2) {
                            cs[k] = (char) (((cs[k] - '0' + a) % 10) + '0');
                        }
                        s = String.valueOf(cs);
                        if (ans.compareTo(s) > 0) {
                            ans = s;
                        }
                    }
                } else {
                    s = String.valueOf(cs);
                    if (ans.compareTo(s) > 0) {
                        ans = s;
                    }
                }
            }
        }
        return ans;
    }
}
class Solution {
public:
    string findLexSmallestString(string s, int a, int b) {
        int n = s.size();
        string ans = s;
        for (int i = 0; i < n; ++i) {
            s = s.substr(n - b) + s.substr(0, n - b);
            for (int j = 0; j < 10; ++j) {
                for (int k = 1; k < n; k += 2) {
                    s[k] = (s[k] - '0' + a) % 10 + '0';
                }
                if (b & 1) {
                    for (int p = 0; p < 10; ++p) {
                        for (int k = 0; k < n; k += 2) {
                            s[k] = (s[k] - '0' + a) % 10 + '0';
                        }
                        ans = min(ans, s);
                    }
                } else {
                    ans = min(ans, s);
                }
            }
        }
        return ans;
    }
};
func findLexSmallestString(s string, a int, b int) string {
n := len(s)
ans := s
for _ = range s {
s = s[n-b:] + s[:n-b]
cs := []byte(s)
for j := 0; j < 10; j++ {
for k := 1; k < n; k += 2 {
cs[k] = byte((int(cs[k]-'0')+a)%10 + '0')
}
if b&1 == 1 {
for p := 0; p < 10; p++ {
for k := 0; k < n; k += 2 {
cs[k] = byte((int(cs[k]-'0')+a)%10 + '0')
}
s = string(cs)
if ans > s {
ans = s
}
}
} else {
s = string(cs)
if ans > s {
ans = s
}
}
}
}
return ans
}

时间复杂度 $O(n^2 \times 10^2)$,空间复杂度 $O(n)$。其中 $n$ 为字符串 $s$ 的长度。


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

暴力美学(更新进一步优化方法,0ms/6.5MB)

作者 lucifer1004
2020年10月18日 11:59

本场周赛题解 | 我的LeetCode比赛题解

解题思路

在本题的数据范围内,可以枚举所有可能的操作结果,从中选择最小的那一个。关键是:如何枚举?

首先考虑轮转操作。对于一个长度为$N$的字符串,每次轮转$b$个位置,等价于轮转$g=GCD(N,b)$个位置。所以,我们只需要以$g$为步长进行轮转的枚举即可。

接下来考虑增加操作。如果$g$是偶数,那么不管怎么轮转,我们只能对下标为奇数的位置进行增加操作;否则,我们也可以对下标为偶数的位置进行增加操作。根据这两种情况,枚举奇数和偶数位置的增加操作的次数即可。因为每一位是$[0,9]$之间的数,而$1\leq a\leq9$,所以我们只需要枚举操作$[0,9]$次的情形。

复杂度

  • 时间复杂度$O(D^2|S|^2)$,本题中$D=10$。
  • 空间复杂度$O(|S|)$

代码

###cpp

class Solution {
    int gcd(int x, int y) {
        return y == 0 ? x : gcd(y, x % y);
    }
public:
    string findLexSmallestString(string s, int a, int b) {
        int n = s.size();
        string ans = s;
        string t = s + s;
        int g = gcd(n, b);
        for (int i = 0; i < n; i += g) {
            string p = t.substr(i, n);
            for (int j = 0; j <= 9; ++j) {
                int th = g % 2 == 0 ? 0 : 9;
                for (int k = 0; k <= th; ++k) {
                    string q(p);
                    for (int t = 1; t < n; t += 2)
                        q[t] = '0' + (q[t] - '0' + a * j) % 10;
                    for (int t = 0; t < n; t += 2)
                        q[t] = '0' + (q[t] - '0' + a * k) % 10;
                    ans = min(ans, q);
                }
            }
        }
        return ans;
    }
};

进一步优化

截屏2020-10-19 21.00.22.png

上述方法还有进一步优化的空间吗?答案是肯定的。事实上,对于每一个轮转,我们只需要让其第一个奇数位,也即p[1]达到最小;当然,如果可以对偶数位进行操作,则需要再考虑让p[0]达到最小。这样,对奇偶的讨论就变成了并联而非串联的关系。在确定了奇数位(和偶数位)的操作次数后,对于每一轮转,我们只会生成一个唯一的新字符串。

从而,我们将算法的时间复杂度优化到了$O(|S|^2+D)$。

###cpp

class Solution {
    int gcd(int x, int y) {
        return y == 0 ? x : gcd(y, x % y);
    }
public:
    string findLexSmallestString(string s, int a, int b) {
        int n = s.size();
        string ans = s;
        string t = s + s;
        int ga = gcd(10, a), gb = gcd(n, b);
        
        // 奇偶通用的add操作
        auto add = [&](string &p, int pos) {
            int lo = p[pos] - '0', added = 0;
            for (int i = ga; i < 10; i += ga) {
                int c = (p[pos] - '0' + i) % 10;
                if (c < lo) {
                    lo = c;
                    added = i;
                }
            }
            if (added)
                for (int i = pos; i < n; i += 2)
                    p[i] = '0' + (p[i] - '0' + added) % 10;
        };
        
        // rotate操作
        for (int i = 0; i < n; i += gb) {
            string p = t.substr(i, n);
            add(p, 1);
            if (gb % 2)
                add(p, 0);
            ans = min(ans, p);
        }
        return ans;
    }
};
昨天以前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站@灵茶山艾府

每日一题-执行操作后的最大 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,那么说明可以构成连续的两个相邻严格递增子数组了
❌
❌