每日一题-和为零的 N 个不同整数🟢
给你一个整数 n
,请你返回 任意 一个由 n
个 各不相同 的整数组成的数组,并且这 n
个数相加和为 0
。
示例 1:
输入:n = 5 输出:[-7,-1,1,3,4] 解释:这些数组也是正确的 [-5,-1,1,2,3],[-3,-1,2,-2,4]。
示例 2:
输入:n = 3 输出:[-1,0,1]
示例 3:
输入:n = 1 输出:[0]
提示:
1 <= n <= 1000
给你一个整数 n
,请你返回 任意 一个由 n
个 各不相同 的整数组成的数组,并且这 n
个数相加和为 0
。
示例 1:
输入:n = 5 输出:[-7,-1,1,3,4] 解释:这些数组也是正确的 [-5,-1,1,2,3],[-3,-1,2,-2,4]。
示例 2:
输入:n = 3 输出:[-1,0,1]
示例 3:
输入:n = 1 输出:[0]
提示:
1 <= n <= 1000
由于 $x + (-x) = 0$ 恒成立,可以把 $1$ 和 $-1$,$2$ 和 $-2$,$3$ 和 $-3$ 等成对添加到答案中。如果 $n$ 是奇数,就再加个 $0$。
方便编程实现的一种构造方案如下。其中 $m = \left\lfloor\dfrac{n}{2}\right\rfloor$。
当 $n$ 为偶数时:
$$
[1,2,\dots,m-1,m,-1,-2,\dots,-(m-1),-m]
$$
当 $n$ 为奇数时:
$$
[1,2,\dots,m-1,m,-1,-2,\dots,-(m-1),-m,0]
$$
###py
class Solution:
def sumZero(self, n: int) -> List[int]:
a = [0] * n
m = n // 2
for i in range(m):
a[i] = i + 1
a[i + m] = -i - 1
return a
###py
class Solution:
def sumZero(self, n: int) -> List[int]:
m = n // 2
if n % 2:
return list(range(-m, m + 1))
return list(range(-m, 0)) + list(range(1, m + 1))
###java
class Solution {
public int[] sumZero(int n) {
int[] a = new int[n];
int m = n / 2;
for (int i = 0; i < m; i++) {
a[i] = i + 1;
a[i + m] = -i - 1;
}
return a;
}
}
###cpp
class Solution {
public:
vector<int> sumZero(int n) {
vector<int> a(n);
int m = n / 2;
for (int i = 0; i < m; i++) {
a[i] = i + 1;
a[i + m] = -i - 1;
}
return a;
}
};
###c
int* sumZero(int n, int* returnSize) {
int* a = calloc(n, sizeof(int));
int m = n / 2;
for (int i = 0; i < m; i++) {
a[i] = i + 1;
a[i + m] = -i - 1;
}
*returnSize = n;
return a;
}
###go
func sumZero(n int) []int {
a := make([]int, n)
m := n / 2
for i := range m {
a[i] = i + 1
a[i+m] = -i - 1
}
return a
}
###js
var sumZero = function(n) {
const a = Array(n).fill(0);
const m = Math.floor(n / 2);
for (let i = 0; i < m; i++) {
a[i] = i + 1;
a[i + m] = -i - 1;
}
return a;
};
###rust
impl Solution {
pub fn sum_zero(n: i32) -> Vec<i32> {
let n = n as usize;
let mut a = vec![0; n];
let m = n / 2;
for i in 0..m {
a[i] = i as i32 + 1;
a[i + m] = -a[i];
}
a
}
}
见下面贪心与思维题单的「六、构造题」。
欢迎关注 B站@灵茶山艾府
Hi 大家好,我是张小猪。欢迎来到『宝宝也能看懂』系列之 leetcode 周赛题解。
这里是第 169 期的第 1 题,也是题目列表中的第 1304 题 -- 『和为零的N个唯一整数』
给你一个整数 n
,请你返回 任意 一个由 n
个 各不相同 的整数组成的数组,并且这 n
个数相加和为 0
。
示例 1:
###shell
输入:n = 5
输出:[-7,-1,1,3,4]
解释:这些数组也是正确的 [-5,-1,1,2,3],[-3,-1,2,-2,4]。
示例 2:
###shell
输入:n = 3
输出:[-1,0,1]
示例 3:
###shell
输入:n = 1
输出:[0]
提示:
1 <= n <= 1000
EASY
题目内容很简单,返回一个符合要求的数组即可,要求包含几点:
n
看完要求之后,我第一反应就是,一正一负不就正好是 0 咯。
基于以上思路,我们可以得到一个直接的解题方案。需要注意的是,对于奇数来说,再额外补充一个 0 即可。
我这里的代码额外做了一点小优化,即通过一个定长的数组来避免数组自动伸缩带来的开销。
###js
const sumZero = n => {
const ret = new Int16Array(n);
for (let i = 1; i <= Math.floor(n / 2); ++i) {
ret[i - 1] = i;
ret[n - i] = -i;
}
return ret;
};
我们回看一下题目的限制条件,n
的取值范围是 [1,1000]
,全部加在一起求和是 500500,是一个安全的 int32 整数。于是一个方案孕育而生,我们直接添加 n - 1
个连续的数字,然后把它们求和的负数放进去即可。
###js
const sumZero = n => {
const ret = new Int32Array(n);
for (let i = 1; i < n; ++i) {
ret[i] = i;
}
ret[0] = -((1 + n) * n / 2 - n);
return ret;
};
我们尝试写几组可能的解来看看:
###shell
n = 1, [0]
n = 2, [-1, 1]
n = 3, [-2, 0, 2]
n = 4, [-3, -1, 1, 3]
n = 5, [-4, -2, 0, 2, 4]
有没有觉得这是一个很熟悉的数组,反正我是印象在大学 C++ 教材里有题目要求输出这个数组,哈哈哈哈。
对于这个数组,我们尝试找一下规律应该就能发现,每个数字其实是符合这个公式的:
###shell
ret[i] = i * 2 - n + 1;
有了公式那么就直接写进代码即可。
###js
const sumZero = n => {
const ret = new Int16Array(n);
for (let i = 0; i < n; ++i) {
ret[i] = i * 2 - n + 1;
}
return ret;
};
第 169 期周赛的第一题,想得到 Accepted 是很简单的。所以这里尝试给出了几种不同的思路方向。当然我相信大家还会有更有意思的思路。>.<
show the code
###java
public int[] sumZero(int n) {
int[] ans = new int[n];
int index = 0;
for (int i = 1; i <= n / 2; i++) {
ans[index++] = -i;
ans[index++] = i;
}
return ans;
}
根据题目描述,我们不妨假设把一个元素 $x$ 变为 $0$ 需要的最小操作次数为 $p$,那么 $p$ 是满足 $4^p \gt x$ 的最小整数。
我们知道了一个元素的最小操作次数,那么对于一个范围 $[l, r]$,我们假设 $[l, r]$ 范围内所有元素的最小操作次数之和为 $s$,最大操作次数为元素 $r$ 的操作次数 $mx$,那么 $[l, r]$ 范围内所有元素变为 $0$ 的最少操作次数为 $\max(\lceil s / 2 \rceil, mx)$。
我们定义一个函数 $f(x)$,表示范围 $[1, x]$ 内所有元素的最小操作次数之和,那么对于每个查询 $[l, r]$,我们可以计算出 $s = f(r) - f(l - 1)$ 和 $mx = f(r) - f(r - 1)$,从而得到答案。
###python
class Solution:
def minOperations(self, queries: List[List[int]]) -> int:
def f(x: int) -> int:
res = 0
p = i = 1
while p <= x:
cnt = min(p * 4 - 1, x) - p + 1
res += cnt * i
i += 1
p *= 4
return res
ans = 0
for l, r in queries:
s = f(r) - f(l - 1)
mx = f(r) - f(r - 1)
ans += max((s + 1) // 2, mx)
return ans
###java
class Solution {
public long minOperations(int[][] queries) {
long ans = 0;
for (int[] q : queries) {
int l = q[0], r = q[1];
long s = f(r) - f(l - 1);
long mx = f(r) - f(r - 1);
ans += Math.max((s + 1) / 2, mx);
}
return ans;
}
private long f(long x) {
long res = 0;
long p = 1;
int i = 1;
while (p <= x) {
long cnt = Math.min(p * 4 - 1, x) - p + 1;
res += cnt * i;
i++;
p *= 4;
}
return res;
}
}
###cpp
class Solution {
public:
long long minOperations(vector<vector<int>>& queries) {
auto f = [&](long long x) {
long long res = 0;
long long p = 1;
int i = 1;
while (p <= x) {
long long cnt = min(p * 4 - 1, x) - p + 1;
res += cnt * i;
i++;
p *= 4;
}
return res;
};
long long ans = 0;
for (auto& q : queries) {
int l = q[0], r = q[1];
long long s = f(r) - f(l - 1);
long long mx = f(r) - f(r - 1);
ans += max((s + 1) / 2, mx);
}
return ans;
}
};
###go
func minOperations(queries [][]int) (ans int64) {
f := func(x int64) (res int64) {
var p int64 = 1
i := int64(1)
for p <= x {
cnt := min(p*4-1, x) - p + 1
res += cnt * i
i++
p *= 4
}
return
}
for _, q := range queries {
l, r := int64(q[0]), int64(q[1])
s := f(r) - f(l-1)
mx := f(r) - f(r-1)
ans += max((s+1)/2, mx)
}
return
}
###ts
function minOperations(queries: number[][]): number {
const f = (x: number): number => {
let res = 0;
let p = 1;
let i = 1;
while (p <= x) {
const cnt = Math.min(p * 4 - 1, x) - p + 1;
res += cnt * i;
i++;
p *= 4;
}
return res;
};
let ans = 0;
for (const [l, r] of queries) {
const s = f(r) - f(l - 1);
const mx = f(r) - f(r - 1);
ans += Math.max(Math.ceil(s / 2), mx);
}
return ans;
}
###rust
impl Solution {
pub fn min_operations(queries: Vec<Vec<i32>>) -> i64 {
let f = |x: i64| -> i64 {
let mut res: i64 = 0;
let mut p: i64 = 1;
let mut i: i64 = 1;
while p <= x {
let cnt = std::cmp::min(p * 4 - 1, x) - p + 1;
res += cnt * i;
i += 1;
p *= 4;
}
res
};
let mut ans: i64 = 0;
for q in queries {
let l = q[0] as i64;
let r = q[1] as i64;
let s = f(r) - f(l - 1);
let mx = f(r) - f(r - 1);
ans += std::cmp::max((s + 1) / 2, mx);
}
ans
}
}
时间复杂度 $O(q \log M)$,其中 $q$ 是查询次数,而 $M$ 是查询范围的最大值。空间复杂度 $O(1)$。
有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
给你一个二维数组 queries
,其中 queries[i]
形式为 [l, r]
。每个 queries[i]
表示了一个元素范围从 l
到 r
(包括 l 和 r )的整数数组 nums
。
在一次操作中,你可以:
a
和 b
。floor(a / 4)
和 floor(b / 4)
。你的任务是确定对于每个查询,将数组中的所有元素都变为零的 最少 操作次数。返回所有查询结果的总和。
示例 1:
输入: queries = [[1,2],[2,4]]
输出: 3
解释:
对于 queries[0]
:
nums = [1, 2]
。nums[0]
和 nums[1]
。数组变为 [0, 0]
。对于 queries[1]
:
nums = [2, 3, 4]
。nums[0]
和 nums[2]
。数组变为 [0, 3, 1]
。nums[1]
和 nums[2]
。数组变为 [0, 0, 0]
。输出为 1 + 2 = 3
。
示例 2:
输入: queries = [[2,6]]
输出: 4
解释:
对于 queries[0]
:
nums = [2, 3, 4, 5, 6]
。nums[0]
和 nums[3]
。数组变为 [0, 3, 4, 1, 6]
。nums[2]
和 nums[4]
。数组变为 [0, 3, 1, 1, 1]
。nums[1]
和 nums[2]
。数组变为 [0, 0, 0, 1, 1]
。nums[3]
和 nums[4]
。数组变为 [0, 0, 0, 0, 0]
。输出为 4。
提示:
1 <= queries.length <= 105
queries[i].length == 2
queries[i] == [l, r]
1 <= l < r <= 109
对正整数 $x$ 执行除以 $4$ 向下取整,将 $x$ 变成 $0$ 的执行次数与 $x$ 的值的关系如下:当 $1 \le x < 4$ 时,需要执行 $1$ 次;当 $4 \le x < 16$ 时,需要执行 $2$ 次;当 $16 \le x < 64$ 时,需要执行 $3$ 次;以此类推,当存在正整数 $p$ 满足 $4^{p - 1} \le x < 4^p$ 时,需要执行 $p$ 次。因此将 $x$ 变成 $0$ 的执行次数是 $\lfloor \log_4 x \rfloor + 1$。
对于二维数组 $\textit{queries}$ 中的每个查询 $[\textit{left}, \textit{right}]$,可以分别计算从 $\textit{left}$ 到 $\textit{right}$ 的每个正整数的执行次数,并得到区间 $[\textit{left}, \textit{right}]$ 中的所有正整数的执行次数之和。
由于 $\textit{left}$ 和 $\textit{right}$ 的取值范围是 $1 \le \textit{left} < \textit{right} \le 10^9$,因此如果直接遍历区间 $[\textit{left}, \textit{right}]$ 中的每个正整数计算执行次数,则时间复杂度过高,需要优化。
为了计算区间 $[\textit{left}, \textit{right}]$ 中的所有正整数的执行次数之和,可以分别计算区间 $[1, \textit{right}]$ 中的所有正整数的执行次数之和与区间 $[1, \textit{left} - 1]$ 中的所有正整数的执行次数之和,两项之差即为区间 $[\textit{left}, \textit{right}]$ 中的所有正整数的执行次数之和。
对于非负整数 $\textit{num}$,计算区间 $[1, \textit{num}]$ 中的所有正整数的执行次数之和的方法如下。
用 $\textit{currReductions}$ 表示当前执行次数,用 $\textit{start}$ 表示执行次数是 $\textit{currReductions}$ 的最小正整数,初始时 $\textit{currReductions} = 1$,$\textit{start} = 1$。
对于每个 $\textit{start}$ 计算 $\textit{end} = \min(\textit{start} \times 4 - 1, \textit{num})$,则区间 $[\textit{start}, \textit{end}]$ 为执行次数是 $\textit{currReductions}$ 的所有正整数的区间,该区间中的正整数个数是 $\textit{end} - \textit{start} + 1$,因此将区间 $[1, \textit{num}]$ 中的所有正整数的执行次数之和增加 $\textit{currReductions} \times (\textit{end} - \textit{start} + 1)$。然后将 $\textit{start}$ 的值乘以 $4$,将 $\textit{currReductions}$ 的值增加 $1$,重复上述操作。当 $\textit{start} > \textit{num}$ 时,结束操作,得到区间 $[1, \textit{num}]$ 中的所有正整数的执行次数之和。特别地,当 $\textit{num} = 0$ 时,上述做法也适用。
将区间 $[\textit{left}, \textit{right}]$ 中的所有正整数的执行次数之和记为 $\textit{reductions}$。当每次操作对两个正整数执行除以 $4$ 向下取整时,区间 $[\textit{left}, \textit{right}]$ 的最少操作次数等于 $\Big\lceil \dfrac{\textit{reductions}}{2} \Big\rceil$。理由如下。
对于正整数 $x$,用 $r(x)$ 表示对正整数 $x$ 执行除以 $4$ 向下取整,将 $x$ 变成 $0$ 的执行次数,则 $r(x) = \lfloor \log_4 x \rfloor + 1$。将区间 $[\textit{left}, \textit{right}]$ 中的每个正整数 $x$ 都替换成 $r(x)$,则得到从 $r(\textit{left})$ 到 $r(\textit{right})$ 的 $\textit{right} - \textit{left} + 1$ 个正整数组成的新数组,将新数组记为 $\textit{reductionsArr}$,则问题转换成:每次从新数组 $\textit{reductionsArr}$ 中选择两个元素分别减少 $1$,计算将新数组 $\textit{reductionsArr}$ 中的所有元素都变成零或负数的最少操作次数(由于 $0$ 除以 $4$ 仍等于 $0$,因此新数组中的元素变成负数也符合原数组中的元素变成 $0$)。
根据 $r(x)$ 的性质,新数组 $\textit{reductionsArr}$ 为单调递增数组且任意两个相邻元素之差等于 $0$ 或 $1$。每次从新数组 $\textit{reductionsArr}$ 中选择最大的两个元素分别减少 $1$,则可以经过若干次操作将所有的最大元素都减少 $1$ 且最多有一个次大元素减少 $1$。
经过若干次操作之后,一定可以将新数组 $\textit{reductionsArr}$ 变成所有元素值都相等(例如全部是 $r$)或其中一个元素值等于其余每个元素值加 $1$(例如只有一个元素是 $r + 1$,其余元素都是 $r$)。对于两种情况,都可以将元素值同步减少,直到所有元素变成 $0$ 或其中一个元素值是 $1$ 且其余每个元素值都是 $0$,当剩余一个元素值是 $1$ 时还需要额外操作一次才能将新数组 $\textit{reductionsArr}$ 中的所有元素都变成零或负数。因此在操作结束之后,新数组 $\textit{reductionsArr}$ 中最多有一个元素是 $-1$,其余元素都是 $0$,最少操作次数等于 $\Big\lceil \dfrac{\textit{reductions}}{2} \Big\rceil$。
分别计算二维数组 $\textit{queries}$ 中的每个查询的最少操作次数,计算所有查询结果的总和,即为答案。
###Java
class Solution {
public long minOperations(int[][] queries) {
long operations = 0;
for (int[] query : queries) {
long reductions = countReductions(query[1]) - countReductions(query[0] - 1);
operations += (reductions + 1) / 2;
}
return operations;
}
public long countReductions(int num) {
long reductions = 0;
int currReductions = 1;
long start = 1;
while (start <= num) {
long end = Math.min(start * 4 - 1, num);
reductions += currReductions * (end - start + 1);
start *= 4;
currReductions++;
}
return reductions;
}
}
###C#
public class Solution {
public long MinOperations(int[][] queries) {
long operations = 0;
foreach (int[] query in queries) {
long reductions = CountReductions(query[1]) - CountReductions(query[0] - 1);
operations += (reductions + 1) / 2;
}
return operations;
}
public long CountReductions(int num) {
long reductions = 0;
int currReductions = 1;
long start = 1;
while (start <= num) {
long end = Math.Min(start * 4 - 1, num);
reductions += currReductions * (end - start + 1);
start *= 4;
currReductions++;
}
return reductions;
}
}
时间复杂度:$O(n \log m)$,其中 $n$ 是数组 $\textit{queries}$ 的长度,$m$ 是数组 $\textit{queries}$ 中的最大值。需要计算 $n$ 个查询的结果,每个查询的计算时间是 $O(\log m)$,因此时间复杂度是 $O(n \log m)$。
空间复杂度:$O(1)$。
$a$ 除以 $4$ 下取整等价于 $a$ 右移 $2$ 位。所以把一个长为 $m$ 的二进制数变成 $0$,需要操作 $\left\lceil\dfrac{m}{2}\right\rceil$ 次。
例如 $l=1$,$r=5$,如果每次操作只把一个数右移 $2$ 位,那么 $1$ 到 $5$ 每个数的操作次数分别为
$$
\textit{ops}=[1,1,1,2,2]
$$
本题一次可以操作两个数,问题等价于:
分析:设 $\textit{tot}=\sum \textit{ops}[i]$,$\textit{mx}=\max(\textit{ops})$。假如每次可以把 $\textit{tot}$ 减少 $2$,那么把 $\textit{tot}$ 减少到 $\le 0$,至少要操作 $\left\lceil\dfrac{\textit{tot}}{2}\right\rceil$ 次。但如果 $\textit{mx}$ 很大,操作次数就等于 $\textit{mx}$(每次操作选 $\textit{mx}$ 和另一个数)。
定理:最少操作次数为
$$
\max\left(\left\lceil\dfrac{\textit{tot}}{2}\right\rceil, \textit{mx}\right)
$$
本题由于 $\textit{nums}$ 中的数字是连续整数,相邻数字的操作次数至多相差 $1$。
例如两个数的情况 $\textit{ops}=[x-1,x]$,得到 $\textit{mx}=x$,$\textit{tot} = 2x-1$。由于 $\left\lceil\dfrac{2x-1}{2}\right\rceil = x$,所以 $\textit{mx} \le \left\lceil\dfrac{\textit{tot}}{2}\right\rceil$ 成立。推广到更多元素的情况,设最大值为 $\textit{mx}=x$,其余元素不超过 $x$,当元素增多时,$\textit{tot}$ 更大,$\textit{mx} \le \left\lceil\dfrac{\textit{tot}}{2}\right\rceil$ 更加成立。
所以本题
$$
\textit{mx} \le \left\lceil\dfrac{\textit{tot}}{2}\right\rceil
$$
恒成立。
所以
$$
\max\left(\left\lceil\dfrac{\textit{tot}}{2}\right\rceil, \textit{mx}\right) = \left\lceil\dfrac{\textit{tot}}{2}\right\rceil
$$
算出了 $\textit{tot}$,就算出了答案。
定义 $f(n)$ 为 $[1,n]$ 中的单个数的操作次数之和。
设 $n$ 的二进制长度为 $m$。按照 $[1,n]$ 中的数字的二进制长度,分组计算:
两部分累加,得
$$
f(n) = \sum_{i=1}^{m-1} \left\lceil\dfrac{i}{2}\right\rceil 2^{i-1} + \left\lceil\dfrac{m}{2}\right\rceil(n+1-2^{m-1})
$$
由于 $[l,r]$ 可以拆分成 $[1,r]$ 减去 $[1,l-1]$,所以 $[l,r]$ 中的单个数的操作次数之和为
$$
\textit{tot} = f(r) - f(l-1)
$$
每次操作至多两个数,操作次数为
$$
\left\lceil\dfrac{f(r) - f(l-1)}{2}\right\rceil = \left\lfloor\dfrac{ f(r) - f(l-1) + 1}{2}\right\rfloor
$$
本题视频讲解,欢迎点赞关注~
###py
# 返回 [1,n] 的单个元素的操作次数之和
def f(n: int) -> int:
m = n.bit_length()
res = sum((i + 1) // 2 << (i - 1) for i in range(1, m))
return res + (m + 1) // 2 * (n + 1 - (1 << m >> 1))
class Solution:
def minOperations(self, queries: List[List[int]]) -> int:
return sum((f(r) - f(l - 1) + 1) // 2 for l, r in queries)
###java
class Solution {
public long minOperations(int[][] queries) {
long ans = 0;
for (int[] q : queries) {
ans += (f(q[1]) - f(q[0] - 1) + 1) / 2;
}
return ans;
}
// 返回 [1,n] 的单个元素的操作次数之和
private long f(int n) {
int m = 32 - Integer.numberOfLeadingZeros(n);
long res = 0;
for (int i = 1; i < m; i++) {
res += (long) (i + 1) / 2 << (i - 1);
}
return res + (long) (m + 1) / 2 * (n + 1 - (1 << m >> 1));
}
}
###cpp
class Solution {
// 返回 [1,n] 的单个元素的操作次数之和
long long f(int n) {
int m = bit_width((uint32_t) n);
long long res = 0;
for (int i = 1; i < m; i++) {
res += 1LL * (i + 1) / 2 << (i - 1);
}
return res + 1LL * (m + 1) / 2 * (n + 1 - (1 << m >> 1));
}
public:
long long minOperations(vector<vector<int>>& queries) {
long long ans = 0;
for (auto& q : queries) {
ans += (f(q[1]) - f(q[0] - 1) + 1) / 2;
}
return ans;
}
};
###go
// 返回 [1,n] 的单个元素的操作次数之和
func f(n int) (res int) {
m := bits.Len(uint(n))
for i := 1; i < m; i++ {
res += (i + 1) / 2 << (i - 1)
}
return res + (m+1)/2*(n+1-1<<m>>1)
}
func minOperations(queries [][]int) int64 {
ans := 0
for _, q := range queries {
ans += (f(q[1]) - f(q[0]-1) + 1) / 2
}
return int64(ans)
}
$\mathcal{O}(1)$ 计算 $f(n)$。
设 $n$ 的二进制长度为 $m$。设 $k$ 为小于 $m$ 的最大偶数。
上面代码的循环,把二进制长为 $1,2$ 的分成一组(每个数操作 $1$ 次),长为 $3,4$ 的分成一组(每个数操作 $2$ 次),长为 $5,6$ 的分成一组(每个数操作 $3$ 次)……依此类推,累加得
$$
(2^2-2^0)\cdot 1 + (2^4-2^2)\cdot 2 + \cdots + (2^k-2^{k-2})\cdot \dfrac{k}{2}
$$
利用错位相减法(详见 视频讲解),上式可化简为
$$
k\cdot 2^{k-1} - \dfrac{2^k-1}{3}
$$
对于长为 $[k+1,m]$ 的二进制数,最小是 $2^k$,最大是 $n$,共有 $n+1-2^k$ 个,每个需要操作 $\left\lceil\dfrac{m}{2}\right\rceil$ 次。
相加得
$$
f(n) = k\cdot 2^{k-1} - \dfrac{2^k-1}{3} + \left\lceil\dfrac{m}{2}\right\rceil(n+1-2^k)
$$
代码实现时,如果 $k=0$,没法左移 $k-1=-1$ 位。可以改为先左移 $k$ 位,再右移一位,这样无需特判 $k=0$ 的情况。
###py
def f(n: int) -> int:
if n == 0:
return 0
m = n.bit_length()
k = (m - 1) // 2 * 2
res = (k << k >> 1) - (1 << k) // 3 # -1 可以省略
return res + (m + 1) // 2 * (n + 1 - (1 << k))
class Solution:
def minOperations(self, queries: List[List[int]]) -> int:
return sum((f(r) - f(l - 1) + 1) // 2 for l, r in queries)
###java
class Solution {
public long minOperations(int[][] queries) {
long ans = 0;
for (int[] q : queries) {
ans += (f(q[1]) - f(q[0] - 1) + 1) / 2;
}
return ans;
}
private long f(int n) {
int m = 32 - Integer.numberOfLeadingZeros(n);
int k = (m - 1) / 2 * 2;
long res = ((long) k << k >> 1) - (1 << k) / 3; // -1 可以省略
return res + (long) (m + 1) / 2 * (n + 1 - (1 << k));
}
}
###cpp
class Solution {
long long f(int n) {
int m = bit_width((uint32_t) n);
int k = (m - 1) / 2 * 2;
long long res = (1LL * k << k >> 1) - (1 << k) / 3; // -1 可以省略
return res + 1LL * (m + 1) / 2 * (n + 1 - (1 << k));
}
public:
long long minOperations(vector<vector<int>>& queries) {
long long ans = 0;
for (auto& q : queries) {
ans += (f(q[1]) - f(q[0] - 1) + 1) / 2;
}
return ans;
}
};
###go
func f(n int) int {
m := bits.Len(uint(n))
k := (m - 1) / 2 * 2
res := k<<k>>1 - 1<<k/3 // -1 可以省略
return res + (m+1)/2*(n+1-1<<k)
}
func minOperations(queries [][]int) int64 {
ans := 0
for _, q := range queries {
ans += (f(q[1]) - f(q[0]-1) + 1) / 2
}
return int64(ans)
}
见下面贪心题单的「§1.8 相邻不同」。
欢迎关注 B站@灵茶山艾府
把 $x$ 变成 $0$ 至少需要 $p$ 步,其中 $p$ 是满足 $4^p > x$ 的最小整数。
知道了每个元素需要的操作步数后,怎么求出答案呢?这就是 leetcode 1953. 你可以工作的最大周数,结论是:设所有元素操作次数之和为 $s$,最大操作次数为 $m$,那么答案为 $\max(\lceil\frac{s}{2}\rceil, m)$。
可以用前缀和的方式把 $[l, r]$ 的操作次数之和算出来,详见参考代码。复杂度 $\mathcal{O}(q\log X)$,其中 $X = 10^9$ 是值域。
class Solution {
public:
long long minOperations(vector<vector<int>>& queries) {
// 计算 [1, x] 的操作次数之和
auto calc = [&](long long x) {
long long ret = 0;
long long p = 1;
// [p, 4p) 范围内的元素,操作次数均为 i
for (int i = 1; p <= x; i++, p *= 4) {
long long cnt = min(p * 4 - 1, x) - p + 1;
ret += cnt * i;
}
return ret;
};
long long ans = 0;
for (auto &qry : queries) {
int l = qry[0], r = qry[1];
ans += max(
// 用前缀和算出 [l, r] 操作次数之和 s,这里求的是 ceil(s / 2)
(calc(r) - calc(l - 1) + 1) / 2,
// 用前缀和算出 r 的操作次数,因为元素越大,操作次数最大
calc(r) - calc(r - 1)
);
}
return ans;
}
};
给你两个整数:num1
和 num2
。
在一步操作中,你需要从范围 [0, 60]
中选出一个整数 i
,并从 num1
减去 2i + num2
。
请你计算,要想使 num1
等于 0
需要执行的最少操作数,并以整数形式返回。
如果无法使 num1
等于 0
,返回 -1
。
示例 1:
输入:num1 = 3, num2 = -2 输出:3 解释:可以执行下述步骤使 3 等于 0 : - 选择 i = 2 ,并从 3 减去 22 + (-2) ,num1 = 3 - (4 + (-2)) = 1 。 - 选择 i = 2 ,并从 1 减去 22 + (-2) ,num1 = 1 - (4 + (-2)) = -1 。 - 选择 i = 0 ,并从 -1 减去 20 + (-2) ,num1 = (-1) - (1 + (-2)) = 0 。 可以证明 3 是需要执行的最少操作数。
示例 2:
输入:num1 = 5, num2 = 7 输出:-1 解释:可以证明,执行操作无法使 5 等于 0 。
提示:
1 <= num1 <= 109
-109 <= num2 <= 109
这题其实挺简单,就是统计num1-num2之后的数的二进制位为1的个数是否少于cnt。
举例:
cnt=1:3-(-2)=5->101(2>cnt)
cnt=2:5-(-2)=7->111(3>cnt)
cnt=3:7-(-2)=9->1001(2<cnt)
结束
原理很简单,因为根据二进制特性,num1-num2余下的必然为2的幂之和,只要该和的加数个数少于等于cnt,无论这个和是什么,我都可以用恰好cnt个数相加得到num1-num2。
注意:根据二进制位数,cnt不会超过32.
###cpp
class Solution {
public:
int makeTheIntegerZero(int num1, int num2) {
int cnt=0;
long n1=num1,n2=num2;
while(1){
cnt++;
n1-=n2;
if(cnt<=n1&&__builtin_popcountll(n1)<=cnt)return cnt;
else if(n2>=-1&&n1<0)return -1;
}
return 0;
}
};
假设每次操作只会减去 $2^i$,大家都知道答案是 num1
的二进制表示中 $1$ 的数量。加入了 num2
之后不太好处理,所以我们尝试枚举操作次数,把 num2
的影响一次性处理掉。
假设我们要恰好执行 $k$ 次操作,令 x = num1 - k * num2
,我们需要检查能否用恰好 $k$ 个 $2^i$ 凑成 $x$。
容易看出,至少需要 popcount(x)
个 $2^i$ 才能凑成 $x$(popcount(x)
就是 $x$ 的二进制表示中 $1$ 的数量);同时,至多只能用 $x$ 个 $2^0 = 1$ 凑出 $x$。也就是说,只要 $k$ 满足 popcount(x) <= k <= x
就是一个合法的 $k$。
那么为什么 popcount(x)
和 $x$ 之间的所有值都能取到呢?这是因为,每个 $2^i$ 都能拆成两个 $2^{i - 1}$,数量增加 $1$,因此所有值都能取到。
因此,我们从 $1$ 开始枚举 $k$,发现合法的 $k$ 即可返回答案。
接下来分析这个做法的复杂度:
num2 == 0
时,popcount(x)
和 x
的值是固定的,只要枚举到 k == popcount(x)
即可返回答案。复杂度 $\mathcal{O}(\log x)$。num2 < 0
时,x
每次至少增加 $1$,而 popcount(x)
是 $\mathcal{O}(\log x)$ 的。因为 $k$ 从 $0$ 开始,每次只增加 $1$,因此它永远不会超过 $x$。那么 $k$ 只要超过 popcount(x)
就够了。复杂度 $\mathcal{O}(\log x)$。num2 > 0
时,x
会越来越小,当 $k > x$ 时即可返回无解。除此之外,当 $k$ 超过 popcount(x)
时即可返回答案,复杂度仍然为 $\mathcal{O}(\log x)$。因此整体复杂度 $\mathcal{O}(\log x)$。
###c++
class Solution {
public:
int makeTheIntegerZero(int num1, int num2) {
for (int k = 1; ; k++) {
long long x = num1 - 1LL * k * num2;
if (k > x) return -1;
if (__builtin_popcountll(x) <= k && k <= x) return k;
}
}
};
假设我们操作了 $k$ 次,此时 $\textit{num}_1$ 变成 $\textit{num}_1 - \textit{num}_2\cdot k$ 再减去 $k$ 个 $2^i$。
能否把 $\textit{num}_1$ 变成 $0$,等价于:
在示例 1 中,$k=3$ 时 $\textit{num}_1 - \textit{num}_2\cdot k = 9$,我们可以把 $9$ 拆分成 $4+4+1$,这三个数都是 $2$ 的幂。
设 $x=\textit{num}_1 - \textit{num}_2\cdot k$。
为了判断能否把 $x$ 拆分成恰好 $k$ 个 $2$ 的幂之和,我们可以先做上下界分析:
由于一个 $2^i$ 可以分解成两个 $2^{i-1}$,而 $2^{i-1}$ 又可以继续分解为 $2^{i-2}$,所以分解出的 $2$ 的幂的个数可以是 $[\textit{low},\textit{high}]$ 中的任意整数。$k$ 只要在这个范围中,那么分解方案就是存在的。
暴力的想法是,从小到大枚举 $k=1,2,3,\ldots$ 计算 $x=\textit{num}_1 - \textit{num}_2\cdot k$,判断 $k$ 是否满足上下界(在区间中)。这样做是否会超时?$k$ 最大枚举到多少呢?
对于上界,即 $k\le x = \textit{num}_1 - \textit{num}_2\cdot k$,变形得 $k\cdot (\textit{num}_2+1)\le \textit{num}_1$。
对于下界,定义 $\text{popcount}(x)$ 为 $x$ 的二进制中的 $1$ 的个数,我们要满足 $k\ge \text{popcount}(x)$。粗略估计一下,当 $k=60$ 时,在本题数据范围下,当 $\textit{num}_1=10^9$,$\textit{num}_2 = -10^9$ 时 $x$ 最大,为 $61\times 10^9$,二进制长度只有 $36$。由于 $\text{popcount}(x)$ 不会超过 $x$ 的二进制长度,所以此时 $k$ 一定满足下界。所以本题的枚举次数其实很小,暴力枚举不会超时。
综上所述,在枚举 $k=1,2,3,\ldots$ 的过程中:
视频讲解(第二题)
class Solution:
def makeTheIntegerZero(self, num1: int, num2: int) -> int:
for k in count(1): # 枚举 k=1,2,3,...
x = num1 - num2 * k
if k > x:
return -1
if k >= x.bit_count():
return k
class Solution {
public int makeTheIntegerZero(int num1, int num2) {
for (long k = 1; k <= num1 - num2 * k; k++) {
if (k >= Long.bitCount(num1 - num2 * k)) {
return (int) k;
}
}
return -1;
}
}
class Solution {
public:
int makeTheIntegerZero(int num1, int num2) {
for (long long k = 1; k <= num1 - num2 * k; k++) {
if (k >= popcount((uint64_t) num1 - num2 * k)) {
return k;
}
}
return -1;
}
};
int makeTheIntegerZero(int num1, int num2) {
for (long long k = 1; k <= num1 - num2 * k; k++) {
if (k >= __builtin_popcountll(num1 - num2 * k)) {
return k;
}
}
return -1;
}
func makeTheIntegerZero(num1, num2 int) int {
for k := 1; k <= num1-num2*k; k++ {
if k >= bits.OnesCount(uint(num1-num2*k)) {
return k
}
}
return -1
}
var makeTheIntegerZero = function(num1, num2) {
for (let k = 1; k <= num1 - num2 * k; k++) {
if (k >= bitCount64(num1 - num2 * k)) {
return k;
}
}
return -1;
};
function bitCount64(i) {
return bitCount32(Math.floor(i / 0x100000000)) + bitCount32(i >>> 0);
}
function bitCount32(i) {
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
impl Solution {
pub fn make_the_integer_zero(num1: i32, num2: i32) -> i32 {
for k in 1.. {
let x = num1 as i64 - num2 as i64 * k;
if k > x {
return -1;
}
if k as u32 >= x.count_ones() {
return k as _;
}
}
unreachable!()
}
}
注:关于这个反函数的研究,见朗伯 W 函数(Lambert W function)。
欢迎关注 B站@灵茶山艾府
我们计算出第 $1$ 个人和第 $3$ 个人的距离 $a$,第 $2$ 个人和第 $3$ 个人的距离 $b$。
###python
class Solution:
def findClosest(self, x: int, y: int, z: int) -> int:
a = abs(x - z)
b = abs(y - z)
return 0 if a == b else (1 if a < b else 2)
###java
class Solution {
public int findClosest(int x, int y, int z) {
int a = Math.abs(x - z);
int b = Math.abs(y - z);
return a == b ? 0 : (a < b ? 1 : 2);
}
}
###cpp
class Solution {
public:
int findClosest(int x, int y, int z) {
int a = abs(x - z);
int b = abs(y - z);
return a == b ? 0 : (a < b ? 1 : 2);
}
};
###go
func findClosest(x int, y int, z int) int {
a, b := abs(x-z), abs(y-z)
if a == b {
return 0
}
if a < b {
return 1
}
return 2
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
###ts
function findClosest(x: number, y: number, z: number): number {
const a = Math.abs(x - z);
const b = Math.abs(y - z);
return a === b ? 0 : a < b ? 1 : 2;
}
###rust
impl Solution {
pub fn find_closest(x: i32, y: i32, z: i32) -> i32 {
let a = (x - z).abs();
let b = (y - z).abs();
if a == b {
0
} else if a < b {
1
} else {
2
}
}
}
###js
/**
* @param {number} x
* @param {number} y
* @param {number} z
* @return {number}
*/
var findClosest = function (x, y, z) {
const a = Math.abs(x - z);
const b = Math.abs(y - z);
return a === b ? 0 : a < b ? 1 : 2;
};
###cs
public class Solution {
public int FindClosest(int x, int y, int z) {
int a = Math.Abs(x - z);
int b = Math.Abs(y - z);
return a == b ? 0 : (a < b ? 1 : 2);
}
}
时间复杂度 $O(1)$,空间复杂度 $O(1)$。
有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
给你三个整数 x
、y
和 z
,表示数轴上三个人的位置:
x
是第 1 个人的位置。y
是第 2 个人的位置。z
是第 3 个人的位置,第 3 个人 不会移动 。第 1 个人和第 2 个人以 相同 的速度向第 3 个人移动。
判断谁会 先 到达第 3 个人的位置:
根据上述规则返回结果。
示例 1:
输入: x = 2, y = 7, z = 4
输出: 1
解释:
由于第 1 个人先到达,所以输出为 1。
示例 2:
输入: x = 2, y = 5, z = 6
输出: 2
解释:
由于第 2 个人先到达,所以输出为 2。
示例 3:
输入: x = 1, y = 5, z = 3
输出: 0
解释:
由于两个人同时到达,所以输出为 0。
提示:
1 <= x, y, z <= 100
由于第 $1$ 个人和第 $2$ 个人的移动速度相同,因此与第 $3$ 个人的距离更近的人会先到达第 $3$ 个人的位置。
第 $1$ 个人到第 $3$ 个人的距离是 $\textit{distance}_1 = |x - z|$,第 $2$ 个人到第 $3$ 个人的距离是 $\textit{distance}_2 = |y - z|$。结果如下。
如果 $\textit{distance}_1 < \textit{distance}_2$,则第 $1$ 个人先到达第 $3$ 个人的位置,返回 $1$。
如果 $\textit{distance}_1 > \textit{distance}_2$,则第 $2$ 个人先到达第 $3$ 个人的位置,返回 $2$。
如果 $\textit{distance}_1 = \textit{distance}_2$,则两个人同时到达第 $3$ 个人的位置,返回 $0$。
###Java
class Solution {
public int findClosest(int x, int y, int z) {
int distance1 = Math.abs(x - z), distance2 = Math.abs(y - z);
if (distance1 < distance2) {
return 1;
} else if (distance1 > distance2) {
return 2;
} else {
return 0;
}
}
}
###C#
public class Solution {
public int FindClosest(int x, int y, int z) {
int distance1 = Math.Abs(x - z), distance2 = Math.Abs(y - z);
if (distance1 < distance2) {
return 1;
} else if (distance1 > distance2) {
return 2;
} else {
return 0;
}
}
}
时间复杂度:$O(1)$。
空间复杂度:$O(1)$。
时间等于距离除以速度,由于两人速度相同,转化成比较两人到第三人的距离。
设 $a=|x-z|$,$b=|y-z|$。
根据题意:
###py
class Solution:
def findClosest(self, x: int, y: int, z: int) -> int:
a = abs(x - z)
b = abs(y - z)
if a == b:
return 0
return 1 if a < b else 2
###java
class Solution {
public int findClosest(int x, int y, int z) {
int a = Math.abs(x - z);
int b = Math.abs(y - z);
return a == b ? 0 : a < b ? 1 : 2;
}
}
###cpp
class Solution {
public:
int findClosest(int x, int y, int z) {
int a = abs(x - z);
int b = abs(y - z);
return a == b ? 0 : a < b ? 1 : 2;
}
};
###c
int findClosest(int x, int y, int z) {
int a = abs(x - z);
int b = abs(y - z);
return a == b ? 0 : a < b ? 1 : 2;
}
###go
func findClosest(x, y, z int) int {
a := abs(x - z)
b := abs(y - z)
if a == b {
return 0
}
if a < b {
return 1
}
return 2
}
func abs(x int) int { if x < 0 { return -x }; return x }
###go
var state = [3]int{1, 0, 2}
func findClosest(x, y, z int) int {
a := abs(x - z)
b := abs(y - z)
return state[cmp.Compare(a, b)+1]
}
func abs(x int) int { if x < 0 { return -x }; return x }
###js
var findClosest = function(x, y, z) {
const a = Math.abs(x - z);
const b = Math.abs(y - z);
return a === b ? 0 : a < b ? 1 : 2;
};
###rust
impl Solution {
pub fn find_closest(x: i32, y: i32, z: i32) -> i32 {
let a = (x - z).abs();
let b = (y - z).abs();
if a == b { 0 } else if a < b { 1 } else { 2 }
}
}
部分语言可以利用 $\texttt{bool}$ 自动转成 $\texttt{int}$ 的特性简化代码逻辑。
###py
class Solution:
def findClosest(self, x: int, y: int, z: int) -> int:
a = abs(x - z)
b = abs(y - z)
return (a > b) << 1 | (a < b)
###cpp
class Solution {
public:
int findClosest(int x, int y, int z) {
int a = abs(x - z);
int b = abs(y - z);
return (a > b) << 1 | (a < b);
}
};
###c
int findClosest(int x, int y, int z) {
int a = abs(x - z);
int b = abs(y - z);
return (a > b) << 1 | (a < b);
}
###js
var findClosest = function(x, y, z) {
const a = Math.abs(x - z);
const b = Math.abs(y - z);
return (a > b) << 1 | (a < b);
};
欢迎关注 B站@灵茶山艾府
###Python3
class Solution:
def findClosest(self, x: int, y: int, z: int) -> int:
d1, d2 = abs(x - z), abs(y - z)
return [0, 2, 1][(d1 > d2) - (d1 < d2)]
给你一个 n x 2
的二维数组 points
,它表示二维平面上的一些点坐标,其中 points[i] = [xi, yi]
。
我们定义 x 轴的正方向为 右 (x 轴递增的方向),x 轴的负方向为 左 (x 轴递减的方向)。类似的,我们定义 y 轴的正方向为 上 (y 轴递增的方向),y 轴的负方向为 下 (y 轴递减的方向)。
你需要安排这 n
个人的站位,这 n
个人中包括 Alice 和 Bob 。你需要确保每个点处 恰好 有 一个 人。同时,Alice 想跟 Bob 单独玩耍,所以 Alice 会以 Alice 的坐标为 左上角 ,Bob 的坐标为 右下角 建立一个矩形的围栏(注意,围栏可能 不 包含任何区域,也就是说围栏可能是一条线段)。如果围栏的 内部 或者 边缘 上有任何其他人,Alice 都会难过。
请你在确保 Alice 不会 难过的前提下,返回 Alice 和 Bob 可以选择的 点对 数目。
注意,Alice 建立的围栏必须确保 Alice 的位置是矩形的左上角,Bob 的位置是矩形的右下角。比方说,以 (1, 1)
,(1, 3)
,(3, 1)
和 (3, 3)
为矩形的四个角,给定下图的两个输入,Alice 都不能建立围栏,原因如下:
(3, 3)
且 Bob 在 (1, 1)
,Alice 的位置不是左上角且 Bob 的位置不是右下角。(1, 3)
且 Bob 在 (1, 1)
,Bob 的位置不是在围栏的右下角。
示例 1:
输入:points = [[1,1],[2,2],[3,3]] 输出:0 解释:没有办法可以让 Alice 的围栏以 Alice 的位置为左上角且 Bob 的位置为右下角。所以我们返回 0 。
示例 2:
输入:points = [[6,2],[4,4],[2,6]] 输出:2 解释:总共有 2 种方案安排 Alice 和 Bob 的位置,使得 Alice 不会难过: - Alice 站在 (4, 4) ,Bob 站在 (6, 2) 。 - Alice 站在 (2, 6) ,Bob 站在 (4, 4) 。 不能安排 Alice 站在 (2, 6) 且 Bob 站在 (6, 2) ,因为站在 (4, 4) 的人处于围栏内。
示例 3:
输入:points = [[3,1],[1,3],[1,1]] 输出:2 解释:总共有 2 种方案安排 Alice 和 Bob 的位置,使得 Alice 不会难过: - Alice 站在 (1, 1) ,Bob 站在 (3, 1) 。 - Alice 站在 (1, 3) ,Bob 站在 (1, 1) 。 不能安排 Alice 站在 (1, 3) 且 Bob 站在 (3, 1) ,因为站在 (1, 1) 的人处于围栏内。 注意围栏是可以不包含任何面积的,上图中第一和第二个围栏都是合法的。
提示:
2 <= n <= 1000
points[i].length == 2
-109 <= points[i][0], points[i][1] <= 109
points[i]
点对两两不同。