classSolution: defcountOperationsToEmptyArray(self, nums: List[int]) -> int: ans = n = len(nums) # 先把 n 计入答案 t = BIT(n + 1) # 下标从 1 开始 pre = 1# 上一个最小值的位置,初始为 1 for k, i inenumerate(sorted(range(n), key=lambda i: nums[i])): i += 1# 下标从 1 开始 if i >= pre: # 从 pre 移动到 i,跳过已经删除的数 ans += i - pre - t.query(pre, i) else: # 从 pre 移动到 n,再从 1 移动到 i,跳过已经删除的数 ans += i - pre + n - k + t.query(i, pre - 1) t.inc(i) # 删除 i pre = i return ans
# 树状数组模板 classBIT: def__init__(self, n): self.tree = [0] * n
# 将下标 i 上的数加一 definc(self, i: int) -> None: while i < len(self.tree): self.tree[i] += 1 i += i & -i
# 返回闭区间 [1, i] 的元素和 defsum(self, i: int) -> int: res = 0 while i > 0: res += self.tree[i] i &= i - 1 return res
longans= n; // 先把 n 计入答案 vart=newBIT(n + 1); // 下标从 1 开始 intpre=1; // 上一个最小值的位置,初始为 1 for (intk=0; k < n; ++k) { inti= id[k] + 1; // 下标从 1 开始 if (i >= pre) // 从 pre 移动到 i,跳过已经删除的数 ans += i - pre - t.query(pre, i); else// 从 pre 移动到 n,再从 1 移动到 i,跳过已经删除的数 ans += n - pre + i - k + t.query(i, pre - 1); t.inc(i); // 删除 i pre = i; } return ans; } }
// 树状数组模板 classBIT { privatefinalint[] tree;
publicBIT(int n) { tree = newint[n]; }
// 将下标 i 上的数加一 publicvoidinc(int i) { while (i < tree.length) { ++tree[i]; i += i & -i; } }
// 返回闭区间 [1, i] 的元素和 publicintsum(int x) { intres=0; while (x > 0) { res += tree[x]; x &= x - 1; } return res; }
classSolution { public: longlongcountOperationsToEmptyArray(vector<int> &nums){ int n = nums.size(), id[n]; iota(id, id + n, 0); sort(id, id + n, [&](int i, int j) { return nums[i] < nums[j]; });
longlong ans = n; // 先把 n 计入答案 BIT t(n + 1); // 下标从 1 开始 int pre = 1; // 上一个最小值的位置,初始为 1 for (int k = 0; k < n; ++k) { int i = id[k] + 1; // 下标从 1 开始 if (i >= pre) // 从 pre 移动到 i,跳过已经删除的数 ans += i - pre - t.query(pre, i); else// 从 pre 移动到 n,再从 1 移动到 i,跳过已经删除的数 ans += n - pre + i - k + t.query(i, pre - 1); t.inc(i); // 删除 i pre = i; } return ans; } };
// 将下标 i 上的数加一 func(t BIT) inc(i int) { for ; i < len(t); i += i & -i { t[i]++ } }
// 返回闭区间 [1, i] 的元素和 func(t BIT) sum(i int) (res int) { for ; i > 0; i &= i - 1 { res += t[i] } return }
// 返回闭区间 [left, right] 的元素和 func(t BIT) query(left, right int) int { return t.sum(right) - t.sum(left-1) }
funccountOperationsToEmptyArray(nums []int)int64 { n := len(nums) id := make([]int, n) for i := range id { id[i] = i } sort.Slice(id, func(i, j int)bool { return nums[id[i]] < nums[id[j]] })
ans := n // 先把 n 计入答案 t := make(BIT, n+1) // 下标从 1 开始 pre := 1// 上一个最小值的位置,初始为 1 for k, i := range id { i++ // 下标从 1 开始 if i >= pre { // 从 pre 移动到 i,跳过已经删除的数 ans += i - pre - t.query(pre, i) } else { // 从 pre 移动到 n,再从 1 移动到 i,跳过已经删除的数 ans += n - pre + i - k + t.query(i, pre-1) } t.inc(i) // 删除 i pre = i } returnint64(ans) }
复杂度分析
时间复杂度:\mathcal{O}(n\log n),其中 n 为 nums 的长度。
空间复杂度:\mathcal{O}(n)。
方法二:进一步挖掘性质
仍然先把数组长度 n 计入答案,后面只统计移动次数。在统计移动次数时,遇到要删除的元素,相当于可以免费向后移动一步(因为删除操作已经计入答案)。试想一下,如果数组是单调递增的,就没有任何额外的移动次数。
从上面的例子中可以发现,如果第 k 次要删除的元素在第 k-1 次要删除的元素的左侧,那么必须多走一整圈,移动次数为 n-k。累加,即为总的移动次数。
最后如果剩下若干递增元素,直接一股脑删除,无需任何移动次数。
[sol2-Python3]
1 2 3 4 5 6 7 8
classSolution: defcountOperationsToEmptyArray(self, nums: List[int]) -> int: ans = n = len(nums) id = sorted(range(n), key=lambda x: nums[x]) for k, (pre, i) inenumerate(pairwise(id), 1): if i < pre: # 必须多走一整圈 ans += n - k # 减去前面删除的元素个数 return ans
longans= n; // 先把 n 计入答案 for (intk=1; k < n; ++k) if (id[k] < id[k - 1]) // 必须多走一整圈 ans += n - k; // 减去前面删除的元素个数 return ans; } }
[sol2-C++]
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution { public: longlongcountOperationsToEmptyArray(vector<int> &nums){ int n = nums.size(), id[n]; iota(id, id + n, 0); sort(id, id + n, [&](int i, int j) { return nums[i] < nums[j]; }); longlong ans = n; for (int k = 1; k < n; ++k) if (id[k] < id[k - 1]) // 必须多走一整圈 ans += n - k; // 减去前面删除的元素个数 return ans; } };
[sol2-Go]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
funccountOperationsToEmptyArray(nums []int)int64 { n := len(nums) id := make([]int, n) for i := range id { id[i] = i } sort.Slice(id, func(i, j int)bool { return nums[id[i]] < nums[id[j]] })
ans := n for k := 1; k < n; k++ { if id[k] < id[k-1] { // 必须多走一整圈 ans += n - k // 减去前面删除的元素个数 } } returnint64(ans) }
复杂度分析
时间复杂度:\mathcal{O}(n\log n),其中 n 为 nums 的长度。瓶颈在排序上。