普通视图

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

每日一题-找出有效子序列的最大长度 I🟡

2025年7月16日 00:00

给你一个整数数组 nums

nums 的子序列 sub 的长度为 x ,如果其满足以下条件,则称其为 有效子序列

  • (sub[0] + sub[1]) % 2 == (sub[1] + sub[2]) % 2 == ... == (sub[x - 2] + sub[x - 1]) % 2

返回 nums最长的有效子序列 的长度。

一个 子序列 指的是从原数组中删除一些元素(也可以不删除任何元素),剩余元素保持原来顺序组成的新数组。

 

示例 1:

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

输出: 4

解释:

最长的有效子序列是 [1, 2, 3, 4]

示例 2:

输入: nums = [1,2,1,1,2,1,2]

输出: 6

解释:

最长的有效子序列是 [1, 2, 1, 2, 1, 2]

示例 3:

输入: nums = [1,3]

输出: 2

解释:

最长的有效子序列是 [1, 3]

 

提示:

  • 2 <= nums.length <= 2 * 105
  • 1 <= nums[i] <= 107

3201. 找出有效子序列的最大长度 I

作者 stormsunshine
2024年6月30日 14:23

解法一

思路和算法

有效子序列满足子序列的任意相邻两项之和除以 $2$ 的余数相等,因此有效子序列中的元素奇偶性有如下四种情况。

  • 有效子序列中的所有元素都是偶数。

  • 有效子序列中的所有元素都是奇数。

  • 有效子序列中的所有元素的奇偶性交替出现,且末尾元素是偶数。

  • 有效子序列中的所有元素的奇偶性交替出现,且末尾元素是奇数。

对于每个有效子序列,在移除最后一个元素之后,剩余元素仍是有效子序列且有效子序列的元素奇偶性不变,因此可以使用动态规划计算最长有效子序列的长度。

用 $n$ 表示数组 $\textit{nums}$ 的长度。创建 $n \times 2 \times 2$ 的三维数组 $\textit{dp}$,其中 $\textit{dp}[i][0][0]$ 表示数组 $\textit{nums}$ 的下标范围 $[0, i]$ 中的所有元素的奇偶性相同且都是偶数的最长有效子序列的长度,$\textit{dp}[i][0][1]$ 表示数组 $\textit{nums}$ 的下标范围 $[0, i]$ 中的所有元素的奇偶性相同且都是奇数的最长有效子序列的长度, $\textit{dp}[i][1][0]$ 表示数组 $\textit{nums}$ 的下标范围 $[0, i]$ 中的所有元素的奇偶性交替出现且末尾元素是偶数的最长有效子序列的长度,$\textit{dp}[i][1][1]$ 表示数组 $\textit{nums}$ 的下标范围 $[0, i]$ 中的所有元素的奇偶性交替出现且末尾元素是奇数的最长有效子序列的长度。

当 $i = 0$ 时,数组 $\textit{nums}$ 的下标范围 $[0, i]$ 中只有一个元素 $\textit{nums}[0]$,因此动态规划的边界情况如下。

  • 当 $\textit{nums}[0]$ 是偶数时,$\textit{dp}[0][0][0] = \textit{dp}[0][1][0] = 1$,$\textit{dp}[0][0][1] = \textit{dp}[0][1][1] = 0$。

  • 当 $\textit{nums}[0]$ 是奇数时,$\textit{dp}[0][0][0] = \textit{dp}[0][1][0] = 0$,$\textit{dp}[0][0][1] = \textit{dp}[0][1][1] = 1$。

当 $i > 0$ 时,需要根据 $\textit{nums}[i]$ 的奇偶性计算 $\textit{dp}[i][][]$ 的状态值。

  • 当 $\textit{nums}[i]$ 是偶数时,如果 $\textit{nums}[i]$ 是有效子序列的末尾元素,则有效子序列的末尾元素是偶数,此时可以在所有元素都是偶数或者所有元素的奇偶性交替出现且末尾元素是奇数的有效子序列的后面添加 $\textit{nums}[i]$ 得到更长的有效子序列。

  • 当 $\textit{nums}[i]$ 是奇数时,如果 $\textit{nums}[i]$ 是有效子序列的末尾元素,则有效子序列的末尾元素是奇数,此时可以在所有元素都是奇数或者所有元素的奇偶性交替出现且末尾元素是偶数的有效子序列的后面添加 $\textit{nums}[i]$ 得到更长的有效子序列。

