普通视图

发现新文章,点击刷新页面。
昨天以前首页

3495. 使数组元素都变为零的最少操作次数

作者 stormsunshine
2025年3月24日 06:17

解法

思路和算法

对正整数 $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}]$ 中的所有正整数的执行次数之和的方法如下。

  1. 用 $\textit{currReductions}$ 表示当前执行次数,用 $\textit{start}$ 表示执行次数是 $\textit{currReductions}$ 的最小正整数,初始时 $\textit{currReductions} = 1$,$\textit{start} = 1$。

  2. 对于每个 $\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$。理由如下。

  1. 对于正整数 $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$)。

  2. 根据 $r(x)$ 的性质,新数组 $\textit{reductionsArr}$ 为单调递增数组且任意两个相邻元素之差等于 $0$ 或 $1$。每次从新数组 $\textit{reductionsArr}$ 中选择最大的两个元素分别减少 $1$,则可以经过若干次操作将所有的最大元素都减少 $1$ 且最多有一个次大元素减少 $1$。

  3. 经过若干次操作之后,一定可以将新数组 $\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)$。

3516. 找到最近的人

作者 stormsunshine
2025年4月13日 18:53

解法

思路和算法

由于第 $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)$。

2787. 将一个数字表示成幂的和的方案数

作者 stormsunshine
2024年6月14日 21:39

解法

思路和算法

计算 $\textit{maxNum} = \lfloor \sqrt[x]{n} \rfloor$,则当 $n$ 表示成各不相同的正整数的 $x$ 次幂之和时,任何一个作为底数的正整数都不能超过 $\textit{maxNum}$。问题等价于计算从 $1^x$ 到 $\textit{maxNum}^x$ 的 $\textit{maxNum}$ 个正整数的 $x$ 次幂中选取一个或多个数字满足选取的数字之和等于 $n$ 的方案数。

该问题是 0-1 背包问题的变种,和传统 0-1 背包问题的区别在于,这道题要求选取的数字之和恰好等于 $n$。对于每个数字,选取的数字之和取决于之前选取的数字,因此可以使用动态规划计算选取的数字之和等于 $n$ 的方案数。

创建 $(\textit{maxNum} + 1) \times (n + 1)$ 的二维数组 $\textit{dp}$,其中 $\textit{dp}[i][j]$ 表示在前 $i$ 个数字中选取数字使数字之和等于 $j$ 的方案数。

当 $i = 0$ 时,没有任何数字,数字之和一定是 $0$,因此动态规划的边界情况是:当 $j = 0$ 时 $\textit{dp}[0][j] = 1$,当 $j > 0$ 时 $\textit{dp}[0][j] = 0$。

当 $i > 0$ 时,当前数字是 $i^x$,只有当 $j \ge i^x$ 时才能选取当前数字。分别考虑 $j < i^x$ 和 $j \ge i^x$ 的情况。

  • 如果 $j < i^x$,则不能选取当前数字,在前 $i$ 个数字中选取数字使数字之和等于 $j$ 等价于在前 $i - 1$ 个数字中选取数字使数字之和等于 $j$,因此方案数为 $\textit{dp}[i - 1][j]$。

  • 如果 $j \ge i^x$,则可以不选取或选取当前数字,根据两种情况计算方案数。

    • 如果不选取当前数字,则需要在前 $i - 1$ 个数字中选取数字使数字之和等于 $j$,方案数为 $\textit{dp}[i - 1][j]$。

    • 如果选取当前数字,则需要在前 $i - 1$ 个数字中选取数字使数字之和等于 $j - i^x$,加上选取的当前数字之后数字之和等于 $j$,因此方案数为 $\textit{dp}[i - 1][j - i^x]$。

因此动态规划的状态转移方程如下。

  • 如果 $j < i^x$,则 $\textit{dp}[i][j] = \textit{dp}[i - 1][j]$。

  • 如果 $j \ge i^x$,则 $\textit{dp}[i][j] = \textit{dp}[i - 1][j] + \textit{dp}[i - 1][j - i^x]$。

根据动态规划的状态转移方程,计算 $\textit{dp}[i][j]$ 的顺序为从小到大遍历每个 $i$。计算得到 $\textit{dp}[\textit{maxNum}][n]$ 即为将 $n$ 表示成各不相同的正整数的 $x$ 次幂之和的方案数。

