普通视图
一次常规的升级,一个适合更多人的选择:新款 iPad Air 首发体验
每日一题-找出所有稳定的二进制数组 I🟡
给你 3 个正整数 zero ,one 和 limit 。
一个 二进制数组 arr 如果满足以下条件,那么我们称它是 稳定的 :
- 0 在
arr中出现次数 恰好 为zero。 - 1 在
arr中出现次数 恰好 为one。 -
arr中每个长度超过limit的 子数组 都 同时 包含 0 和 1 。
请你返回 稳定 二进制数组的 总 数目。
由于答案可能很大,将它对 109 + 7 取余 后返回。
示例 1:
输入:zero = 1, one = 1, limit = 2
输出:2
解释:
两个稳定的二进制数组为 [1,0] 和 [0,1] ,两个数组都有一个 0 和一个 1 ,且没有子数组长度大于 2 。
示例 2:
输入:zero = 1, one = 2, limit = 1
输出:1
解释:
唯一稳定的二进制数组是 [1,0,1] 。
二进制数组 [1,1,0] 和 [0,1,1] 都有长度为 2 且元素全都相同的子数组,所以它们不稳定。
示例 3:
输入:zero = 3, one = 3, limit = 2
输出:14
解释:
所有稳定的二进制数组包括 [0,0,1,0,1,1] ,[0,0,1,1,0,1] ,[0,1,0,0,1,1] ,[0,1,0,1,0,1] ,[0,1,0,1,1,0] ,[0,1,1,0,0,1] ,[0,1,1,0,1,0] ,[1,0,0,1,0,1] ,[1,0,0,1,1,0] ,[1,0,1,0,0,1] ,[1,0,1,0,1,0] ,[1,0,1,1,0,0] ,[1,1,0,0,1,0] 和 [1,1,0,1,0,0] 。
提示:
1 <= zero, one, limit <= 200
找出所有稳定的二进制数组 I
方法一:动态规划
题目要求二进制数组 $\textit{arr}$ 中每个长度超过 $\textit{limit}$ 的子数组同时包含 $0$ 和 $1$,这个条件等价于二进制数组 $\textit{arr}$ 中每个长度等于 $\textit{limit} + 1$ 的子数组都同时包含 $0$ 和 $1$(读者可以思考一下证明过程)。
按照题目要求,我们需要将 $\textit{zero}$ 个 $0$ 和 $\textit{one}$ 个 $1$ 依次填入二进制数组 $\textit{arr}$,使用 $\textit{dp}_0[i][j]$ 表示已经填入 $i$ 个 $0$ 和 $\textit{j}$ 个 $1$,并且最后填入的数字为 $0$ 的可行方案数目,$\textit{dp}_1[i][j]$ 表示已经填入 $i$ 个 $0$ 和 $\textit{j}$ 个 $1$,并且最后填入的数字为 $1$ 的可行方案数目。对于 $\textit{dp}_0[i][j]$,我们分析一下它的转换方程:
-
当 $j = 0$ 且 $i \in [0, \min(\textit{zero}, \textit{limit})]$ 时:我们可以不断地填入 $0$,所以 $\textit{dp}_0[i][j] = 1$。
-
当 $i = 0$,或者 $j = 0$ 且 $i \notin [0, \min(\textit{zero}, \textit{limit})]$ 时:我们没法构造可行的方案,所以 $\textit{dp}_0[i][j] = 0$。
-
当 $i > 0$ 且 $j > 0$ 时:$\textit{dp}_0[i][j]$ 可以分别由 $\textit{dp}_0[i - 1][j]$ 和 $\textit{dp}_1[i - 1][j]$ 转移而来,分别考虑两种情况:
-
对于 $\textit{dp}_1[i - 1][j]$:显然可以通过在 $\textit{dp}_1[i - 1][j]$ 对应的所有填入方案后再填入一个 $0$ 得到对应的可行方案。
-
对于 $\textit{dp}_0[i - 1][j]$:当 $i \le \textit{limit}$ 时,显然可以通过在 $\textit{dp}_1[i - 1][j]$ 对应的所有填入方案后再填入一个 $0$ 得到对应的可行方案;当 $i \gt \textit{limit}$ 时,我们需要去除一些不可行的方案数。因为 $\textit{dp}_0[i - 1][j]$ 对应的所有填入方案都是可行的,而只有一种情况会在额外填入一个 $0$ 时,变成不可行,即先前已经连续填入 $\textit{limit}$ 个 $0$,对应的方案数为 $\textit{dp}_1[i - \textit{limit} - 1][j]$。
-
根据以上分析,我们有 $\textit{dp}_0[i][j]$ 的转移方程:
$$
\textit{dp}_0[i][j] = \begin{cases}
1, & i \in [0, \min(\textit{zero}, \textit{limit})], j = 0 \
\textit{dp}_1[i - 1][j] + \textit{dp}_0[i - 1][j] - \textit{dp}_1[i - \textit{limit} - 1][j], & i > limit, j > 0 \
\textit{dp}_1[i - 1][j] + \textit{dp}_0[i - 1][j], & i \in [0, limit], j > 0 \
0, & otherwise
\end{cases}
$$
同理,我们也可以获得 $\textit{dp}_1[i][j]$ 的转移方程:
$$
\textit{dp}_1[i][j] = \begin{cases}
1, & i = 0, j \in [0, \min(\textit{one}, \textit{limit})] \
\textit{dp}_0[i][j - 1] + \textit{dp}_1[i][j - 1] - \textit{dp}_0[i][j - \textit{limit} - 1], & i > 0, j > limit \
\textit{dp}_0[i][j - 1] + \textit{dp}_1[i][j - 1], & i > 0, j \in [0, limit] \
0, & otherwise
\end{cases}
$$
最后,稳定二进制数组的数目等于 $\textit{dp}_0[\textit{zero}][\textit{one}] + \textit{dp}_1[\textit{zero}][\textit{one}]$。
###C++
class Solution {
public:
int numberOfStableArrays(int zero, int one, int limit) {
vector<vector<vector<long long>>> dp(zero + 1, vector<vector<long long>>(one + 1, vector<long long>(2)));
long long mod = 1e9 + 7;
for (int i = 0; i <= min(zero, limit); i++) {
dp[i][0][0] = 1;
}
for (int j = 0; j <= min(one, limit); j++) {
dp[0][j][1] = 1;
}
for (int i = 1; i <= zero; i++) {
for (int j = 1; j <= one; j++) {
if (i > limit) {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % mod + mod) % mod;
if (j > limit) {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % mod + mod) % mod;
}
}
return (dp[zero][one][0] + dp[zero][one][1]) % mod;
}
};
###Java
class Solution {
public int numberOfStableArrays(int zero, int one, int limit) {
final long MOD = 1000000007;
long[][][] dp = new long[zero + 1][one + 1][2];
for (int i = 0; i <= Math.min(zero, limit); i++) {
dp[i][0][0] = 1;
}
for (int j = 0; j <= Math.min(one, limit); j++) {
dp[0][j][1] = 1;
}
for (int i = 1; i <= zero; i++) {
for (int j = 1; j <= one; j++) {
if (i > limit) {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % MOD + MOD) % MOD;
if (j > limit) {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % MOD + MOD) % MOD;
}
}
return (int) ((dp[zero][one][0] + dp[zero][one][1]) % MOD);
}
}
###C#
public class Solution {
public int NumberOfStableArrays(int zero, int one, int limit) {
const long MOD = 1000000007;
long[][][] dp = new long[zero + 1][][];
for (int i = 0; i <= zero; i++) {
dp[i] = new long[one + 1][];
for (int j = 0; j <= one; j++) {
dp[i][j] = new long[2];
}
}
for (int i = 0; i <= Math.Min(zero, limit); i++) {
dp[i][0][0] = 1;
}
for (int j = 0; j <= Math.Min(one, limit); j++) {
dp[0][j][1] = 1;
}
for (int i = 1; i <= zero; i++) {
for (int j = 1; j <= one; j++) {
if (i > limit) {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % MOD + MOD) % MOD;
if (j > limit) {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % MOD + MOD) % MOD;
}
}
return (int) ((dp[zero][one][0] + dp[zero][one][1]) % MOD);
}
}
###Go
func numberOfStableArrays(zero int, one int, limit int) int {
dp := make([][][2]int, zero + 1)
mod := int(1e9 + 7)
for i := 0; i <= zero; i++ {
dp[i] = make([][2]int, one + 1)
}
for i := 0; i <= min(zero, limit); i++ {
dp[i][0][0] = 1
}
for j := 0; j <= min(one, limit); j++ {
dp[0][j][1] = 1
}
for i := 1; i <= zero; i++ {
for j := 1; j <= one; j++ {
if i > limit {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1]
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1]
}
dp[i][j][0] = (dp[i][j][0] % mod + mod) % mod
if j > limit {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0]
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0]
}
dp[i][j][1] = (dp[i][j][1] % mod + mod) % mod
}
}
return (dp[zero][one][0] + dp[zero][one][1]) % mod
}
###Python
class Solution:
def numberOfStableArrays(self, zero: int, one: int, limit: int) -> int:
dp = [[[0, 0] for _ in range(one + 1)] for _ in range(zero + 1)]
mod = int(1e9 + 7)
for i in range(min(zero, limit) + 1):
dp[i][0][0] = 1
for j in range(min(one, limit) + 1):
dp[0][j][1] = 1
for i in range(1, zero + 1):
for j in range(1, one + 1):
if i > limit:
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1]
else:
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1]
dp[i][j][0] = (dp[i][j][0] % mod + mod) % mod
if j > limit:
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0]
else:
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0]
dp[i][j][1] = (dp[i][j][1] % mod + mod) % mod
return (dp[zero][one][0] + dp[zero][one][1]) % mod
###C
#define MOD 1000000007
int numberOfStableArrays(int zero, int one, int limit) {
long long dp[zero + 1][one + 1][2];
memset(dp, 0, sizeof(dp));
for (int i = 0; i <= (zero < limit ? zero : limit); ++i) {
dp[i][0][0] = 1;
}
for (int j = 0; j <= (one < limit ? one : limit); ++j) {
dp[0][j][1] = 1;
}
for (int i = 1; i <= zero; ++i) {
for (int j = 1; j <= one; ++j) {
if (i > limit) {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % MOD + MOD) % MOD;
if (j > limit) {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % MOD + MOD) % MOD;
}
}
int result = (dp[zero][one][0] + dp[zero][one][1]) % MOD;
return result;
}
###JavaScript
const MOD = 1000000007;
var numberOfStableArrays = function(zero, one, limit) {
const dp = Array.from({ length: zero + 1 }, () =>
Array.from({ length: one + 1 }, () => [0, 0])
);
for (let i = 0; i <= Math.min(zero, limit); i++) {
dp[i][0][0] = 1;
}
for (let j = 0; j <= Math.min(one, limit); j++) {
dp[0][j][1] = 1;
}
for (let i = 1; i <= zero; i++) {
for (let j = 1; j <= one; j++) {
if (i > limit) {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % MOD + MOD) % MOD;
if (j > limit) {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % MOD + MOD) % MOD;
}
}
return (dp[zero][one][0] + dp[zero][one][1]) % MOD;
};
###TypeScript
const MOD = 1000000007;
function numberOfStableArrays(zero: number, one: number, limit: number): number {
const dp: number[][][] = Array.from({ length: zero + 1 }, () =>
Array.from({ length: one + 1 }, () => [0, 0])
);
for (let i = 0; i <= Math.min(zero, limit); i++) {
dp[i][0][0] = 1;
}
for (let j = 0; j <= Math.min(one, limit); j++) {
dp[0][j][1] = 1;
}
for (let i = 1; i <= zero; i++) {
for (let j = 1; j <= one; j++) {
if (i > limit) {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % MOD + MOD) % MOD;
if (j > limit) {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % MOD + MOD) % MOD;
}
}
return (dp[zero][one][0] + dp[zero][one][1]) % MOD;
};
###Rust
const MOD: i32 = 1000000007;
impl Solution {
pub fn number_of_stable_arrays(zero: i32, one: i32, limit: i32) -> i32 {
let mut dp = vec![vec![vec![0; 2]; one as usize + 1]; zero as usize + 1];
for i in 0..=zero.min(limit) as usize {
dp[i][0][0] = 1;
}
for j in 0..=one.min(limit) as usize {
dp[0][j][1] = 1;
}
for i in 1..=zero as usize {
for j in 1..=one as usize {
if i > limit as usize {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit as usize - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % MOD + MOD) % MOD;
if j > limit as usize {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit as usize - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % MOD + MOD) % MOD;
}
}
(dp[zero as usize][one as usize][0] + dp[zero as usize][one as usize][1]) % MOD
}
}
复杂度分析
-
时间复杂度:$O(\textit{zero} \times \textit{one})$,其中 $\textit{zero}$ 和 $\textit{one}$ 分别为 $0$ 和 $1$ 的出现次数。
-
空间复杂度:$O(\textit{zero} \times \textit{one})$。
两种方法:动态规划 / 组合数学(Python/Java/C++/Go)
本题和双周赛第四题是一样的,请看 我的题解。
逐步优化 DP 状态
前记:读了读别人的题解,感觉 newhar 老师的思路更清晰易懂一点,推荐大家看 newhar 的题解,这里就记一下自己的解法。
解法:DP
以下题解中把 limit 简记为 $l$。
转化题目条件
首先,题目要求序列中恰好出现 zero 个 $0$ 以及 one 个 $1$,显然序列的长度必须为 zero + one。记序列的长度为 $n$。
只要一个子数组包含 $0$ 和 $1$,那么所有完全包含它的子数组也会包含 $0$ 和 $1$。因此,题目中“arr 中每个长度超过 $l$ 的子数组都同时包含 $0$ 和 $1$”这一条件可以重写为:
arr中每个长度恰为 $(l + 1)$ 的子数组都同时包含 $0$ 和 $1$。
设计 DP 状态
考虑任一下标 $i$,假设 $i$ 左边最近的 $0$ 到 $i$ 的距离是 $p_i$($p_i = 0$ 说明 arr[i] == 0),$i$ 左边最近的 $1$ 到 $i$ 的距离是 $q_i$($q_i = 0$ 说明 arr[i] == 1)。不妨假设下标 $i$ 是某个长度为 $(l + 1)$ 的子数组的右端点,因为这个子数组里既有 $0$ 又有 $1$,那么我们同时有 $p_i \le l$ 以及 $q_i \le l$。
由此可以设计出朴素的 DP 状态。设 $f(i, j, p, q)$ 表示考虑序列的前 $i$ 个元素中有 $j$ 个 $1$,且 $i$ 到最近的 $0$ 距离为 $p$,到最近的 $1$ 距离为 $q$ 的方案数。但这样状态总数为 $\mathcal{O}(n^2l^2)$,无论是 C 题还是 D 题都无法通过。
优化 DP 状态
注意到元素要么是 $0$ 要么是 $1$,也就是说对于任意的 $i$,$p = 0$ 和 $q = 0$ 恰有一个成立。因此可以将 DP 状态压缩为 $f(i, j, t = 0/1, d)$ 表示考虑序列的前 $i$ 个元素中有 $j$ 个 $1$,且第 $i$ 个元素填的是 $t$,$i$ 到最近的另一种元素(即 $1 - t$)距离为 $d$ 的方案数。这样状态总数为 $\mathcal{O}(n^2l)$。
转移方程分为两部分。
f(i, j, 0, d) += f(i - 1, j, 0, d - 1)
f(i, j, 1, d) += f(i - 1, j - 1, 1, d - 1)
这一部分的含义是,让第 $i$ 个元素和第 $(i - 1)$ 个元素相同,那么到另一种元素的距离自然增加 $1$。
f(i, j, 0, 1) += f(i - 1, j, 1, *)
f(i, j, 1, 1) += f(i - 1, j - 1, 0, *)
这一部分的含义是,让第 $i$ 个元素和第 $(i - 1)$ 个元素不同,那么到另一种元素(也就是上一个元素)的距离就是 $1$。这里的星号通过维护前缀和即可 $\mathcal{O}(1)$ 计算。
初值就是枚举第一个元素填 $0$ 还是 $1$。对于第一个元素来说,另一种元素已经 $1$ 个位置没出现了,所以 $d = 1$,即 $f(1, 0, 0, 1) = f(1, 1, 1, 1) = 1$。
整体复杂度 $\mathcal{O}(n^2l)$,可以通过 C 题。
继续优化 DP 状态
我们发现,DP 主要的运算复杂度来自转移方程的第一部分。第一部分的转移方程看起来很简单,好像只是把上一个位置所有 $(d - 1)$ 的 DP 值都照搬过来而已。由于 $d \le l$ 的限制,只是丢弃了 $f(i - 1, j, *, l)$ 的 DP 值。而 DP 方程的第二部分没有丢弃任何 DP 值,通过前缀和直接把所有情况照单全收。
这里其实在暗示我们:只要上个位置距离另一种元素的距离不到 $l$,那么当前位置无论填什么都是安全的。只有上个位置距离另一种元素的距离恰为 $l$ 时,当前位置必须填与上个位置不同的元素。也就是说:
(当前位置填元素 t 的方案数) = (上个位置的所有方案数) - (元素 1 - t 最近一次在 i - limit - 1 出现,从 i - limit 到 i 全部都是元素 t 的方案数)
设 $f(i, j, t = 0/1)$ 表示考虑序列的前 $i$ 个元素中有 $j$ 个 $1$,且第 $i$ 个元素填的是 $t$ 的方案数。“元素 $(1 - t)$ 最近一次在 $(i - l - 1)$ 出现,从 $(i - l)$ 到 $i$ 全部都是元素 $t$ 的方案数”怎么算呢?当然就是 $f(i - l - 1, j', 1 - t)$。这里 $j'$ 要看如果 $t = 0$ 那么 $j' = j$,否则如果 $t = 1$ 那么由于从 $(i - l)$ 到 $i$ 全部都是 $1$,则 $j' = j - l - 1$。
由此得到转移方程.
f(i, j, 0) = f(i - 1, j, 0) + f(i - 1, j, 1) - f(i - l - 1, j, 1)
f(i, j, 1) = f(i - 1, j - 1, 0) + f(i - 1, j - 1, 1) - f(i - l - 1, j - l - 1, 0)
整体复杂度 $\mathcal{O}(n^2)$,可以通过 D 题。
但是,实现过程中有一个初值的细节问题。当 $i = l + 1$ 时,我们要求 “元素 $(1 - t)$ 最近一次在下标 $0$ 出现的方案数”。空序列一定是合法的,所以有 $f(0, 0, 0) = f(0, 0, 1) = 1$。然而当 $i = 1$ 的时候,这样的初值设置会导致 $f(i - 1, j, 0) + f(i - 1, j, 1)$ 这部分的转移方程出现重复计算,所以我们直接继续设置初值 $f(1, 0, 0) = f(1, 1, 1) = 1$。
参考代码(c++)
###c++
class Solution {
public:
int numberOfStableArrays(int zero, int one, int limit) {
const int MOD = 1e9 + 7;
int n = zero + one;
long long g[n + 1][one + 1][2];
memset(g, 0, sizeof(g));
g[0][0][0] = g[0][0][1] = g[1][0][0] = g[1][1][1] = 1;
auto update = [&](long long &x, long long y) {
x = (x + y) % MOD;
};
for (int i = 2; i <= n; i++) {
for (int j = 0; j <= one; j++) {
update(g[i][j][0], g[i - 1][j][1] + g[i - 1][j][0]);
if (i > limit) update(g[i][j][0], MOD - g[i - 1 - limit][j][1]);
if (j > 0) update(g[i][j][1], g[i - 1][j - 1][0] + g[i - 1][j - 1][1]);
if (i > limit && j > limit) update(g[i][j][1], MOD - g[i - 1 - limit][j - 1 - limit][0]);
}
}
return (g[n][one][0] + g[n][one][1]) % MOD;
}
};
iPhone 17e 上手体验:「苹替」的平替,今年少有的性价比?
![]()
如果说,去年在官网看到 iPhone 16e,我有一种非常「下头」的感觉,那么今年的 iPhone 17e,却让我「上头」了好几天。
和 iPhone 标准版对比,iPhone 17e 还是那台刀法精准的「入门款」,但整台手机给人的气质,又完全不同。
爱范儿已经提前拿到了 iPhone 17e,我们给它找了三个对手互相 PK,一起来看看,这台「真香机」究竟值不值得买。
![]()
VS iPhone 16e:升级都踩在点子上
不得不说,苹果对 iPhone 17e 的几个改动,都是相当精准和到点的。
从外观上看,iPhone 17e 和 iPhone 16e 几乎毫无差别,正面依旧「刘海」,背面还是「单摄」——不对,iPhone 17e 这次还多了一个全新的「淡粉」配色。
![]()
▲ 左:iPhone 16e;右:iPhone 17e,如果不是新配色,几乎完全没差别
iPhone 16e 只有黑白两种基础配色,仿佛作为一款入门机型,不值得花心思选个好看的配色。
今年的 iPhone 17e 的淡粉色不仅颜值高,还是 iPhone 17 系列中唯一一款粉配色,属于是标准版和 Pro 都没有的待遇。
新颜色是外在升级,而 iPhone 17e 的一个内在升级,则是 MagSafe 磁吸这个呼声极高的功能,终于补上了。
![]()
不管是充电宝、补光灯、卡包还是散热背夹,iPhone 17e 不用再靠手机壳,能够无缝解锁整个磁吸生态。
更重要的是,支持 MagSafe 后,iPhone 17e 无线充电功率最高可达 15W,和 iPhone 17 Pro 保持一致,是 iPhone 16e Qi 无线充电功率的两倍。
和配色类似,即使你不使用 MagSafe,它依然是一个「加分项」,提升整机性价比,同时让人感觉它更像是「iPhone 17 家族」的一员。
![]()
虽然正面屏幕还是 iPhone 14 同款,苹果这次给 iPhone 17e 换上了 iPhone 17 同款的第二代超瓷晶面板,根据官网的表述,抗刮划能力是 iPhone 16e 的 3 倍,可以省下一笔贴膜的钱。
另一个「看不见」的变化,则是 iPhone 17e 搭载的全新 A19 处理器。和前代一样,是标准版 6 核 CPU + 4 核 GPU 的「减配版」,对比 iPhone 17 少了一个 GPU 核心。
![]()
▲ iPhone 17 家族
去年的 iPhone 17 已经证明,制程更先进的 A19 对比上一代 A18 提升非常明显。我们用 iPhone 17e 和 iPhone 16e 跑了跑分和游戏,看看实际提升有多高。
从跑分来看,即使 iPhone 17e 和 iPhone 16e 一样同为 5 核 GPU,前者的 A19 芯片图形性能还是要强大不少。
![]()
《明日方舟:终末地》20 分钟,iPhone 17e 的平均帧率 43.1,iPhone 16e 的平均帧率 36.1,从帧率曲线看,iPhone 17e 也要更稳定,而 iPhone 16e 会在 30 帧和 60 帧之间反复横跳。总体来说游玩的体验相近,不过 iPhone 16e 的机身反而会没那么烫手。
![]()
影像从来不是 iPhone 16e 的强项,iPhone 17e 在硬件规格上也没明显提升,用它来记录生活可以,要求它出片就稍微有点强人所难了。
![]()
![]()
![]()
从各个方面来看,iPhone 17e 和 iPhone 16e 之间的差别不大,不过升级都算踩在了点子上——实用的 MagSafe,以及 A19 的强大性能。
iPhone 17e 在起售价保持 4499 元不变的同时,起始存储从 128GB 升级到了 256GB,对于还在用几年前小存储 iPhone 的用户来说,确实很值得升级。
即使是 iPhone 16e,全新的 256GB 版本,恐怕价格上也不会比 17e 低太多。
![]()
VS iPhone 17:有差距,但更有差价
那么,这么超值的 iPhone 17e,对比同样「挤爆牙膏」的 iPhone 17,又该如何抉择?
价格上,两者就已经有显著差异。去年,iPhone 16e 发布时,iPhone 16 国补渠道价已经来到了 4699 元,和 3999 的 iPhone 16e 没差太多,因此我们都更推荐方方面面更胜一筹的 iPhone 16。
目前,iPhone 17 的渠道价格在 5499 元左右,根据去年 iPhone 16e 的情况,可以合理推断 iPhone 17e 的国补价格大概在 3999 元,差价 1500 元。
这 1500 元,首先能买到一个 120Hz 的高刷屏幕,还是窄边框设计配灵动岛,这些配置让 iPhone 17 都更像一台「新 iPhone」,一块更好的屏幕,对使用体验的提升是立竿见影的。
![]()
至于影像能力,少一个超广角的焦段不必提,iPhone 17e 的主摄传感器尺寸还比 iPhone 17 要更小,因而解析力略逊一筹,暗光场景差距会明显一点。
![]()
▲ iPhone 17 天更蓝草更绿
![]()
![]()
比起硬件素质,更致命的是拍照功能上的缺失,特别是可调色摄影风格,以及前置自拍可以横图竖拍的 Center Stage 这两个口碑很好的功能,都在 iPhone 17e 上缺席了。
![]()
▲ iPhone 17 上的 Center Stage 自拍功能
考虑到这个世界上除了摄影师还有很多随手拍的玩家,我们之前也写过,在 Project Indigo 的加持下,即便同样单摄的 iPhone Air 也能成为街拍神器——所以影像方面就见仁见智了。
做工则是很多人会忽略,但上手就会发现的差异,iPhone 17e 后者边框和背板之间没有做圆润的过渡,并且磨砂玻璃后盖的工艺也要比 iPhone 17 要粗粝不少。
性能方面,两者的差异主要集中在 GPU 核心数量,这也实打实体现在了跑分上。
![]()
但从实际的游戏表现来看,iPhone 17 30 分钟《终末地》平均帧率 43.4,和 iPhone 17 的 43.1 可以说相差无几,后者会相对稳一点。
![]()
个人认为,这些体验、配置上的差距,确实对得起两台手机之间 1500 元的差价,苹果推出 iPhone 17e,某种程度更加「衬托」iPhone 17 的「真香」。
如果你对高刷、影像、性能都不感冒,只想要一台「好用」的手机,那无疑 iPhone 17e 是值得推荐的。
如果说,去年的 iPhone 16e 我总觉得买给父母有点「亏」,其他的选择更有性价比;iPhone 17 多出来的配置,对长辈而言也许不太敏感;在我心目中,iPhone 17e 就是父母和学生用机的第一选择。
![]()
最后,如果是双卡用户,购买 iPhone 17e 时要注意:iPhone 17e 只支持一个实体卡槽,如果想要双卡双待,至少其中一张卡必须是 eSIM。
VS 国产中端新机:「苹替」的平替?
不管是官网的配置,还是上面的对比评测, 其实都可以看出,iPhone 17e 和 iPhone 16e,进步其实并没有那么明显,无非就是 MagSafe、A19、新配色、256GB 起步。
去年,爱范儿认为 iPhone 16e 是一台「酱香型手机」,它还不错,只是不值得马上买,需要等渠道降价将它的性价比释放出来。
一年过去,情况已经大有不同。从去年年底开始,一场元器件成本的风暴席卷了整个消费电子行业,内存价格暴涨,所有品牌、所有价位的厂商,今年推出的新机大概率都免不了价格上涨、配置缩水的命运。
而原本 3500-4000 这个价位,也就是华为 nova、OPPO Reno、vivo S 这类「走量」的中端系列,将会显著受到这一波涨价的冲击,价格被迫提到之前的旗舰档,内存 8GB、存储 128GB 的手机可能还会重出江湖。
![]()
▲ vivo S50
在这种万马齐喑的行业寒冬时刻,苹果推出的入门款产品,不仅没涨价,还给加塞了个 MagSafe,以及 256GB 存储起步。
之所以去年的 iPhone 16e 被诟病「不够性价比」,也是和同价位国产手机比较略逊一筹;而现在对手涨价和缩水难免,逆潮流的 iPhone 17e 不仅有质价比,还「被迫」有了性价比。
有意思的是,这些国产中端机这几年喜欢用「苹替」一词进行包装和营销,而 iPhone 17e,某种意义上就是这些「苹替」的「平替」。
![]()
于是乎,苹果和国产品牌的处境,好像微妙地发生了置换。
过去,「性价比」从来不是苹果的标签,因此 Android 才有了走「便宜大碗」的生存空间,苹果这个价位没有的高刷大屏、超广长焦,全部都安排上。在 4000 元左右的中端档位,苹果用 iPhone SE、iPhone 16e 挑战,效果一直以来不算很好。
但现在,苹果凭借强大的供应链管理能力,价位和配置能顶着压力继续慷慨,而国产 Android 手机的定价,却只能随着成本水涨船高。
某种意义上说,面对比以往都显得更有诚意的 iPhone 17e,面临压力的国产 Android 旗舰反而成了「挑战者」。
![]()
当然,目前我们还不能知道国产中端机比以前贵多少,如果你还是觉得国补价的 iPhone 17e 买不下手,那我建议你再等等。
差不多两个月后,今年第一波国产入门机型也会发布,到时候不仅可以比较一下两者的配置、价格,顺便也有机会看到 iPhone 17e 的价格进一步走低。
这也许是今年少有的,还值得一等的消费电子产品了。
#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。
天赐材料:2025年净利润同比增长181%,拟每10股派3元
南侨食品:2025年归母净利润同比下降79.81%,拟10派0.3元
热门中概股美股盘前涨跌不一,小鹏汽车涨超3%
美股大型科技股盘前普跌,Arm跌超2%
八亿时空:全资子公司拟1.56亿元出售南通詹鼎11.5892%股权
宁德时代:拟注册发行不超过400亿元债券
网易LobsterAI接入企业微信、QQ、飞书、钉钉
*ST松发:申请撤销公司股票退市风险警示
宁德时代:2025年净利润722亿元,同比增长42.28%
同花顺:2025年净利润32.05亿元,同比增长75.79%
揭秘闪充建站成本,比亚迪也玩「百亿补贴」!
![]()
当时发布的时候,我们充电桩的数量,以及很多软硬件当时都还没有。现在回过头来看,这些都是教训。当然,也是财富了。
比亚迪闪充发布会后,爱范儿和董车会参加了一场媒体沟通会。会上,比亚迪集团品牌及公关处总经理李云飞用这样一段话,复盘了去年初发布第一代兆瓦闪充技术的经历。
有了上一次的经验,比亚迪这次换了一种打法。
在喊出「9 分钟充饱」这一口号之前,他们已经派出了 2000 人的团队,在全国各地建好了 4239 座闪充站。到今年年底,这个计划的目标是两万座。
![]()
用最省钱的方式,花掉上百亿
确认了建站目标,大家最关心的自然是资金投入。
一套包含储能模块的闪充桩造价并不低,李云飞在采访中给出了大致的数字,每套装置的成本在大几十万元的级别。按照今年两万座的建站规划,这笔账目算下来需要实打实地掏出上百亿元。
「几百亿对比亚迪目前的体量来说没有任何问题,这笔钱必须要花。」李云飞说。
虽说是几百亿的大生意,但比亚迪具体的推行方式却非常「轻量化」,和特斯拉、小鹏、理想的那种自建站的方式并不一样。
![]()
实际上,比亚迪采用了一套最省钱的方法来铺开充电网络。他们大量推行「站中站」模式,也就是在现有的第三方充电站里直接安装一根或多根闪充设备。而在官方公布的这 2 万座闪充站里,有 18000 座都是属于站中站模式。
李云飞详细描述了这套设备的现场施工流程:
我们现在去外部建这个闪充站的时候,都不需要去挖地基了,(整个流程)就跟装空调一样。发过去以后的话,直接装一个地台,金属框架上面有螺丝,往地下一打,直接(把充电桩)放上去就行,电一接就完事了。
这种极度简化的施工过程,得益于设备自带的储能系统。因为不需要向地方电网申请额外的容量配额,原有的线路就足以支撑储能电池在夜间或闲时进行蓄电。
![]()
对于第三方场站的运营方来说,这种合作模式也有着很强的吸引力。
李云飞透露了前期实地调研的数据,目前市面上很多公共充电站的平均利用率只有 5% 左右。大量车位在闲置,充电枪的翻台率极低。
我们过去以后,合作方是非常欢迎的。就相当于他开了一个饭馆,我们说我们带朋友带客人去,我们还自带锅碗瓢盆,且可以给他带来巨大的客流。
李云飞用通俗的语言解释了这种互利关系。自带储能设备的闪充站入驻后,能吸引自家的闪充车主,也会因为对全行业开放而带来其他品牌的用户。比亚迪认为,这会大幅提升原有场站的整体利用率。
为了配合快速铺开的建站节奏,比亚迪在发布会上推出了一个「圆梦建站」的计划。只要凑够 4 个车主提出建站需求,官方承诺会在一周内完成实地考察并建好新站。
![]()
李云飞坦言这是向行业学习的营销经验。从发布会当晚到第二天下午,后台已经收到了大量用户的建站需求。这套机制能够精准定位真实的补能盲区,把钱花在刀刃上。
除了基建层面的讨论,也有人好奇,第二代刀片电池全面推向市场后,现有的第一代产品会不会面临停产淘汰的局面。
李云飞给出了明确的答复。
没有清退一说,我们一代跟二代是并存的。我们也是想给客户更多的选择。有些客户对这个闪充无感,那还是考虑现有的第一代电池和现有的产品解决方案。
![]()
他补充提到,对于日常习惯在家里慢充的用户,哪怕充电时间被压缩到五分钟或者十分钟,他们也不会有太强烈的感知。因此,比亚迪希望能够保持产品线的多样性,让不同需求的用户都能找到合适的价格区间。
顺着家庭充电的场景,李云飞还提出了一个观点:随着公共快充站的普及和充电时间的缩短,家庭充电桩在未来可能会失去存在的意义。
「如果说充电跟加油一样快的话,还有必要去装家桩吗?」李云飞算了一笔账。
很多地方装家充桩受很多条件限制。你要装家充桩,首先物业得给你拉线、增容,这是要成本的。另外为了兼容交流慢充,车上还必须配一个 AC 交流充电机装置,也将近一两千块钱。这一进一出都是成本,虽然看似是客户掏钱,但实际上是整个社会的资源浪费。
![]()
在比亚迪看来,与其让车主和物业去死磕这些安装门槛,不如用一套覆盖极广、效率极高的公共闪充网络,来集中解决诸多电动化难题。
把选择权交还市场
在发布会上,为了制造足够的视觉冲击力,比亚迪重点展示了续航超过 1000 公里的纯电旗舰车型。但在比亚迪实际的销量基盘里,插电混动车型占据着极大的比重。
当被问及这些车型的充电表现时,李云飞在交流会现场做了一个补充说明。
无论是混动车还是纯电动车,无论它纯电的续航里程是多少,是 400 公里还是 1000 公里,无论是多少度电,它都能统一的做成 5 分钟充好,9 分钟充饱,零下 30 度只加 3 分钟。
这其实并不是比亚迪一家的独角戏,仔细观察当下的汽车市场,发力混动车型的充电速度已经成为头部车企的共识。以小鹏汽车发布的鲲鹏超级电动体系为例,这套主打增程混动的动力方案同样配备了 5C 超充电池,把电量从 10% 充到 80% 的时间压缩到了 12 分钟。
![]()
▲ 上周发布的小鹏 G6 超级增程版
补能效率的大幅提升,自然会让外界拿这套兆瓦闪充网络去和行业里现有的换电模式做对比。有人好奇这是否会引发一场激烈的路线之争。
李云飞给出了他的看法。
我觉得闪充和换电都挺好。比亚迪推出各种技术和品牌,目的就是给客户提供更多选择。对于行业来说,就需要百花齐放,殊途同归。看起来闪充和换电是不一样的路线,但共同路线都是促进油转电。
至于这些最顶尖的电池技术是否会对外供应,李云飞给出了肯定的答复。目前比亚迪的电池业务已经在给国内外十几个汽车品牌供货。第二代刀片电池未来同样会对外开放,但短期内需要优先满足内部需求,并留出足够的产能爬坡时间。
![]()
回到整车产品线的规划上,闭门交流会还透露了一个重要的策略转变。
过去几年,比亚迪在高端品牌上有着很强的技术捆绑执念。李云飞在现场进行了反思。
「我们原来也有一种执念,技术一定要区别。仰望就是易四方,其他品牌想要易四方,不行,不给。」李云飞觉得这种做法存在局限性,因为随着市场竞争加剧,消费者对价格变得极其敏感。
如今为了顺应市场,他们开始进行大刀阔斧的配置解绑。
以刚刚发布的新车为例,腾势 Z9GT 推出了单电机版本,起售价从当初的 33.48 万元来到了 26.98 万元,仰望 U7 搭载的云辇-Z 智能悬架系统也不再是全系强制标配。
![]()
「有些客户对易三方无感,你非得给他加这么多硬件,硬件冗余就是成本,成本的话就是价格。所以我们还是遵从市场,遵从客户的考虑。」李云飞表示,把是否需要极致操控的选择权交还给消费者,通过降低非必要硬件的冗余,可以提供更多元的价格区间,让产品覆盖更广的人群。
过去几年,国产高端新能源车热衷于通过疯狂堆砌旗舰级软硬件来拉高品牌定位。但在价格战的持续洗礼下,普通用户变得越来越精打细算。一些用户开始意识到,自己并不需要为那些日常通勤几乎用不到的东西买单。比如,云辇-Z 和易三方。
![]()
比亚迪的目标人群同样覆盖海外。
「我们在海外闪充站的建设落地可能会晚于国内,大概在 2026 年年底。」李云飞说。
他认为,现阶段的国内消费者「非常幸福」。在燃油车时代,外资品牌的新车往往在海外卖了好几代才会引入中国。而在如今的智能化和电动化时代,情况完全反过来了。
中国品牌基本上在国内先销售一两年,逐渐把国内的车型再引入到海外。最好的产品,最好的价格,最好的服务都在国内。
#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。