zoukankan      html  css  js  c++  java
  • leetcode#1674. 使数组互补的最少操作次数(差分数组)

    1. 使数组互补的最少操作次数

    给你一个长度为 偶数 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 。

    返回使数组 互补 的 最少 操作次数。

    示例 1:

    输入:nums = [1,2,4,3], limit = 4
    输出:1
    解释:经过 1 次操作,你可以将数组 nums 变成 [1,2,2,3](加粗元素是变更的数字):
    nums[0] + nums[3] = 1 + 3 = 4.
    nums[1] + nums[2] = 2 + 2 = 4.
    nums[2] + nums[1] = 2 + 2 = 4.
    nums[3] + nums[0] = 3 + 1 = 4.
    对于每个 i ,nums[i] + nums[n-1-i] = 4 ,所以 nums 是互补的。
    示例 2:

    输入:nums = [1,2,2,1], limit = 2
    输出:2
    解释:经过 2 次操作,你可以将数组 nums 变成 [2,2,2,2] 。你不能将任何数字变更为 3 ,因为 3 > limit 。
    示例 3:

    输入:nums = [1,2,1,2], limit = 2
    输出:0
    解释:nums 已经是互补的。

    提示:

    n == nums.length
    2 <= n <= 105
    1 <= nums[i] <= limit <= 105
    n 是偶数。

    https://leetcode-cn.com/problems/minimum-moves-to-make-array-complementary/

    思路:
    接下来我们举几个例子寻找一下规律:
    假设数组是1,3,4,2。limit = 5,则第一个数对为(1,2)
    我们找出每一个数对的最大值max和最小值min。

    如图中数对,将该数对和转化成3所需要的操作次数显然是0,接下来还要讨论转化次数为1和转化次数为2的情况。
    显然转化1次能取到的最小值是min + 1,能取到的最大值是max + limit。那么在这范围之外的就是需要转化次数2次。
    所以对于每一对数对:分如下几种情况

    在[2, min]这个区间,arr[i] += 2;
    在[min + 1, min + max]区间,arr[i] += 1;
    在min + max上,arr[i] += 0;
    在[min + max + 1, max + limit]区间上,arr[i] += 1;
    在[max + limit + 1, limit + limit]区间上,arr[i] += 2;

    具体操作

    • 先将 [2, 2 * limit] 的范围需要的操作数 + 2;

    • 之后,将 [1 + min(A, B), limit + max(A, B)] 的范围需要的操作数 - 1(即 2 - 1 = 1,操作 1 次);

    • 之后,将 [A + B] 位置的值再 -1(即 1 - 1 = 0,操作 0 次)。

    仔细观察,我们发现,我们只需要作区间更新,和单点查询。

    对于这个需求,有一种非常常规的”数据结构“,叫差分数组,完全满足需求,并且编程极其简单,整体可以在 O(n) 的时间解决。

    打上引号,是因为差分数组就是一个数组而已

    关于差分数组

    简单来说,差分数组 diff[i],存储的是 res[i] - res[i - 1];而差分数组 diff[0...i] 的和,就是 res[i] 的值。
    大家可以用一个小数据试验一下,很好理解。

    如果我们想给 [l, r] 的区间加上一个数字 a, 只需要 diff[l] += a,diff[r + 1] -= a。
    这样做,diff[0...i] 的和,就是更新后 res[i] 的值。
    依然是,大家可以用一个小数据试验一下,其实很好理解。

    这里不赘述,更严谨的分析看上面这个文档:https://oi-wiki.org/basic/prefix-sum/#_6

    代码

    func minMoves(nums []int, limit int) int {
    	n := len(nums)
    
    	// 差分数组, diff[0...x] 的和表示最终互补的数字和为 x,需要的操作数
    	// 因为差分数组的计算需要更新 r + 1,所以数组的总大小在 limit * 2 + 1 的基础上再 + 1
    	diff := make([]int, 2*limit+2)
    
    	for i := 0; i < n/2; i++ {
    		a, b := nums[i], nums[n-1-i]
    
    		// [2, 2 * limit] 范围 + 2
    		l, r := 2, 2*limit
    		diff[l] += 2
    		diff[r+1] -= 2
    
    		// [1 + min(A, B), limit + max(A, B)] 范围 -1
    		l, r = min(a, b)+1, max(a, b)+limit
    		diff[l] -= 1
    		diff[r+1] += 1
    
    		// [A + B] 再 -1  
    		l, r = a+b, a+b
    		diff[l] -= 1
    		diff[r+1] += 1
    	}
    
    	// 依次求和,得到 最终互补的数字和 i 的时候,需要的操作数 sum
    	// 取最小值
    	sum, ret := 0, n
    	for i := 2; i <= 2*limit; i++ {
    		sum += diff[i]
    		if sum < ret {
    			ret = sum
    		}
    	}
    	return ret
    }
    
    func min(a, b int) int {
    	if a < b {
    		return a
    	}
    	return b
    }
    
    func max(a, b int) int {
    	if a > b {
    		return a
    	}
    	return b
    }
    
    
  • 相关阅读:
    CSS定位 position
    时间日期类--显示时间
    【HDOJ】4775 Infinite Go
    【HDOJ】4297 One and One Story
    【HDOJ】4056 Draw a Mess
    【HDOJ】2242 考研路茫茫——空调教室
    【HDOJ】3828 A + B problem
    【HDOJ】3386 Final Kichiku “Lanlanshu”
    【HDOJ】1648 Keywords
    【HDOJ】1699 The comment in cpp
  • 原文地址:https://www.cnblogs.com/iQXQZX/p/14087909.html
Copyright © 2011-2022 走看看