LC 1505. 最多 K 次交换相邻数位后得到的最小整数
https://leetcode-cn.com/problems/minimum-possible-integer-after-at-most-k-adjacent-swaps-on-digits/
这题目的一些关键难点我都考虑到了,但是在实现步骤上出现了差错。
- 首先这题需要使用贪心算法,尽可能地选择靠前的小的数字前移
- 但是每次必须重新选择,而不能先将0全部前移,然后将1全部前移,如此下去
- 而应该先将一个0前移之后看看下一个0是否可以前移到最开始,否则去测试1是否可以前移到最开始
- 这个算法的难点是,如果 `nums[i]` 上的数字前移之后,那么所有 `nums[j] (j > i)` 的数字前移的cost都要减1
所以这个算法的难点是,我们要维护一个数据结构,当前移 `nums[j]` 这个数字的时候,知道已经有多少个数字(比如k)是 `nums[i] (i<j)` 的。那么这次移动 `nums[j]` 这个数字的话,cost就是 `j-k`.
- 简单的话我们可以使用一个排序数组+二分搜索,更新时间复杂度是O(n), 查询时间复杂度是O(lgn) [这种方式提交可以过]
- 或者是手写二叉树,记录左子树一共有多少个节点,更新和查询时间复杂度都是O(lgn), 但是手写麻烦还有有旋转。
其实这个问题完全可以转换成为求解前缀和
- 一开始有个数组 `moved[0..n-1]` 内容都是0
- 如果 `nums[i]` 移动到最开始的话,那么 `moved[i]=1`
- 对于求解 `# of nums[i] (i<j)` 的话,那么其实就是求解 `sum(moved[0..j-1])`
如果将这个问题转换成为前缀和的话,那么又会多了两种数据结构:区间树和Fenwick树,其实是一个思想,但是Fenwick写起来更简单。我之前写过一篇 BIT/Fenwick树 的文章。
代码如下,二分搜索或者是Fenwick可以作为一个抽象策略存在:
class Solution: class BIT: def __init__(self, n): self.n = n self.values = [0] * (n + 1) def update(self, x): x += 1 n = self.n while x <= n: self.values[x] += 1 x += (x & -x) def query(self, x): x += 1 ans = 0 while x > 0: ans += self.values[x] x -= (x & -x) return ans class BS: def __init__(self, n): self.bs = [] def update(self, x): import bisect bisect.insort(self.bs, x) def query(self, x): import bisect return bisect.bisect(self.bs, x) def minInteger(self, num: str, k: int) -> str: qs = [[] for _ in range(10)] buf = [] for i in reversed(range(len(num))): c = int(num[i]) qs[c].append(i) BSClass = self.BIT bs = BSClass(len(num)) while k and len(buf) < len(num): ok = False for i in range(10): if not qs[i]: continue q = qs[i][-1] j = bs.query(q) # print(q, j) if (q - j) <= k: qs[i].pop() bs.update(q) k -= (q - j) buf.append(q) ok = True break if not ok: break used = set(buf) ans = [] for q in buf: ans.append(num[q]) for i in range(len(num)): if i in used: continue ans.append(num[i]) ans = ''.join(ans) return ans