@@ -56,13 +56,13 @@ tags:
5656
5757### 方法一:DFS + 剪枝
5858
59- 根据题意,我们需要将数组 ` nums ` 划分为 $k$ 个子集,且每个子集的和相等。因此,先累加 ` nums ` 中所有元素的和,如果不能被 $k$ 整除,说明无法划分为 $k$ 个子集,提前返回 ` false ` 。
59+ 根据题意,我们需要将数组 $\textit{ nums}$ 划分为 $k$ 个子集,且每个子集的和相等。因此,先累加 $\textit{ nums}$ 中所有元素的和,如果不能被 $k$ 整除,说明无法划分为 $k$ 个子集,提前返回 $\textit{ false}$ 。
6060
61- 如果能被 $k$ 整除,不妨将每个子集期望的和记为 $s$,然后创建一个长度为 $k$ 的数组 ` cur ` ,表示当前每个子集的和。
61+ 如果能被 $k$ 整除,不妨将每个子集期望的和记为 $s$,然后创建一个长度为 $k$ 的数组 $\textit{ cur}$ ,表示当前每个子集的和。
6262
63- 对数组 ` nums ` 进行降序排序(减少搜索次数),然后从第一个元素开始,依次尝试将其加入到 ` cur ` 的每个子集中。这里如果将 ` nums[i] ` 加入某个子集 ` cur[j] ` 后,子集的和超过 $s$,说明无法放入,可以直接跳过;另外,如果 ` cur[j] ` 与 ` cur[j - 1] ` 相等,意味着我们在 ` cur[j - 1] ` 的时候已经完成了搜索,也可以跳过当前的搜索。
63+ 对数组 $\textit{ nums}$ 进行降序排序(减少搜索次数),然后从第一个元素开始,依次尝试将其加入到 $\textit{ cur}$ 的每个子集中。这里如果将 $\textit{ nums} [ i] $ 加入某个子集 $\textit{ cur} [ j] $ 后,子集的和超过 $s$,说明无法放入,可以直接跳过;另外,如果 $\textit{ cur} [ j] $ 与 $\textit{ cur} [ j - 1] $ 相等,意味着我们在 $\textit{ cur} [ j - 1] $ 的时候已经完成了搜索,也可以跳过当前的搜索。
6464
65- 如果能将所有元素都加入到 ` cur ` 中,说明可以划分为 $k$ 个子集,返回 ` true ` 。
65+ 如果能将所有元素都加入到 $\textit{ cur}$ 中,说明可以划分为 $k$ 个子集,返回 $\textit{ true}$ 。
6666
6767<!-- tabs:start -->
6868
@@ -145,8 +145,7 @@ public:
145145 s /= k;
146146 int n = nums.size();
147147 vector<int > cur(k);
148- function<bool(int)> dfs;
149- dfs = [ &] (int i) {
148+ function<bool(int)> dfs = [ &] (int i) {
150149 if (i == n) {
151150 return true;
152151 }
@@ -210,31 +209,32 @@ func canPartitionKSubsets(nums []int, k int) bool {
210209
211210``` ts
212211function canPartitionKSubsets(nums : number [], k : number ): boolean {
213- let s = nums .reduce ((a , b ) => a + b );
214- if (s % k !== 0 ) {
215- return false ;
216- }
217- s /= k ;
218- nums .sort ((a , b ) => a - b );
219- const n = nums .length ;
220- const f: boolean [] = new Array (1 << n ).fill (false );
221- f [0 ] = true ;
222- const cur: number [] = new Array (n ).fill (0 );
223- for (let i = 0 ; i < 1 << n ; ++ i ) {
224- if (! f [i ]) {
225- continue ;
212+ const dfs = (i : number ): boolean => {
213+ if (i === nums .length ) {
214+ return true ;
226215 }
227- for (let j = 0 ; j < n ; ++ j ) {
228- if (cur [i ] + nums [ j ] > s ) {
229- break ;
216+ for (let j = 0 ; j < k ; j ++ ) {
217+ if (j > 0 && cur [j ] === cur [ j - 1 ] ) {
218+ continue ;
230219 }
231- if ((( i >> j ) & 1 ) === 0 ) {
232- f [ i | ( 1 << j )] = true ;
233- cur [ i | ( 1 << j )] = ( cur [ i ] + nums [ j ]) % s ;
220+ cur [ j ] += nums [ i ];
221+ if ( cur [ j ] <= s && dfs ( i + 1 )) {
222+ return true ;
234223 }
224+ cur [j ] -= nums [i ];
235225 }
226+ return false ;
227+ };
228+
229+ let s = nums .reduce ((a , b ) => a + b , 0 );
230+ const mod = s % k ;
231+ if (mod !== 0 ) {
232+ return false ;
236233 }
237- return f [(1 << n ) - 1 ];
234+ s = Math .floor (s / k );
235+ const cur = Array (k ).fill (0 );
236+ nums .sort ((a , b ) => b - a );
237+ return dfs (0 );
238238}
239239```
240240
@@ -246,22 +246,22 @@ function canPartitionKSubsets(nums: number[], k: number): boolean {
246246
247247### 方法二:状态压缩 + 记忆化搜索
248248
249- 与方法一相同,我们依然先判断数组 ` nums ` 是否有可能被划分为 $k$ 个子集。如果不能被 $k$ 整除,直接返回 ` false ` 。
249+ 与方法一相同,我们依然先判断数组 $\textit{ nums}$ 是否有可能被划分为 $k$ 个子集。如果不能被 $k$ 整除,直接返回 $\textit{ false}$ 。
250250
251- 我们记 $s$ 为每个子集期望的和,当前元素被划分的情况为 ` state ` 。对于第 $i$ 个数,若 ` (( state >> i) & 1) ` 等于 $0$,说明第 $i$ 个元素未被划分。
251+ 我们记 $s$ 为每个子集期望的和,当前元素被划分的情况为 $\textit{ state}$ 。对于第 $i$ 个数,若 $\textit{ state}$ 的第 $i$ 位为 $0$,说明第 $i$ 个元素未被划分。
252252
253253我们的目标是从全部元素中凑出 $k$ 个和为 $s$ 的子集。记当前子集的和为 $t$。在未划分第 $i$ 个元素时:
254254
255- - 若 $t + nums[ i] \gt s$,说明第 $i$ 个元素不能被添加到当前子集中,由于我们对 ` nums ` 数组进行升序排列,因此数组 ` nums ` 从位置 $i$ 开始的所有元素都不能被添加到当前子集,直接返回 ` false ` 。
256- - 否则,将第 $i$ 个元素添加到当前子集中,状态变为 ` state | (1 << i) ` ,然后继续对未划分的元素进行搜索。需要注意的是,若 $t + nums[ i] = s$,说明恰好可以得到一个和为 $s$ 的子集,下一步将 $t$ 归零(可以通过 ` (t + nums[i]) % s ` 实现),并继续划分下一个子集。
255+ - 若 $t + \textit{ nums} [ i] \gt s$,说明第 $i$ 个元素不能被添加到当前子集中,由于我们对 $\textit{ nums}$ 数组进行升序排列,因此数组 $\textit{ nums}$ 从位置 $i$ 开始的所有元素都不能被添加到当前子集,直接返回 $\textit{ false}$ 。
256+ - 否则,将第 $i$ 个元素添加到当前子集中,状态变为 $\textit{ state} | 2^i$ ,然后继续对未划分的元素进行搜索。需要注意的是,若 $t + \textit{ nums} [ i] = s$,说明恰好可以得到一个和为 $s$ 的子集,下一步将 $t$ 归零(可以通过 $ (t + \textit{ nums} [ i] ) \bmod s$ 实现),并继续划分下一个子集。
257257
258- 为了避免重复搜索,我们使用一个长度为 $2^n$ 的数组 ` f ` 记录每个状态下的搜索结果。数组 ` f ` 有三个可能的值:
258+ 为了避免重复搜索,我们使用一个长度为 $2^n$ 的数组 $\textit{f}$ 记录每个状态下的搜索结果。数组 $\textit{f}$ 有三个可能的值:
259259
260260- ` 0 ` :表示当前状态还未搜索过;
261261- ` -1 ` :表示当前状态下无法划分为 $k$ 个子集;
262262- ` 1 ` :表示当前状态下可以划分为 $k$ 个子集。
263263
264- 时间复杂度 $O(n\times 2^n)$,空间复杂度 $O(2^n)$。其中 $n$ 表示数组 $nums$ 的长度。对于每个状态,我们需要遍历数组 ` nums ` ,时间复杂度为 $O(n)$;状态总数为 $2^n$,因此总的时间复杂度为 $O(n\times 2^n)$。
264+ 时间复杂度 $O(n \times 2^n)$,空间复杂度 $O(2^n)$。其中 $n$ 表示数组 $\textit{ nums} $ 的长度。对于每个状态,我们需要遍历数组 $\textit{ nums}$ ,时间复杂度为 $O(n)$;状态总数为 $2^n$,因此总的时间复杂度为 $O(n\times 2^n)$。
265265
266266<!-- tabs: start -->
267267
@@ -355,8 +355,7 @@ public:
355355 int n = nums.size();
356356 int mask = (1 << n) - 1;
357357 vector<int > f(1 << n);
358- function<bool(int, int)> dfs;
359- dfs = [ &] (int state, int t) {
358+ function<bool(int, int)> dfs = [ &] (int state, int t) {
360359 if (state == mask) {
361360 return true;
362361 }
@@ -428,6 +427,47 @@ func canPartitionKSubsets(nums []int, k int) bool {
428427}
429428```
430429
430+ #### TypeScript
431+
432+ ``` ts
433+ function canPartitionKSubsets(nums : number [], k : number ): boolean {
434+ let s = nums .reduce ((a , b ) => a + b , 0 );
435+ if (s % k !== 0 ) {
436+ return false ;
437+ }
438+ s = Math .floor (s / k );
439+ nums .sort ((a , b ) => a - b );
440+ const n = nums .length ;
441+ const mask = (1 << n ) - 1 ;
442+ const f = Array (1 << n ).fill (0 );
443+
444+ const dfs = (state : number , t : number ): boolean => {
445+ if (state === mask ) {
446+ return true ;
447+ }
448+ if (f [state ] !== 0 ) {
449+ return f [state ] === 1 ;
450+ }
451+ for (let i = 0 ; i < n ; ++ i ) {
452+ if ((state >> i ) & 1 ) {
453+ continue ;
454+ }
455+ if (t + nums [i ] > s ) {
456+ break ;
457+ }
458+ if (dfs (state | (1 << i ), (t + nums [i ]) % s )) {
459+ f [state ] = 1 ;
460+ return true ;
461+ }
462+ }
463+ f [state ] = - 1 ;
464+ return false ;
465+ };
466+
467+ return dfs (0 , 0 );
468+ }
469+ ```
470+
431471<!-- tabs: end -->
432472
433473<!-- solution: end -->
@@ -438,13 +478,13 @@ func canPartitionKSubsets(nums []int, k int) bool {
438478
439479我们可以使用动态规划的方法求解本题。
440480
441- 我们定义 $f[ i] $ 表示当前选取的数字的状态为 $i$ 时,是否存在 $k$ 个子集满足题目要求。初始时 $f[ 0] =true$,答案为 $f[ 2^n-1] $。其中 $n$ 表示数组 $nums$ 的长度。另外,我们定义 $cur[ i] $ 表示当前选取的数字的状态为 $i$ 时,最后一个子集的和。
481+ 我们定义 $f[ i] $ 表示当前选取的数字的状态为 $i$ 时,是否存在 $k$ 个子集满足题目要求。初始时 $f[ 0] = true$,答案为 $f[ 2^n-1] $。其中 $n$ 表示数组 $nums$ 的长度。另外,我们定义 $cur[ i] $ 表示当前选取的数字的状态为 $i$ 时,最后一个子集的和。
442482
443- 我们在 $[ 0,2^n) $ 的范围内枚举状态 $i$,对于每个状态 $i$,如果 $f[ i] $ 为 ` false ` ,我们直接跳过即可。否则,我们枚举 $nums$ 数组中的任意一个数 $nums[ j] $,如果 $cur[ i] + nums[ j] \gt s$,我们直接跳出枚举循环,因为后面的数更大,无法放入当前子集;否则,如果 $i$ 的二进制表示的第 $j$ 位为 $0$,说明当前 $nums[ j] $ 还没有被选取,我们可以将其放入当前子集中,此时状态变为 $i | 2^j$,并更新 $cur[ i | 2^j] = (cur[ i] + nums[ j] ) \bmod s$,并且 $f[ i | 2^j] = true$。
483+ 我们在 $[ 0, 2^n] $ 的范围内枚举状态 $i$,对于每个状态 $i$,如果 $f[ i] $ 为 $\textit{ false}$ ,我们直接跳过即可。否则,我们枚举 $\textit{ nums} $ 数组中的任意一个数 $\textit{ nums} [ j] $,如果 $\textit{ cur} [ i] + \textit{ nums} [ j] > s$,我们直接跳出枚举循环,因为后面的数更大,无法放入当前子集;否则,如果 $i$ 的二进制表示的第 $j$ 位为 $0$,说明当前 $\textit{ nums} [ j] $ 还没有被选取,我们可以将其放入当前子集中,此时状态变为 $i | 2^j$,并更新 $\textit{ cur} [ i | 2^j] = (\textit{ cur} [ i] + \textit{ nums} [ j] ) \bmod s$,并且 $f[ i | 2^j] = \textit{ true} $。
444484
445- 最后,我们返回 $f[ 2^n- 1] $ 即可。
485+ 最后,我们返回 $f[ 2^n - 1] $ 即可。
446486
447- 时间复杂度 $O(n \times 2^n)$,空间复杂度 $O(2^n)$。其中 $n$ 表示数组 $nums$ 的长度。
487+ 时间复杂度 $O(n \times 2^n)$,空间复杂度 $O(2^n)$。其中 $n$ 表示数组 $\textit{ nums} $ 的长度。
448488
449489<!-- tabs: start -->
450490
@@ -584,6 +624,38 @@ func canPartitionKSubsets(nums []int, k int) bool {
584624}
585625```
586626
627+ #### TypeScript
628+
629+ ``` ts
630+ function canPartitionKSubsets(nums : number [], k : number ): boolean {
631+ let s = nums .reduce ((a , b ) => a + b );
632+ if (s % k !== 0 ) {
633+ return false ;
634+ }
635+ s /= k ;
636+ nums .sort ((a , b ) => a - b );
637+ const n = nums .length ;
638+ const f: boolean [] = Array (1 << n ).fill (false );
639+ f [0 ] = true ;
640+ const cur: number [] = Array (n ).fill (0 );
641+ for (let i = 0 ; i < 1 << n ; ++ i ) {
642+ if (! f [i ]) {
643+ continue ;
644+ }
645+ for (let j = 0 ; j < n ; ++ j ) {
646+ if (cur [i ] + nums [j ] > s ) {
647+ break ;
648+ }
649+ if (((i >> j ) & 1 ) === 0 ) {
650+ f [i | (1 << j )] = true ;
651+ cur [i | (1 << j )] = (cur [i ] + nums [j ]) % s ;
652+ }
653+ }
654+ }
655+ return f [(1 << n ) - 1 ];
656+ }
657+ ```
658+
587659<!-- tabs: end -->
588660
589661<!-- solution: end -->
0 commit comments