Leetcode 1415:长度为n的开心字符串中字典序第k小的字符串(超详细的解法!!!)

该博客详细介绍了如何解决LeetCode上的第1415题,即找到长度为n的开心字符串中字典序第k小的字符串。博主提供了解题思路,包括暴力枚举法以及优化后的解决方案,通过数学计算确定字符串的每个字符。还提到了总共有3*2^n-1个开心字符串,并给出了相关参考链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个 「开心字符串」定义为:

  • 仅包含小写字母 ['a', 'b', 'c'].
  • 对所有在 1s.length - 1 之间的 i ,满足 s[i] != s[i + 1] (字符串的下标从 1 开始)。

比方说,字符串 “abc”“ac”,“b”“abcbabcbcb” 都是开心字符串,但是 “aa”“baa”“ababbc” 都不是开心字符串。

给你两个整数 nk ,你需要将长度为 n 的所有开心字符串按字典序排序。

请你返回排序后的第 k 个开心字符串,如果长度为 n 的开心字符串少于 k 个,那么请你返回 空字符串

示例 1:

输入:n = 1, k = 3
输出:"c"
解释:列表 ["a", "b", "c"] 包含了所有长度为 1 的开心字符串。按照字典序排序后第三个字符串为 "c" 。

示例 2:

输入:n = 1, k = 4
输出:""
解释:长度为 1 的开心字符串只有 3 个。

示例 3:

输入:n = 3, k = 9
输出:"cab"
解释:长度为 3 的开心字符串总共有 12 个 ["aba", "abc", "aca", "acb", "bab", "bac", "bca", "bcb", "cab", "cac", "cba", "cbc"] 。第 9 个字符串为 "cab"

示例 4:

输入:n = 2, k = 7
输出:""

示例 5:

输入:n = 10, k = 100
输出:"abacbabacb"

提示:

  • 1 <= n <= 10
  • 1 <= k <= 100

解题思路

首先比较容易想到暴力解法,也就是枚举所有可能的结果,枚举到第k个字符串的时候就结束。

class Solution:
    def getHappyString(self, n: int, k: int) -> str:
        res = None

        def dfs(s):
            nonlocal k, res
            if len(s) == n:
                k -= 1 
                if k == 0: res = s
                return
  
            for c in ['a', 'b', 'c']:
                if not s or