因此动态规划的状态转移方程如下。

  • 当 $\textit{nums}[i]$ 是偶数时,$\textit{dp}[i][0][0] = \textit{dp}[i - 1][0][0] + 1$,$\textit{dp}[i][0][1] = \textit{dp}[i - 1][0][1]$,$\textit{dp}[i][1][0] = \textit{dp}[i - 1][1][1] + 1$,$\textit{dp}[i][1][1] = \textit{dp}[i - 1][1][1]$。

  • 当 $\textit{nums}[i]$ 是奇数时,$\textit{dp}[i][0][0] = \textit{dp}[i - 1][0][0]$,$\textit{dp}[i][0][1] = \textit{dp}[i - 1][0][1] + 1$,$\textit{dp}[i][1][0] = \textit{dp}[i - 1][1][0]$,$\textit{dp}[i][1][1] = \textit{dp}[i - 1][1][0] + 1$。

根据动态规划的状态转移方程,应从小到大遍历每个 $i$ 并计算 $\textit{dp}[i][][]$。计算所有的状态之后,$\textit{dp}[n - 1][][]$ 中的最大值即为最长有效子序列的长度。

上述做法的时间复杂度和空间复杂度都是 $O(n)$。

实现方面,由于 $\textit{dp}[i][][]$ 只取决于 $\textit{dp}[i - 1][][]$,和更早的状态无关,因此可以使用滚动数组的思想,只保留前一个 $i$ 处的状态,将空间复杂度降到 $O(1)$。

代码

下面的代码为不优化空间的实现。

###Java

class Solution {
    public int maximumLength(int[] nums) {
        int n = nums.length;
        int[][][] dp = new int[n][2][2];
        dp[0][0][nums[0] % 2] = 1;
        dp[0][1][nums[0] % 2] = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] % 2 == 0) {
                dp[i][0][0] = dp[i - 1][0][0] + 1;
                dp[i][0][1] = dp[i - 1][0][1];
                dp[i][1][0] = dp[i - 1][1][1] + 1;
                dp[i][1][1] = dp[i - 1][1][1];
            } else {
                dp[i][0][0] = dp[i - 1][0][0];
                dp[i][0][1] = dp[i - 1][0][1] + 1;
                dp[i][1][0] = dp[i - 1][1][0];
                dp[i][1][1] = dp[i - 1][1][0] + 1;
            }
        }
        return Math.max(Math.max(dp[n - 1][0][0], dp[n - 1][0][1]), Math.max(dp[n - 1][1][0], dp[n - 1][1][1]));
    }
}

###C#

public class Solution {
    public int MaximumLength(int[] nums) {
        int n = nums.Length;
        int[][][] dp = new int[n][][];
        for (int i = 0; i < n; i++) {
            dp[i] = new int[2][];
            for (int j = 0; j < 2; j++) {
                dp[i][j] = new int[2];
            }
        }
        dp[0][0][nums[0] % 2] = 1;
        dp[0][1][nums[0] % 2] = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] % 2 == 0) {
                dp[i][0][0] = dp[i - 1][0][0] + 1;
                dp[i][0][1] = dp[i - 1][0][1];
                dp[i][1][0] = dp[i - 1][1][1] + 1;
                dp[i][1][1] = dp[i - 1][1][1];
            } else {
                dp[i][0][0] = dp[i - 1][0][0];
                dp[i][0][1] = dp[i - 1][0][1] + 1;
                dp[i][1][0] = dp[i - 1][1][0];
                dp[i][1][1] = dp[i - 1][1][0] + 1;
            }
        }
        return Math.Max(Math.Max(dp[n - 1][0][0], dp[n - 1][0][1]), Math.Max(dp[n - 1][1][0], dp[n - 1][1][1]));
    }
}

