【LeetCode 每日一题】2197. 替换数组中的非互质数
Problem: 2197. 替换数组中的非互质数
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N * log U)
- 空间复杂度:O(N)
整体思路
这段代码旨在解决一个数组变换问题:给定一个整数数组 nums
,需要不断地合并相邻的、不互质(最大公约数 > 1)的两个数,直到数组中任意相邻的两个数都互质为止。合并规则是将这两个数替换为它们的最小公倍数 (LCM)。
该算法巧妙地利用了 栈 (Stack) 的思想来模拟这个合并过程。虽然代码中使用的是 ArrayList
,但其 add
(入栈)、getLast
(窥视栈顶)、removeLast
(出栈)等操作完全符合栈的“后进先出” (LIFO) 特性。
-
核心思想:栈式处理
- 算法将最终的结果数组看作一个栈
st
。它从左到右遍历输入的nums
数组,逐个处理每个数x
。 - 当处理一个新数
x
时,它需要与栈顶的元素进行比较。如果它们不互质,就需要合并。合并后的新数可能会继续与新的栈顶元素不互质,所以这个合并过程需要持续进行,直到新数与栈顶元素互质,或者栈为空。
- 算法将最终的结果数组看作一个栈
-
算法步骤:
- 初始化一个空的列表(作为栈)
st
。 - 遍历
nums
数组中的每一个元素x
。 - 对于每个
x
,进入一个while
循环:
a. 检查条件:!st.isEmpty() && gcd(x, st.getLast()) > 1
。这个条件判断栈是否为空,以及当前数x
是否与栈顶元素st.getLast()
不互质。
b. 合并操作:如果条件满足,说明需要合并。
*int y = st.removeLast();
:将栈顶元素y
弹出。
*x = (x / gcd(x, y) * y);
:计算x
和y
的最小公倍数 (LCM),并将结果赋给x
。这里使用了lcm(x, y) = (|x * y|) / gcd(x, y)
的公式,为了防止溢出,写成了(x / gcd(x, y)) * y
的形式。
c. 循环继续:合并后得到的新x
,需要继续与新的栈顶元素进行比较,因此while
循环会继续执行,直到x
与栈顶互质或栈为空。 - 入栈:当
while
循环结束后,说明当前的x
(可能是原始的x
,也可能是多次合并后的结果)已经可以安全地放在栈顶了。执行st.add(x)
,将其压入栈中。
- 初始化一个空的列表(作为栈)
-
gcd
辅助函数- 算法依赖于一个
gcd
函数来计算两个数的最大公约数 (Greatest Common Divisor)。 - 代码中实现的是经典的欧几里得算法(辗转相除法),这是一个非常高效的计算最大公约数的方法。
- 算法依赖于一个
-
返回结果
- 当
nums
数组中的所有元素都处理完毕后,栈st
中剩下的元素就是最终的结果序列,将其返回。
- 当
完整代码
import java.util.ArrayList;
import java.util.List;class Solution {/*** 合并数组中所有不互质的相邻元素,直到任意相邻元素都互质。* @param nums 输入的整数数组* st.add(x);}return st;}/*** 使用欧几里得算法(辗转相除法)计算两个整数的最大公约数 (GCD)。* @param x 第一个整数* @param y 第二个整数* @return x 和 y 的最大公约数*/private int gcd(int x, int y) {// 循环直到余数为 0while (y != 0) {int temp = y;y = x % y; // 新的 y 是 x 除以 y 的余数x = temp; // 新的 x 是原来的 y}// 当 y 为 0 时,x 就是最大公约数return x;}
}
时空复杂度
- 设
N
是nums
数组的长度。 - 设
U
是nums
数组中元素的最大值。
时间复杂度:O(N * log U)
- 外层循环:
for (int x : nums)
遍历nums
数组一次,执行N
次。 - 内层
while
循环:- 这个循环的行为是“合并”式的。每次循环都会消耗掉栈顶的一个元素。
- 在整个算法的生命周期中,一个元素最多被压入栈一次,也最多被弹出一次。
- 因此,
while
循环的总执行次数(在所有外层循环中累加)不会超过N
次。
gcd
函数:- 欧几里得算法的复杂度与输入数值的对数成正比。计算
gcd(x, y)
的时间复杂度大约是 O(log(min(x, y)))。 - 在我们的算法中,每次调用
gcd
,其参数都与输入中的数值或它们的LCM相关。我们可以用U
(输入最大值) 作为其复杂度的上界,即 O(log U)。
- 欧几里得算法的复杂度与输入数值的对数成正比。计算
综合分析:
总的时间复杂度大致是 (外层循环和内层循环的总迭代次数) * (单次 gcd
的时间)。
由于总迭代次数是 O(N) 级别,每次迭代都可能调用 gcd
,所以总的时间复杂度是 O(N * log U)。
空间复杂度:O(N)
- 主要存储开销:算法使用了一个列表
st
来作为栈。 - 空间大小:
- 在最坏的情况下,如果
nums
数组中的所有元素都两两互质,那么st
栈将会存储所有N
个元素。 - 因此,
st
占用的空间与输入规模N
成线性关系。
- 在最坏的情况下,如果
gcd
函数:gcd
函数是迭代实现的,只使用了常数个额外变量,空间复杂度是 O(1)。
综合分析:
算法所需的额外空间主要由栈 st
决定。因此,其空间复杂度为 O(N)。