简单题:找出最长的神奇数列 |豆包 MarsCode AI 刷题

21 阅读5分钟

问题描述

小F是一个好学的中学生,今天他学习了数列的概念。他在纸上写下了一个由 0 和 1 组成的正整数序列,长度为 n。这个序列中的 1 和 0 交替出现,且至少由 3 个连续的 0 和 1 组成的部分数列称为「神奇数列」。例如,10101 是一个神奇数列,而 1011 不是。现在,小F想知道在这个序列中,最长的「神奇数列」是哪一个。你能帮他找到吗?

如果有多个神奇数列,那么输出最先出现的一个。


测试样例

样例1:

输入:inp = "0101011101"
输出:'010101'

样例2:

输入:inp = "1110101010000"
输出:'10101010'

样例3:

输入:inp = "1010101010101010"
输出:'1010101010101010'

解题过程:

问题分析

根据题目要求,不难看出这是经典的最长上升子序列问题,使用动态规划,但是所求的不是长度而是具体的子序列,不考虑其他手段。

定义「神奇数列」

• 由至少 3 个数字组成。

• 数字只能是 0 1

• 数字 01 交替出现,不能有重复的数字相邻。

目标: 找到给定二进制字符串中最长的「神奇数列」。

思路分析

🌟 动态规划的核心在于利用子问题的解来构建原问题的解。

1. 状态定义:

• 设定一个数组 dp[i],表示以第 i 个字符结尾的最长交替子串的长度。

• 初始化:dp[0] = 1,因为第一个字符本身可以视为长度为 1 的交替子串。

2. 状态转移方程:

如果当前字符与前一个字符不同,则说明可以构成一个更长的交替子串:

dp[i] = dp[i - 1] + 1

如果当前字符与前一个字符相同,则无法继续之前的交替子串,只能重新开始:

dp[i] = 1

3. 结果更新:

• 在计算 dp[i] 的过程中,维护一个变量 maxLength,记录当前最长的交替子串长度。

• 还需要记录对应的 startIndex,以便最后输出最长的「神奇数列」。

4. 判断子串长度是否至少为 3:

• 由于「神奇数列」要求长度至少为 3,所以在更新 maxLength 时,需要检查 dp[i] 是否大于等于 3

代码实现

以下是基于上述思路的 Java 实现:


public class Main {
    public static String solution(String s) {
        // Edit your code here
        int n = s.length();
        if (n < 3) {
            return ""; // 字符串长度小于3,不可能存在神奇数列
        }

        int[] dp = new int[n];
        dp[0] = 1;

        int maxLength = 0;
        int startIndex = -1;

        for (int i = 1; i < n; i++) {
            if (s.charAt(i) != s.charAt(i - 1)) {
                dp[i] = dp[i - 1] + 1;
            } else {
                dp[i] = 1;
            }

            // 检查当前交替子串是否为最长神奇数列
            if (dp[i] >= 3 && dp[i] > maxLength) {
                maxLength = dp[i];
                startIndex = i - dp[i] + 1;
            }
        }

        // 如果未找到神奇数列,返回空字符串
        if (maxLength == 0) {
            return "";
        }

        // 返回最长神奇数列
        return s.substring(startIndex, startIndex + maxLength);
    }

    public static void main(String[] args) {
        // Add your test cases here
        System.out.println(solution("0101011101"));
        System.out.println(solution("0101011101").equals("010101"));
    }
}

代码说明

初始化:

创建一个长度为 n 的数组dpdp[0] = 1,表示第一个字符的交替子串长度为 1

动态规划过程:

i = 1 开始遍历字符串:

如果 s.charAt(i) != s.charAt(i - 1) : dp[i] = dp[i - 1] + 1,当前字符可以延续前一个交替子串。

否则: dp[i] = 1,需要重新开始新的交替子串。

结果更新:

在每次更新 dp[i] 后,检查:

如果 dp[i] >= 3 且 dp[i] > maxLength,更新 maxLengthstartIndex

输出处理:

如果 maxLength == 0,说明没有符合条件的「神奇数列」,返回空字符串。

否则,使用 substring 提取最长的「神奇数列」。

示例测试

以输入 "0101011101" 为例:

dp 数组的更新过程:


i=0, dp[0]=1
i=1, s[1]!=s[0], dp[1]=2
i=2, s[2]!=s[1], dp[2]=3
i=3, s[3]!=s[2], dp[3]=4
i=4, s[4]!=s[3], dp[4]=5
i=5, s[5]!=s[4], dp[5]=6
i=6, s[6]==s[5], dp[6]=1
i=7, s[7]!=s[6], dp[7]=2
i=8, s[8]==s[7], dp[8]=1
i=9, s[9]!=s[8], dp[9]=2

maxLengthstartIndex 的更新:

i=5 时,dp[5]=6,更新 maxLength=6startIndex=0

• 最终输出 s.substring(0, 6),即 "010101"

大伙在做动态规划题的时候都可以手动去推导推导,看看结果是否符合预期

优化方向和策略

当前的实现已经是线性时间复杂度 O(n),空间复杂度 O(n)。我们可以进一步优化空间复杂度。

优化空间复杂度

不使用额外的数组 dp 我们可以只使用一个变量来记录当前交替子串的长度。

优化后的代码

import java.util.Scanner;


public class MagicSequenceOptimized {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();


        String result = findLongestMagicSequence(s);
        System.out.println(result);
    }


    public static String findLongestMagicSequence(String s) {
        int n = s.length();
        if (n < 3) {
            return ""; // 字符串长度小于3,不可能存在神奇数列
        }


        int maxLength = 0;
        int startIndex = -1;


        int currentLength = 1; // 当前交替子串的长度
        int currentStart = 0;  // 当前交替子串的起始索引


        for (int i = 1; i < n; i++) {
            if (s.charAt(i) != s.charAt(i - 1)) {
                currentLength++;
            } else {
                // 检查并更新最长神奇数列
                if (currentLength >= 3 && currentLength > maxLength) {
                    maxLength = currentLength;
                    startIndex = currentStart;
                }
                // 重置当前交替子串
                currentLength = 1;
                currentStart = i;
            }
        }


        // 检查最后一个交替子串
        if (currentLength >= 3 && currentLength > maxLength) {
            maxLength = currentLength;
            startIndex = currentStart;
        }


        if (maxLength == 0) {
            return "";
        }


        return s.substring(startIndex, startIndex + maxLength);
    }
}

优化说明

空间复杂度降为 O(1):

不再需要数组 dp[],只使用几个变量即可完成计算。

逻辑调整:

(1). 使用 currentLengthcurrentStart 记录当前交替子串的信息。

(2). 在遇到重复字符时,及时更新 maxLengthstartIndex

解题总结

一道经典的最长上升子序列问题,也是我第一次尝试解答此类问题,相比于动态规划中的其他大哥,比如路径,背包,分割来说又是不一样的解题思路,在此记录我学习的历程,与各位共勉。

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

空间复杂度:O(n)优化为 O(1)