🤖 AI总结
主题
使用Go语言实现限制有序数组元素出现次数不超过k次的算法。
摘要
文章介绍了一种在有序数组中限制每个元素最多出现k次的算法,并提供了Go、Python、C++的代码实现。
关键信息
- 1 利用有序数组特性,通过比较当前元素与结果区倒数第k个元素判断是否保留。
- 2 提供Go、Python、C++三种语言实现,时间复杂度O(n),空间复杂度O(1)。
- 3 文章末尾推广了AI知识分享公众号。
2026-09-01:限制有序数组中的元素出现次数。用go语言,给定一个已经按从小到大排好的整数列表 nums,以及一个整数 k。现在需要生成一个新列表,这个列表要满足:原列表中的每个数值,在新列表中最多只能重复出现 k 次,并且所有元素的先后顺序必须和原列表中的顺序完全一致。最后返回这个新列表。
1 <= nums.length <= 100。
1 <= nums[i] <= 100。
nums 按非递减顺序排序。
1 <= k <= nums.length。
输入: nums = [1,1,1,2,2,3], k = 2。
输出: [1,1,2,2,3]。
解释:
每个元素最多可以出现 2 次。
元素 1 出现了 3 次,因此只保留其中 2 次。
元素 2 出现了 2 次,因此全部保留。
元素 3 出现了 1 次,因此保留。
因此,结果数组为 [1, 1, 2, 2, 3]。
题目来自力扣3940。
算法步骤详细描述
1.输入与初始设定
给定一个已按非递减顺序排好的整数数组nums,以及一个正整数k(表示每个不同元素最多允许出现的次数)。数组长度记为n。
2.前 k 个元素默认保留
由于数组已经排序,任何元素在数组中都是连续出现的。前k个元素(索引0到k-1)中,同一个值的出现次数不可能超过k(因为总共才k个位置),因此它们一定满足“最多出现k次”的条件,可以直接纳入最终结果。
为此,定义一个指针writePos(或称为栈大小),初始值为k,表示当前已保留的有效元素个数,也即下一个可写入位置。
3.从第 k 个元素开始逐个检查
从索引i = k开始,依次遍历数组的剩余元素(直到末尾)。对于每个元素nums[i],需要判断是否应该保留。
4.判断是否保留当前元素的依据
想要保留nums[i],必须确保该元素在已保留的结果中出现的次数还没有达到k次。
因为数组是有序的,相同的元素必定连续出现。在已经保留的结果中,如果当前元素已经出现了k次,那么这k个相同元素必然位于结果区的末尾(因为有序)。结果区的末尾部分就是索引从writePos - k到writePos - 1的位置,其中倒数第k个就是索引writePos - k。
因此,只需比较当前元素nums[i]与结果区中倒数第k个元素(即nums[writePos - k])是否相等:
•若不相等:说明当前元素在结果区中的出现次数尚未达到k次(因为如果已经达到,那么倒数第k个元素必然等于当前元素)。此时该元素可以保留,将其写入nums[writePos](覆盖原值),然后将writePos加 1。
•若相等:说明当前元素已经在结果区中出现了k次,再添加就会超过限制,因此跳过该元素,不进行写入,继续处理下一个元素。
5.原地更新与覆盖的安全性
由于writePos总是小于或等于当前遍历的索引i(因为要么写入并增加,要么跳过,所以writePos不会超过i),因此写入操作不会覆盖尚未检查到的未来元素,保证了算法的正确性。
6.遍历结束
当循环结束后,所有原数组元素都被检查完毕。此时,数组的前writePos个元素(即nums[0:writePos])就是满足条件的结果序列,它们保持了原顺序,且每个不同元素最多出现k次。
7.返回结果
返回切片nums[:writePos]作为最终的新列表(此处返回的是原数组的视图,不复制数据,符合原代码做法)。
复杂度分析
•时间复杂度:只对数组进行了一次线性扫描,从索引k到n-1,每个元素执行常数次比较和可能的赋值操作,因此总时间复杂度为O(n),其中n是数组长度。
•额外空间复杂度:除了几个整型变量(如writePos、循环变量i)外,没有使用任何额外数组或数据结构,所有修改都在原数组上完成。返回的切片只是原数组的一部分引用,不产生新的数据副本。因此额外空间复杂度为O(1)(常数级别)。
Go完整代码如下:
package main
import (
"fmt"
)
func limitOccurrences(nums []int, k int) []int {
stackSize := k // 栈的大小,前 k 个元素默认保留
for i := k; i < len(nums); i++ {
if nums[i] != nums[stackSize-k] { // 和栈的倒数第 k 个数比较
nums[stackSize] = nums[i] // 入栈
stackSize++
}
}
return nums[:stackSize]
}func main() {
nums := []int{1, 1, 1, 2, 2, 3}
k := 2
result := limitOccurrences(nums, k)
fmt.Println(result)
}
![]()
Python完整代码如下:
# -*-coding:utf-8-*-
def limit_occurrences(nums, k):
"""
原地修改 nums,使每个不同元素最多出现 k 次,并返回新列表(切片)。
保持原数组的相对顺序,要求 nums 已按升序排列。
"""
if k <= 0:
return [] # 无效 k,返回空列表(可按需调整)
if len(nums) <= k:
return nums[:] # 长度不足,无需修改,返回副本
stack_size = k # 结果区域大小,初始保留前 k 个元素
for i in range(k, len(nums)):
# 比较当前元素与结果区中倒数第 k 个元素
if nums[i] != nums[stack_size - k]:
nums[stack_size] = nums[i]
stack_size += 1
return nums[:stack_size]
def main():
nums = [1, 1, 1, 2, 2, 3]
k = 2
result = limit_occurrences(nums, k)
print(result)if __name__ == "__main__":
main()
![]()
C++完整代码如下:
/**
* 原地修改 nums,使每个不同元素最多出现 k 次,
* 返回一个新的 vector,包含处理后的有效元素。
* 要求 nums 已按升序排列。
*/
std::vector limitOccurrences(std::vector& nums, int k) {
// 处理 k <= 0 的情况(原 Go 未处理,这里增加防御)
if (k <= 0) {
return {};
}
int n = static_cast(nums.size());
if (n <= k) {
// 长度不足,直接返回原数组副本
return nums;
}
int stackSize = k; // 结果区域大小,前 k 个元素默认保留
for (int i = k; i < n; ++i) {
// 比较当前元素与结果区中倒数第 k 个元素
if (nums[i] != nums[stackSize - k]) {
nums[stackSize] = nums[i]; // 入栈(原地覆盖)
++stackSize;
}
}
// 返回有效部分构成的 vector
return std::vector(nums.begin(), nums.begin() + stackSize);
}
int main() {
std::vector nums = {1, 1, 1, 2, 2, 3};
int k = 2;
std::vector result = limitOccurrences(nums, k);
// 输出结果
for (int x : result) {
std::cout << x << " ";
}
std::cout << std::endl;return 0;
}
![]()
我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。