下面的代码为优化空间的实现。

###Java

class Solution {
    public int maximumLength(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[2][2];
        dp[0][nums[0] % 2] = 1;
        dp[1][nums[0] % 2] = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] % 2 == 0) {
                dp[0][0]++;
                dp[1][0] = dp[1][1] + 1;
            } else {
                dp[0][1]++;
                dp[1][1] = dp[1][0] + 1;
            }
        }
        return Math.max(Math.max(dp[0][0], dp[0][1]), Math.max(dp[1][0], dp[1][1]));
    }
}

###C#

public class Solution {
    public int MaximumLength(int[] nums) {
        int n = nums.Length;
        int[][] dp = new int[2][];
        for (int j = 0; j < 2; j++) {
            dp[j] = new int[2];
        }
        dp[0][nums[0] % 2] = 1;
        dp[1][nums[0] % 2] = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] % 2 == 0) {
                dp[0][0]++;
                dp[1][0] = dp[1][1] + 1;
            } else {
                dp[0][1]++;
                dp[1][1] = dp[1][0] + 1;
            }
        }
        return Math.Max(Math.Max(dp[0][0], dp[0][1]), Math.Max(dp[1][0], dp[1][1]));
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是字符串 $s$ 的长度。状态数是 $O(n)$,每个状态的计算时间是 $O(1)$,因此时间复杂度是 $O(n)$。

  • 空间复杂度:$O(n)$ 或 $O(1)$,其中 $n$ 是字符串 $s$ 的长度。空间复杂度取决于实现方式,不优化空间的实现的空间复杂度是 $O(n)$,优化空间的实现的空间复杂度是 $O(1)$。

解法二

思路和算法

考虑更一般的情况,子序列 $\textit{sub}$ 的任意相邻两项之和除以 $k$ 的余数相等。这道题为 $k = 2$ 的特例。

长度为 $x$ 的有效子序列 $\textit{sub}$ 满足对于任意 $1 \le i \le x - 2$ 都有 $(\textit{sub}[i - 1] + \textit{sub}[i]) \bmod k = (\textit{sub}[i] + \textit{sub}[i + 1]) \bmod k$,等价于 $(\textit{sub}[i - 1] + \textit{sub}[i] - \textit{sub}[i] - \textit{sub}[i + 1]) \bmod k = 0$,整理得 $(\textit{sub}[i - 1] - \textit{sub}[i + 1]) \bmod k = 0$,即 $\textit{sub}[i - 1] \bmod k = \textit{sub}[i + 1] \bmod k$。因此子序列 $\textit{sub}$ 满足子序列中的下标相差 $2$ 的两项除以 $k$ 的余数相等,进一步可以得到子序列 $\textit{sub}$ 满足所有偶数下标项除以 $k$ 的余数相等且所有奇数下标项除以 $k$ 的余数相等。

为了计算最长有效子序列的长度,对于所有满足 $0 \le i, j < k$ 的整数 $i$ 和 $j$ 需要计算最后两项除以 $k$ 的余数分别是 $i$ 和 $j$ 的子序列的最大长度。

创建 $k \times k$ 的二维数组 $\textit{dp}$,其中 $\textit{dp}[i][j]$ 表示最后两项除以 $k$ 的余数分别是 $i$ 和 $j$ 的子序列的最大长度。

动态规划的边界情况是:如果当前没有最后两项除以 $k$ 的余数分别是 $i$ 和 $j$ 的子序列,则 $\textit{dp}[i][j] = 0$。初始时,$\textit{dp}$ 中的所有状态值都是 $0$。

