leetcode-1674-使数组互补的最少操作次数-线段树

题目描述

[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
}

欢迎添加公众号:彬彬魔坊,更多数据结构,象棋,台球,魔方,计算机相关知识
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值