题目描述
[1674] 使数组互补的最少操作次数
给你一个长度为 偶数 n 的整数数组 nums 和一个整数 limit 。每一次操作,你可以将 nums 中的任何整数替换为 1 到 limit 之间的另一个整数。
如果对于所有下标 i(下标从 0 开始),nums[i] + nums[n - 1 - i] 都等于同一个数,则数组 nums 是 互补的 。例如,数组 [1,2,3,4] 是互补的,因为对于所有下标 i ,nums[i] + nums[n - 1 - i] = 5 。
返回使数组 互补 的 最少 操作次数。
n == nums.length
2 <= n <= 105
1 <= nums[i] <= limit <= 105
n
是偶数。
Related Topics
- 数组
- 线段树
- 区间覆盖
题目剖析&信息挖掘
先转化成区间覆盖问题,再用相关数据结构去解决。
解题思路
方法一 区间覆盖法
思考过程
- 首先想到的是,基准问题,第一感觉是其中的某一项为基准,但马上得到了否定。
- 不一定是取某一项为基准。
- 举个例子 [1,1,100,100] limit=100,即不是2,也不是200
- 但是可肯定的是 这个标准点肯定是落在 [2, 2*limit],可以缩小到 min(num[0]+num[n-1,…,]num[i]+num[n-1-i]), max(num[0]+num[n-1,…,]num[i]+num[n-1-i])。
- 简单证明一下 假设 当基准项取 x = min(num[0]+num[n-1,…,]num[i]+num[n-1-i])时, 各数字的操作(表示是否改变数字)数为 (o0, o1, …on)
- 现在令x变小,则操作数为(p0, p1, …pn), 可以得到 p[i]+p[n-1-i] >= o[i]+o[n-1-i]。所以在x < min(num[0]+num[n-1,…,]num[i]+num[n-1-i]) 时,不能得到更优的解。
- x>max(num[0]+num[n-1,…,]num[i]+num[n-1-i]) 也同理。
- 有了这个基础,我有了一个方向,就是枚举x去求解可行解,最后取最优解。
- 如果按照普通做法,枚举一个x然后去求解一下各组数据到x需要的操作数,这个复杂度是O(n^2), 对于题目给出的数据模板无法通过。
- 那么有没有办法把这些操作数存储到一个数组A, A[x]代表所有组数据到x的总操作数呢。B[x]代表能到达x的数组组合(判断是否可行解),可以按如下做法。
- 对于某组数据 num[i], num[n-1-i](假设num[i]<num[n-1-i]),
- 我们可以知道,其0次操作能达到的区间就是 [num[i]+num[n-1-i], num[i]+num[n-1-i]]
- 1次操作能达到的区间就是 [num[i]+1,num[i]+num[n-1-i]), (num[i]+num[n-1-i], limit+num[n-1-i]]
- 2次操作能达到的区间就是 [2, num[i]+1), (limit+num[n-1-i], 2*limit]
- 遍历以上区间,分别对A, B进行累加
- 最后遍历B, 如果B[x] == n/2 说明x是一个可行解,在此前提下,取到A[x]的最小值。
- 但是以上方法的复杂度是O(n^2), 不过可以转化到区间覆盖问题进行求解,可以优化到O(nlogn)
分析
-
通过上面的解析,我把题目先往区间覆盖问题进行一下转化
-
有q个区间,每个区间有一个weight属性,求被n个区间覆盖到,并且weihgt总和最小的点。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mKnFatlu-1607084721711)
-
-
这种问题在我所掌握的方法中,可以通过线段树数据结构去解决,总复杂度是 O(nlogn)。
思路
- 基本思路是先求出所有的区间以及其操作数
- 初始化线段树,并添加线段
- 遍历所有的基准x, 并求解到达x的操作数。
- 这里要使用到线段树的延迟机制,把正常操作分摊到insert, query操作中,线段树相关内容可以查询网络资源,后续我也会出一些文章。
type node struct {
l, r int // 代表树结点代表的区间范围
leftChild, rightChild *node
delay int // 延迟标记
totalCount, totalWeight int // 总覆盖数,及总操作数
}
type SegmentTree struct {
nodes []node
root int
}
// 初始化线段树,分配内存大小, 构造树型
func (tree *SegmentTree) Init(l, r int) {
}
// 构造树型
func (tree *SegmentTree) buildNode(l, r, root int) *node {
return nil
}
func (tree *SegmentTree) InsertSegment(l, r, weight int) {
}
func (tree *SegmentTree) insert(l, r, root int) {
}
func (tree *SegmentTree) QueryPoint(x int) (totalCount, totalWeight int) {
return 0, 0
}
func (tree *SegmentTree) query(l, r, root int) *node {
return nil
}
func minMoves(nums []int, limit int) int {
segmentTree := &SegmentTree{}
segmentTree.Init(2, 2*limit) // 初始化线段树,范围是2- 2*limit
/* 构造线段添加到树中*/
n := len(nums)
for i := 0; i < n/2; i++ {
n1, n2 := getSort(nums[i], nums[n-1-i])
segmentTree.InsertSegment(n1+n2, n1+n2, 0) // 本身操作数为0
//- 1次操作能达到的区间就是 [num[i]+1,num[i]+num[n-1-i]), (num[i]+num[n-1-i], limit+num[n-1-i]]
segmentTree.InsertSegment(n1+1, n1+n2-1, 1)
segmentTree.InsertSegment(n1+n2+1, limit+n2, 1)
//- 2次操作能达到的区间就是 [2, num[i]+1), (limit+num[n-1-i], 2*limit]
segmentTree.InsertSegment(2, n1, 2)
segmentTree.InsertSegment(limit+n2+1, 2*limit, 2)
}
minOp := len(nums)
for x := 2; x <= 2*limit; x++ {
totCnt, opCnt := segmentTree.QueryPoint(x)
if totCnt == n/2 && opCnt < minOp {
minOp = opCnt
}
}
return minOp
}
注意
- 大规模数据
知识点
- 数组
- 区间最值
复杂度
- 时间复杂度:O(nlog(n))
- 空间复杂度:O(n)
参考
- https://baike.baidu.com/item/%E7%BA%BF%E6%AE%B5%E6%A0%91/10983506?fr=aladdin 线段树
代码实现
type node struct {
l, r int // 代表树结点代表的区间范围
leftChild, rightChild *node
totalCount, totalWeight int // 总覆盖数,及总操作数
}
type SegmentTree struct {
nodes []node // 事先申请结点,加事内存分配
root int //根结点编号
}
// 初始化线段树,分配内存大小, 构造树型
func (tree *SegmentTree) Init(l, r int) {
tree.nodes = make([]node, (r-l+1)*4)
tree.root = 1 //
tree.buildNode(l, r, tree.root)
}
// 构造树型
func (tree *SegmentTree) buildNode(l, r, root int) *node {
if l > r {
return nil
}
mid := (l + r) >> 1
tree.nodes[root].l, tree.nodes[root].r = l, r
tree.nodes[root].totalCount, tree.nodes[root].totalWeight = 0, 0
if l == r {
return &tree.nodes[root]
}
// 构造左右子树
tree.nodes[root].leftChild = tree.buildNode(l, mid, root<<1)
tree.nodes[root].rightChild = tree.buildNode(mid+1, r, root<<1|1)
return &tree.nodes[root]
}
func (tree *SegmentTree) InsertSegment(l, r, weight int) {
tree.insert(l, r, weight, tree.root)
}
func (tree *SegmentTree) insert(l, r, weight, root int) {
if l > tree.nodes[root].r || r < tree.nodes[root].l {
return
}
if l <= tree.nodes[root].l && tree.nodes[root].r <= r {
tree.nodes[root].totalWeight += weight
tree.nodes[root].totalCount++
return
}
tree.insert(l, r, weight, root<<1)
tree.insert(l, r, weight, root<<1|1)
}
func (tree *SegmentTree) QueryPoint(x int) (totalCount, totalWeight int) {
n := tree.query(x, x, tree.root)
if n != nil {
return n.totalCount, n.totalWeight
}
return 0, 0
}
func (tree *SegmentTree) query(l, r, root int) *node {
if l > tree.nodes[root].r || r < tree.nodes[root].l {
return nil
}
if tree.nodes[root].l == tree.nodes[root].r {
return &tree.nodes[root]
}
tree.pushDown(root)
mid := (tree.nodes[root].l + tree.nodes[root].r) >> 1
if l <= mid {
return tree.query(l, r, root<<1)
}
return tree.query(l, r, root<<1|1)
}
func (tree *SegmentTree) pushDown(root int) {
totCnt, totWeight := tree.nodes[root].totalCount, tree.nodes[root].totalWeight
tree.nodes[root].totalCount, tree.nodes[root].totalWeight = 0, 0
tree.nodes[root<<1].totalCount += totCnt
tree.nodes[root<<1].totalWeight += totWeight
tree.nodes[root<<1|1].totalCount += totCnt
tree.nodes[root<<1|1].totalWeight += totWeight
}
func getSort(a, b int) (int, int) {
if a < b {
return a, b
}
return b, a
}
func minMoves(nums []int, limit int) int {
segmentTree := &SegmentTree{}
segmentTree.Init(2, 2*limit) // 初始化线段树,范围是2- 2*limit
/* 构造线段添加到树中*/
n := len(nums)
for i := 0; i < n/2; i++ {
n1, n2 := getSort(nums[i], nums[n-1-i])
segmentTree.InsertSegment(n1+n2, n1+n2, 0) // 本身操作数为0
//- 1次操作能达到的区间就是 [num[i]+1,num[i]+num[n-1-i]), (num[i]+num[n-1-i], limit+num[n-1-i]]
segmentTree.InsertSegment(n1+1, n1+n2-1, 1)
segmentTree.InsertSegment(n1+n2+1, limit+n2, 1)
//- 2次操作能达到的区间就是 [2, num[i]+1), (limit+num[n-1-i], 2*limit]
segmentTree.InsertSegment(2, n1, 2)
segmentTree.InsertSegment(limit+n2+1, 2*limit, 2)
}
minOp := len(nums)
for x := 2; x <= 2*limit; x++ {
totCnt, opCnt := segmentTree.QueryPoint(x)
if totCnt == n/2 && opCnt < minOp {
minOp = opCnt
}
}
return minOp
}
欢迎添加公众号:彬彬魔坊,更多数据结构,象棋,台球,魔方,计算机相关知识