遍历数组 $\textit{nums}$,对于遍历到的每个元素 $\textit{num}$,执行如下操作。

  1. 计算当前余数 $\textit{curr} = \textit{num} \bmod k$。

  2. 遍历 $0 \le \textit{prev} < k$ 的每个余数 $\textit{prev}$,为了得到最后两项除以 $k$ 的余数分别是 $\textit{prev}$ 和 $\textit{curr}$ 的最长有效子序列,需要首先得到最后两项除以 $k$ 的余数分别是 $\textit{curr}$ 和 $\textit{prev}$ 的最长有效子序列,然后在末尾添加 $\textit{num}$ 得到更长的有效子序列,因此将 $\textit{dp}[\textit{prev}][\textit{curr}]$ 的值更新为 $\textit{dp}[\textit{curr}][\textit{prev}] + 1$,然后使用 $\textit{dp}[\textit{prev}][\textit{curr}]$ 更新最长有效子序列的长度。

上述计算过程即为动态规划的状态转移方程。

遍历结束之后,即可得到最长有效子序列的长度。

代码

###Java

class Solution {
    public int maximumLength(int[] nums) {
        return maximumLength(nums, 2);
    }

    public int maximumLength(int[] nums, int k) {
        int maxLength = 0;
        int[][] dp = new int[k][k];
        for (int num : nums) {
            int curr = num % k;
            for (int prev = 0; prev < k; prev++) {
                dp[prev][curr] = dp[curr][prev] + 1;
                maxLength = Math.max(maxLength, dp[prev][curr]);
            }
        }
        return maxLength;
    }
}

###C#

public class Solution {
    public int MaximumLength(int[] nums) {
        return MaximumLength(nums, 2);
    }

    public int MaximumLength(int[] nums, int k) {
        int maxLength = 0;
        int[][] dp = new int[k][];
        for (int i = 0; i < k; i++) {
            dp[i] = new int[k];
        }
        foreach (int num in nums) {
            int curr = num % k;
            for (int prev = 0; prev < k; prev++) {
                dp[prev][curr] = dp[curr][prev] + 1;
                maxLength = Math.Max(maxLength, dp[prev][curr]);
            }
        }
        return maxLength;
    }
}

复杂度分析

  • 时间复杂度:$O(k^2 + nk)$,其中 $k$ 是给定的整数,这道题中 $k = 2$,$n$ 是数组 $\textit{nums}$ 的长度。创建大小为 $k \times k$ 的二维数组 $\textit{dp}$ 的时间是 $O(k^2)$,遍历数组 $\textit{nums}$ 时对于每个元素的计算时间是 $O(k)$,因此时间复杂度是 $O(k^2 + nk)$。

  • 空间复杂度:$O(k^2)$,其中 $k$ 是给定的整数,这道题中 $k = 2$。需要创建大小为 $k \times k$ 的二维数组。

昨天 — 2025年7月15日LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:模拟(清晰题解)

作者 lcbin
2025年7月15日 06:54

方法一:模拟

我们首先判断字符串的长度是否小于 3,如果是则返回 false

然后我们遍历字符串,判断每个字符是否是字母或数字,如果不是则返回 false。否则我们判断该字符是否是元音字母,如果是则将 has_vowel 置为 true,否则将 has_consonant 置为 true

最后,如果 has_vowelhas_consonant 都为 true,则返回 true,否则返回 false

###python

class Solution:
    def isValid(self, word: str) -> bool:
        if len(word) < 3:
            return False
        has_vowel = has_consonant = False
        vs = set("aeiouAEIOU")
        for c in word:
            if not c.isalnum():
                return False
            if c.isalpha():
                if c in vs:
                    has_vowel = True
                else:
                    has_consonant = True
        return has_vowel and has_consonant

###java

class Solution {
    public boolean isValid(String word) {
        if (word.length() < 3) {
            return false;
        }
        boolean hasVowel = false, hasConsonant = false;
        boolean[] vs = new boolean[26];
        for (char c : "aeiou".toCharArray()) {
            vs[c - 'a'] = true;
        }
        for (char c : word.toCharArray()) {
            if (Character.isAlphabetic(c)) {
                if (vs[Character.toLowerCase(c) - 'a']) {
                    hasVowel = true;
                } else {
                    hasConsonant = true;
                }
            } else if (!Character.isDigit(c)) {
                return false;
            }
        }
        return hasVowel && hasConsonant;
    }
}