上述做法的时间复杂度和空间复杂度都是 $O(\sqrt[x]{n} \times n)$。

实现方面可以优化空间,做法如下。

  1. 由于 $\textit{dp}[i][]$ 只取决于 $\textit{dp}[i - 1][]$,和更早的状态无关,因此可以使用滚动数组的思想,只保留前一个 $i$ 处的状态,将空间复杂度降到 $O(n)$。

  2. 优化空间时,如果从小到大遍历每个 $j$ 则对于每个遍历到的 $i$ 都需要新建一个临时数组。可以进一步优化空间,从大到小遍历每个 $j$,则不需要新建临时数组。

为了避免浮点数误差,计算 $\textit{maxNum}$ 时可以取 $\textit{maxNum} = \lfloor \sqrt[x]{n + 0.5} \rfloor$。

代码

下面的代码为不优化空间的实现。

###Java

class Solution {
    static final int MODULO = 1000000007;

    public int numberOfWays(int n, int x) {
        int maxNum = (int) Math.pow(n + 0.5, 1.0 / x);
        int[][] dp = new int[maxNum + 1][n + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= maxNum; i++) {
            int power = (int) Math.pow(i, x);
            for (int j = 0; j <= n; j++) {
                if (j < power) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - power]) % MODULO;
                }
            }
        }
        return dp[maxNum][n];
    }
}

###C#

public class Solution {
    const int MODULO = 1000000007;

    public int NumberOfWays(int n, int x) {
        int maxNum = (int) Math.Pow(n + 0.5, 1.0 / x);
        int[][] dp = new int[maxNum + 1][];
        for (int i = 0; i <= maxNum; i++) {
            dp[i] = new int[n + 1];
        }
        dp[0][0] = 1;
        for (int i = 1; i <= maxNum; i++) {
            int power = (int) Math.Pow(i, x);
            for (int j = 0; j <= n; j++) {
                if (j < power) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - power]) % MODULO;
                }
            }
        }
        return dp[maxNum][n];
    }
}

下面的代码为优化空间的实现。

###Java

class Solution {
    static final int MODULO = 1000000007;

    public int numberOfWays(int n, int x) {
        int maxNum = (int) Math.pow(n + 0.5, 1.0 / x);
        int[] dp = new int[n + 1];
        dp[0] = 1;
        for (int i = 1; i <= maxNum; i++) {
            int power = (int) Math.pow(i, x);
            for (int j = n; j >= power; j--) {
                dp[j] = (dp[j] + dp[j - power]) % MODULO;
            }
        }
        return dp[n];
    }
}

###C#

public class Solution {
    const int MODULO = 1000000007;

    public int NumberOfWays(int n, int x) {
        int maxNum = (int) Math.Pow(n + 0.5, 1.0 / x);
        int[] dp = new int[n + 1];
        dp[0] = 1;
        for (int i = 1; i <= maxNum; i++) {
            int power = (int) Math.Pow(i, x);
            for (int j = n; j >= power; j--) {
                dp[j] = (dp[j] + dp[j - power]) % MODULO;
            }
        }
        return dp[n];
    }
}

复杂度分析

代码中使用了 $\texttt{Math.pow}$ 方法,该方法调用了本地方法 $\texttt{StrictMath.pow}$,因此其时间与空间复杂度取决于本地方法的实现,此处的复杂度分析将该方法的时间复杂度和空间复杂度视为 $O(1)$。

  • 时间复杂度:$O(\sqrt[x]{n} \times n)$,其中 $n$ 和 $x$ 是给定的正整数。状态数是 $O(\sqrt[x]{n} \times n)$,每个状态的计算时间是 $O(1)$,因此时间复杂度是 $O(\sqrt[x]{n} \times n)$。

  • 空间复杂度:$O(\sqrt[x]{n} \times n)$ 或 $O(n)$,其中 $n$ 和 $x$ 是给定的正整数。空间复杂度取决于实现方式,不优化空间的实现需要创建大小为 $(\lfloor \sqrt[x]{n} \rfloor + 1) \times (n + 1)$ 的二维数组因此空间复杂度是 $O(\sqrt[x]{n} \times n)$,优化空间的实现需要创建长度为 $n + 1$ 的一维数组因此空间复杂度是 $O(n)$。

❌
❌