<think>嗯,我现在有一个问题,需要解决的是:给定一个长度为n的字符串s,可以删除其中的m个字符,使得剩下的字符串字典序最小。我得想出一个Python的解决方案。首先,我得理解这个问题。字典序最小,就是说剩下的字符串在所有可能的删除方式中,按照字典顺序排在最前面的那个。比如,假设原字符串是“bac”,删除1个字符的话,可能的选项有“ba”(删第三个字符)、“bc”(删第一个)、“ac”(删第二个)。其中最小的是“ac”。所以,问题是如何高效地找到这样的字符串。 那要怎么处理呢?可能想到的是贪心算法。贪心策略通常在这种情况下有效,因为每一步都选择当前最优的选择,可能得到全局最优解。例如,假设要保留k个字符(k = n - m),我们需要从左到右选择每个位置上的字符,确保剩下的字符尽可能小。 具体来说,可能可以用栈的结构来处理。比如,维护一个栈,遍历每个字符,当当前字符比栈顶的小,并且还有足够的字符可以删除的话,就弹出栈顶,相当于删掉之前的那个较大的字符,这样剩下的会更小。这个过程需要循环处理,直到不能再删或者栈顶的元素不大于当前字符。 举个例子,假设字符串是“cbacdcbc”,m=2,即保留6个字符。可能步骤如下: 初始栈为空。第一个字符是c,压入栈。 第二个字符是b,比c小,这时候可以弹出c(此时已经删了一个字符),然后压入b。此时栈是[b]。 第三个字符是a,样比b小,弹出b(删第二个字符,总删除数达到2),压入a。此时栈是[a]。此时已经删了2个字符,不能再删了,剩下的字符直接压入栈。最终结果是剩下的字符串是“acdcbc”,但可能还有其他情况需要考虑。 不过,这里可能需要注意,当需要保留k个字符时,应该确保最终栈的大小正好是k。所以,可能在处理每个字符的时候,要判断是否还能删除(即已经删除的数目是否还没超过m),时也要确保剩下的字符足够完成k个的长度。 另一个例子,比如字符串是“abc”,m=1,需要删除1个字符。这时候应该删除最后一个字符,得到“ab”,因为这样字典序更小。或者如果原字符串是“abac”,删除1个的话,应该选择删除第二个b,得到“aac”,或者可能要看哪边更优。 那如何设计这个算法呢?可能的步骤是: 1. 计算需要保留的字符数目k = n - m。 2. 如果k <=0,返回空字符串。 3. 初始化一个栈。 4. 遍历每个字符c: a. 当栈不为空,且栈顶的字符大于c,并且还可以删除(即已删除的数量还没到m),则弹出栈顶,相当于删除该字符,已删除数加1。 b. 将c压入栈。 5. 遍历结束后,可能栈的长度大于k,此时需要截断到k的长度。比如,如果删除的数目还没到m,那么剩下的可能只能从后面删除,因为前面的已经尽可能小了。 6. 最后,将栈中的字符组合成字符串,并且处理前导零的情况(如果有的话),但题目中没有提到是否有前导零,可能不需要处理。比如原问题中的字符串可能包含数字或者其他字符? 例如,对于输入s = "4321", m=2,k=2。处理过程: 栈初始为空。 第一个字符4,压入栈。 第二个字符3,比4小,此时可以删除4(已删1),压入3。栈是[3]。 第三个字符2,比3小,可以删除3(已删2),压入2。此时已删够,后面直接加入剩下的字符。此时栈是[2,1]。最终结果是"21",但可能正确的字典序更小的情况应该是“21”?或者原字符串是“4321”,删两个字符的话,比如删4和3,得到21。是的,这个是正确的。 另一个例子,s = "1432", m=1,k=3。遍历每个字符: 栈初始为空。 1压入栈。 4压入栈。栈是[1,4]。 3比4小,此时可以删除4(已删1),压入3。此时栈是[1,3]。剩下的字符是2,压入栈。最终结果是"132"吗?或者是否正确?或者可能正确的应该是删掉4,得到1、3、2,即"132",确实是字典序比保留4的情况下的"143"更小? 看来这种贪心策略是可行的。所以,这个算法的步骤应该是正确的。 那如何用Python实现呢? 可能的代码结构: def removeKdigits(s: str, m: int) -> str: k = len(s) - m if k <=0: return "" stack = [] for c in s: while m >0 and stack and stack[-1] > c: stack.pop() m -=1 stack.append(c) # 如果还有剩余的m,需要从栈后面删掉(比如原字符串是递增的情况) # 例如,s=12345, m=2,这时候前面的循环不会删除任何字符,所以需要从后面删掉最后两位 if m >0: stack = stack[:-m] # 现在,栈可能超过k长度?比如k = len(s)-m,但处理之后可能栈的长度是len(s)-m_removed,而 m_removed可能小于m? # 例如,假设原来的循环中删除了m_removed个,剩下的需要再删m - m_removed个,这时候总保留的长度是 len(stack) - (m - m_removed) = len(stack) -m + m_removed = 原长度 s - m_removed - m + m_removed = s.len() -m =k?所以可能不需要? # 或者,当处理完所有字符后,可能栈的长度是 len(s) - m_removed,其中 m_removed是已删除的次数。此时需要确保栈的长度为k,即总保留k个字符。所以如果 len(stack) >k,则需要截断到k的长度。例如,当原字符串是递增的,所以在处理过程中没有删除足够的数目,这时候剩下的需要从后面删掉多余的。 # 例如,当k=2,原字符串是“1234”,m=2。处理后的栈是['1','2','3','4'],此时m还是2。所以需要截断到前k=2个字符? # 所以,不管怎样,最终的结果应该取前k个字符?或者取栈的前k个?或者可能栈的长度可能超过k? 比如,假设总共有n个字符,处理后栈的长度是n - m_removed,而m_removed可能小于m。这时候需要删除剩下的m_remaining = m - m_removed个字符,这时候应该从栈的末尾删除,因为栈前面的已经尽可能小,所以末尾的字符较大,所以删掉末尾的。 所以,最终的处理是: stack = stack[:k] # 因为k = len(s) -m,所以如果处理过程中已经删除了m_removed个,那么栈的长度是 len(s) -m_removed,此时如果k = len(s) -m,则可能需要将栈截断到k的长度,即删除 (len(s) -m_removed) -k = (len(s) -m_removed) - (len(s)-m) )= m -m_removed,也就是剩下的需要删除的数量。 例如,原来的处理之后,栈的长度是 len(s) -m_removed,而需要保留的是k = len(s)-m,所以当 len(stack) >k时,应该截断到k的长度。或者,当处理后m_removed <m的话,此时总共有m_removed次删除,剩下的需要删掉 m -m_removed次,即从栈里删掉后面的m -m_removed次。所以最终栈应该是 stack[:k]. 因此,代码可以加上: # 最后,可能需要截断到k的长度 stack = stack[:k] 所以综合起来,代码: def min_remaining_string(s: str, m: int) -> str: k = len(s) - m if k <=0: return "" stack = [] for c in s: while m >0 and stack and stack[-1] > c: stack.pop() m -=1 stack.append(c) # 如果还有剩余的m,删除后面的 if m >0: stack = stack[:-m] # 截断到k的长度 stack = stack[:k] # 转换为字符串 return ''.join(stack).lstrip('0') or '0' # 这个步骤是否需要?比如,原题是否有前导零的问题? 比如,假设原题中的字符串是数字,例如LeetCode 402题,需要删除k个数字使得剩下的最小,并且要处理前导零的情况。但问题中用户是否提到这种情况?比如用户的问题描述是否有这样的例子? 根据用户的问题,可能需要考虑前导零的情况。例如,如果输入是“10200”,m=1,k=4。此时正确结果是“0200”,但前导零可能要被删除,所以结果是“200”吗?或者原题中的要求是否需要处理这种情况? 例如,原题可能要求删除后的字符串不能有前导零,或者如果有的话,返回“0”。比如LeetCode 402题的处理方式。但用户的问题中是否提到这一点? 用户的问题描述是:“输出这个字符串”,所以可能不需要处理前导零,或者应该保留所有字符,包括前导零。但可能需要根据实际情况调整。例如,原题如果是数字的话,前导零是不需要的,但如果是普通字符串,可能有不的要求。例如,假设输入是“000”,m=0,输出是“000”是有效的。因此,是否要去掉前导零可能取决于具体问题要求。如果用户的问题没有特别说明,可能不需要处理。但可能在例子中,比如“10200” m=1,正确的处理是得到“0200”还是“200”?根据贪心算法,栈处理后的结果是删除一个字符。比如原字符串是“10200”,m=1,k=4。遍历每个字符: 初始栈为空。 字符1入栈。 字符0:此时栈顶是1>0,并且m=1>0,所以弹出1,m减为0。栈变为[0],压入0。此时栈是[0]。 接下来字符2:m=0,无法弹出,直接压入。栈是[0,2]. 字符0:压入,栈[0,2,0]. 字符0:压入,栈[0,2,0,0]. 最终,k=4,所以栈的长度是4,所以结果是'0200'。如果问题允许前导零,则正确。否则可能需要处理。 但用户的问题可能没有要求去除前导零,所以代码是否需要处理?这可能需要明确。例如,如果原题中的示例输入是否包含这种情况? 假设用户的问题不需要处理前导零,那么可以返回栈中的字符组成的字符串。否则,需要处理前导零。比如,在LeetCode 402题中,需要返回最小的数,所以要去掉前导零,并且如果结果为空,返回“0”。 但用户的问题描述中并没有特别说明,所以可能不需要处理前导零。或者,是否题目中的字符串是数字?例如,问题可能说“有一个数字字符串s”,则可能需要处理前导零。但用户的问题中给出的描述是“字符串s”,所以可能无需处理。 因此,在代码中是否需要添加处理前导零的步骤?例如,最后返回时,使用.lstrip('0'),如果结果是空则返回“0”? 例如,在用户给出的问题中,可能希望将结果中的前导零去除。例如,输入s是“1432”,m=1,得到“132”,这没有问题。但如果是输入s是“10200”,m=1,则结果应该是“0200”,还是“200”? 根据问题描述,可能原题要求输出字典序最小的字符串,而字典序中“0200”比“200”小,因为第一个字符0比2小。但如果是作为数字的话,“0200”等于“200”,但作为字符串来说,“0200”的字典序更小。因此,原题可能要求保留前导零。所以,代码不需要处理前导零,直接返回栈中的结果。 但需要确认用户的问题是否有这样的要求。根据用户的问题描述,可能没有,所以代码不需要处理前导零。 所以最终的代码是: def min_remaining_string(s: str, m: int) -> str: k = len(s) - m if k <= 0: return "" stack = [] for c in s: while m > 0 and stack and stack[-1] > c: stack.pop() m -= 1 stack.append(c) # 处理剩余的m if m > 0: stack = stack[:-m] # 截断到k的长度 stack = stack[:k] # 转换为字符串 return ''.join(stack) 但可能还需要处理当k为0的情况,比如当n=3,m=3,此时k=0,返回空字符串。 测试这个代码是否可行? 例如,测试案例: 案例1: s = "abc", m = 1 → k=2。遍历每个字符: a入栈。 b入栈,此时栈是[a,b]. c入栈。此时m=0,栈是[a,b,c]. 但k=2,所以截断到前两位,得到'ab'。但正确的应该是删除最后一个字符,得到'ab'。是的。 案例2: s = "bac", m=1 → 正确结果应该是'ac'。代码处理过程: b入栈。 a < b,弹出b(m=0),压入a。栈是[a]. c压入。此时栈长度是2,k=3-1=2。所以返回'ac'。正确。 案例3: s = "4321", m=2 → k=2. 处理过程: 4入栈。 3 <4 → 弹出4(m=1),压入3。 2 <3 → 弹出3(m=0),压入2. 1压入。此时栈是[2,1]. m=0。k=2,所以返回'21'。正确。 案例4: s = "10200", m=1 → k=4. 处理过程: 1入栈。 0 <1 → 弹出1(m=0),压入0. 2压入,0压入,0压入。栈是[0,2,0,0]. 返回 '0200',字典序比任何其他可能更小。比如,其他可能如删除第二个0得到'1200',但字典序更大。所以代码正确。 所以,这个算法是正确的。</think>为了使删除m个字符后的字符串字典序最小,可以使用贪心算法结合栈结构。具体步骤如下: 1. **计算保留长度**:保留的字符数k = len(s) - m。如果k ≤ 0,直接返回空字符串。 2. **维护单调栈**:遍历字符串,若当前字符比栈顶小且还有删除次数,则弹出栈顶(相当于删除更大的字符)。 3. **处理剩余删除**:遍历后若仍有删除次数,从栈末尾删除多余字符。 4. **截断到目标长度**:确保最终结果长度为k。 实现代码如下: ```python def min_remaining_string(s: str, m: int) -> str: k = len(s) - m if k <= 0: return "" stack = [] for c in s: while m > 0 and stack and stack[-1] > c: stack.pop() m -= 1 stack.append(c) # 处理剩余删除次数 if m > 0: stack = stack[:-m] # 截断到k的长度 stack = stack[:k] return ''.join(stack) ``` **示例分析**: - 输入:`s = "bac", m = 1` - 处理过程: 1. `'b'`入栈。 2. `'a'`比栈顶`'b'`小,删除`'b'`,`'a'`入栈。 3. `'c'`直接入栈。 4. 最终栈为`['a', 'c']`,返回`"ac"`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值