SWE · Coding

TikTok 星号括号:可达余额区间与全部合法展开

将星号明确为左括号、右括号或空,线性判定,并用DFS枚举去重后结果。

题目重点:将星号明确为左括号、右括号或空,线性判定,并用DFS枚举去重后结果。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练只含(、)、*,每个*独立替换为(、)或空串;只输出合法括号串,不输出替换决策历史。
  • 空输入合法,枚举得到包含空字符串的列表;输出不同字符串去重并排序。
  • 枚举与判定复杂度分别说明,星号很多时输出可指数增长。

解题路线

  1. 标准括号余额balance是左数减右数,任何前缀不能负,最终必须0。有*时同一前缀可对应多个余额;只保存最小low和最大high,描述仍可达到的非负余额区间。
  2. 遇(两端加1;遇)两端减1,但low截为0;遇*,最小可少1(当作右)且不能负,最大可多1(当作左)。因为*还可为空,每步可达余额不会形成奇偶空洞,区间表示完整。
  3. 若high<0,所有替换都已出现负前缀,立即False;末尾low=0表示至少有一条路线回到零。只看总括号数或把所有*优先补左括号,会忽略前缀约束。
  4. 枚举时DFS维护index、实际balance与字符前缀。*依次试空、左、右;负余额立即剪枝,balance>剩余字符数时即使余下全右也无法归零,同样剪枝。
  5. 到末尾balance=0才加入结果集合。不同替换历史可能产生同一字符串,例如两个*可以左右或空空等不同路径,输出合同要求按最终字符串去重,而不是把每条决策都算独立答案。
  6. 不要把线性判定算法说成能在线性时间输出全部答案。判定只证明存在;枚举必须承担输出大小,prefix列表追加/弹出避免每层反复复制长字符串,叶子再join。
  7. 若*只允许左右而不能空,可达余额具有奇偶限制,本文low截零的连续区间论证需调整。若追问输出一种可行替换,可记录构造而非枚举所有,先重新确认空替换的规则。

手动走一遍

(*)可把*当空得到();(*))把*当左得到(());)*无论如何开头已负,False。输入**的不同合法结果是空串和();多个替换路线输出同一串时只留一份。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

def valid_star(text):
    if any(c not in '()*' for c in text):
        raise ValueError('expected parentheses or star')
    low = high = 0
    for char in text:
        if char == '(':
            low += 1
            high += 1
        elif char == ')':
            low = max(0, low - 1)
            high -= 1
        else:
            low = max(0, low - 1)
            high += 1
        if high < 0:
            return False
    return low == 0


def expand_valid(text):
    if any(c not in '()*' for c in text):
        raise ValueError('expected parentheses or star')
    results, prefix = set(), []
    def search(index, balance):
        if balance < 0 or balance > len(text) - index:
            return
        if index == len(text):
            if balance == 0:
                results.add(''.join(prefix))
            return
        choices = ('', '(', ')') if text[index] == '*' else (text[index],)
        for char in choices:
            if char:
                prefix.append(char)
            search(index + 1, balance + (char == '(') - (char == ')'))
            if char:
                prefix.pop()
    search(0, 0)
    return sorted(results)

复杂度

判定O(n)时间O(1)工作空间。枚举有s个星号,粗界O(n·3^s)时间,DFS工作O(n);S个不同答案总字符量O(Sn),排序另计O(S log S·n)。递归深度n。

易错点与追问

容易失分的边界

  • 只检查最终余额忽略负前缀。
  • 星号不允许空却使用本文区间论证。
  • 枚举重复串未去重。
  • 将指数输出写成线性时间。

继续深挖

  • 只要一组合法替换怎么构造?
  • 替换历史数和不同结果数为何不同?
  • 若*不允许空如何加入奇偶约束?
  • 只计答案是否能用DP而不保存字符串?
✓已复制微信号!