leetcode-899. 有序队列

111 阅读3分钟

题目

899. 有序队列

难度困难

给定一个字符串 s 和一个整数 k 。你可以从 s 的前 k 个字母中选择一个,并把它加到字符串的末尾。

返回 在应用上述步骤的任意数量的移动后,字典上最小的字符串

示例 1:

输入: s = "cba", k = 1
输出: "acb"

示例 2:

输入: s = "baaca", k = 3
输出: "aaabc"

思路

这一看是个 hard 题而且题面很短,就比较吓人,总体上看有点像脑筋急转弯的类型,有思路之后会觉得“就这?”,没思路的时候又感觉没有什么思考的方向。

这一道题可以通过分情况讨论的方法来解决,关键点是要能发现当 k>1 时,进行任意步题干的给定操作后可以得到原字符串的任意一种排列结果

  • k = 1 时 此时没有选择字母的空间,能做的操作就只有将首字母直接移动到尾部,当字符串 s 的长度为 n 时,结果字符串一共就只有 n 种排列,所以可以直接暴力,把每种可能都计算出来,然后返回其中字典序最小的字符串即可。

  • k > 1 时 因为进行任意步题干的给定操作后可以得到原字符串的任意一种排列结果,所以我们直接对整个字符串做全排序,得到字典序最小的那种情况返回即可

下面将对 “为什么当 k > 1 时,进行任意步题干的给定操作后可以得到原字符串的任意一种排列结果?” 这个问题进行讨论

不妨设 k = 2, 原字符串长度为 n,由字符x0x1x_0,x_1xn1x_{n-1}这些字符以任意顺序组合而成,而我们想要得到的任意一个排列结果为 x0x1x2...xn2xn1x_0 x_1 x_2...x_{n-2} x_{n-1}

下面给出得到目标字符串的方法

  1. xn1x_{n-1} 移动至字符串头部
    一直将首字符移动到尾部,直到 xn1x_{n-1} 成为首字符

  2. xn2xn1x_{n-2}x_{n-1} 移动至字符串头部
    一直将首字符移动到尾部,直到 xn2x_{n−2} 成为首字符,然后一直将第二个字符移动到尾部,直到 xn1x_{n−1} 出现,成为第二个字符

  3. xn3xn2xn1x_{n-3}x_{n-2}x_{n-1} 移动至字符串头部
    其实方法和第二步完全一致,一直将首字符移动到尾部,直到 xn3x_{n−3} 成为首字符,然后一直将第二个字符移动到尾部,直到 xn2x_{n−2} 出现,成为第二个字符
    此时,因为第二步已经让 xn2xn1x_{n-2}x_{n-1} 相邻并且这个状态没有改变,所以在 xn3xn2x_{n-3}x_{n-2}后紧跟的一定是xn1x_{n-1},让 xn3xn2xn1x_{n-3}x_{n-2}x_{n-1} 移动至字符串头部的任务完成

  4. 依据同样的道理,就可以将 xn4xn3xn2xn1x_{n-4}x_{n-3}x_{n-2}x_{n-1} ... 等移动至字符串头部,直到字符串头部为 x0x1x2...xn2xn1x_0 x_1 x_2...x_{n-2} x_{n-1},此时字符串就成功变成了目标字符串

由此可以证明 k=2 时结论成立。
当 k > 2 时,完全可以限制只使用前两个字符进行指定操作,将问题转换为 k=2 的情况。

利用这个归纳推理,可以证明出“进行任意步题干的给定操作后可以得到原字符串的任意一种排列结果”这个结论在 k>1 时都成立。

实现

func orderlyQueue(s string, k int) string {
    if k>1 {
        b := []byte(s)
        sort.Slice(b, func(i, j int) bool {
            return b[i] < b[j]
        })
        return string(b)
    }

    ret, tmp := s, s
    for i:=0 ; i<len(s) ; i++ {
        tmp = tmp[1:] + tmp[:1]
        if ret > tmp {
            ret = tmp
        }
    }
    return ret
}