###cpp

class Solution {
public:
    bool isValid(string word) {
        if (word.size() < 3) {
            return false;
        }
        bool has_vowel = false, has_consonant = false;
        bool vs[26]{};
        string vowels = "aeiou";
        for (char c : vowels) {
            vs[c - 'a'] = true;
        }
        for (char c : word) {
            if (isalpha(c)) {
                if (vs[tolower(c) - 'a']) {
                    has_vowel = true;
                } else {
                    has_consonant = true;
                }
            } else if (!isdigit(c)) {
                return false;
            }
        }
        return has_vowel && has_consonant;
    }
};

###go

func isValid(word string) bool {
if len(word) < 3 {
return false
}
hasVowel := false
hasConsonant := false
vs := make([]bool, 26)
for _, c := range "aeiou" {
vs[c-'a'] = true
}
for _, c := range word {
if unicode.IsLetter(c) {
if vs[unicode.ToLower(c)-'a'] {
hasVowel = true
} else {
hasConsonant = true
}
} else if !unicode.IsDigit(c) {
return false
}
}
return hasVowel && hasConsonant
}

###ts

function isValid(word: string): boolean {
    if (word.length < 3) {
        return false;
    }
    let hasVowel: boolean = false;
    let hasConsonant: boolean = false;
    const vowels: Set<string> = new Set(['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']);
    for (const c of word) {
        if (!c.match(/[a-zA-Z0-9]/)) {
            return false;
        }
        if (/[a-zA-Z]/.test(c)) {
            if (vowels.has(c)) {
                hasVowel = true;
            } else {
                hasConsonant = true;
            }
        }
    }
    return hasVowel && hasConsonant;
}

###rust

impl Solution {
    pub fn is_valid(word: String) -> bool {
        if word.len() < 3 {
            return false;
        }

        let mut has_vowel = false;
        let mut has_consonant = false;
        let vowels = ['a', 'e', 'i', 'o', 'u'];

        for c in word.chars() {
            if !c.is_alphanumeric() {
                return false;
            }
            if c.is_alphabetic() {
                let lower_c = c.to_ascii_lowercase();
                if vowels.contains(&lower_c) {
                    has_vowel = true;
                } else {
                    has_consonant = true;
                }
            }
        }

        has_vowel && has_consonant
    }
}

###cs

public class Solution {
    public bool IsValid(string word) {
        if (word.Length < 3) {
            return false;
        }

        bool hasVowel = false, hasConsonant = false;
        bool[] vs = new bool[26];
        foreach (char c in "aeiou") {
            vs[c - 'a'] = true;
        }

        foreach (char c in word) {
            if (char.IsLetter(c)) {
                char lower = char.ToLower(c);
                if (vs[lower - 'a']) {
                    hasVowel = true;
                } else {
                    hasConsonant = true;
                }
            } else if (!char.IsDigit(c)) {
                return false;
            }
        }

        return hasVowel && hasConsonant;
    }
}

时间复杂度 $O(n)$,空间复杂度 $O(1)$。其中 $n$ 为字符串的长度。


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

有效单词

2025年7月7日 10:48

方法一:一次遍历

思路与算法

首先,我们可以判断给定的单词长度是否大于等于 $3$,其次我们需要通过一次遍历来判断是否包含元音字母、辅音字母以及除去数字和大小写字母以外的其他字母。

代码

###C++

class Solution {
public:
    bool isValid(string word) {
        if (word.size() < 3) {
            return false;
        }
        bool has_vowel = false;
        bool has_consonant = false;
        for (auto c : word) {
            if (isalpha(c)) {
                c = tolower(c);
                if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                    has_vowel = true;
                } else {
                    has_consonant = true;
                }
            } else if (!isdigit(c)) {
                return false;
            }
        }
        return has_vowel && has_consonant;
    }
};

###Python

class Solution:
    def isValid(self, word: str) -> bool:
        if len(word) < 3:
            return False

        has_vowel = False
        has_consonant = False

        for c in word:
            if c.isalpha():
                if c.lower() in 'aeiou':
                    has_vowel = True
                else:
                    has_consonant = True
            elif not c.isdigit():
                return False

        return has_vowel and has_consonant

###Rust

impl Solution {
    pub fn is_valid(word: String) -> bool {
        if word.len() < 3 {
            return false;
        }

        let mut has_vowel = false;
        let mut has_consonant = false;

        for c in word.chars() {
            if c.is_ascii_alphabetic() {
                let lc = c.to_ascii_lowercase();
                if "aeiou".contains(lc) {
                    has_vowel = true;
                } else {
                    has_consonant = true;
                }
            } else if !c.is_ascii_digit() {
                return false;
            }
        }

        has_vowel && has_consonant
    }
}

###Java

class Solution {
    public boolean isValid(String word) {
        if (word.length() < 3) {
            return false;
        }
        boolean hasVowel = false;
        boolean hasConsonant = false;
        for (char c : word.toCharArray()) {
            if (Character.isLetter(c)) {
                char ch = Character.toLowerCase(c);
                if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
                    hasVowel = true;
                } else {
                    hasConsonant = true;
                }
            } else if (!Character.isDigit(c)) {
                return false;
            }
        }
        return hasVowel && hasConsonant;
    }
}

###C#

public class Solution {
    public bool IsValid(string word) {
        if (word.Length < 3) {
            return false;
        }
        bool hasVowel = false;
        bool hasConsonant = false;
        foreach (char c in word) {
            if (char.IsLetter(c)) {
                char ch = char.ToLower(c);
                if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
                    hasVowel = true;
                } else {
                    hasConsonant = true;
                }
            } else if (!char.IsDigit(c)) {
                return false;
            }
        }
        return hasVowel && hasConsonant;
    }
}

###Go

func isValid(word string) bool {
    if len(word) < 3 {
        return false
    }
    hasVowel := false
    hasConsonant := false
    for _, c := range word {
        if unicode.IsLetter(c) {
            ch := unicode.ToLower(c)
            if ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u' {
                hasVowel = true
            } else {
                hasConsonant = true
            }
        } else if !unicode.IsDigit(c) {
            return false
        }
    }
    return hasVowel && hasConsonant
}

###C

bool isValid(char* word) {
    int len = strlen(word);
    if (len < 3) {
        return false;
    }
    bool has_vowel = false;
    bool has_consonant = false;
    for (int i = 0; i < len; i++) {
        char c = word[i];
        if (isalpha(c)) {
            c = tolower(c);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                has_vowel = true;
            } else {
                has_consonant = true;
            }
        } else if (!isdigit(c)) {
            return false;
        }
    }
    return has_vowel && has_consonant;
}

###JavaScript

var isValid = function(word) {
    if (word.length < 3) {
        return false;
    }
    let hasVowel = false;
    let hasConsonant = false;
    for (const c of word) {
        if (/[a-zA-Z]/.test(c)) {
            const ch = c.toLowerCase();
            if (ch === 'a' || ch === 'e' || ch === 'i' || ch === 'o' || ch === 'u') {
                hasVowel = true;
            } else {
                hasConsonant = true;
            }
        } else if (!/\d/.test(c)) {
            return false;
        }
    }
    return hasVowel && hasConsonant;
};

###TypeScript

function isValid(word: string): boolean {
    if (word.length < 3) {
        return false;
    }
    let hasVowel = false;
    let hasConsonant = false;
    for (const c of word) {
        if (/[a-zA-Z]/.test(c)) {
            const ch = c.toLowerCase();
            if (ch === 'a' || ch === 'e' || ch === 'i' || ch === 'o' || ch === 'u') {
                hasVowel = true;
            } else {
                hasConsonant = true;
            }
        } else if (!/\d/.test(c)) {
            return false;
        }
    }
    return hasVowel && hasConsonant;
};

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是 $\textit{word}$ 的长度。由于只进行了一次遍历,因此时间复杂度为 $O(n)$。

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

❌
❌