Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f7895bb

Browse files
feat: add solutions to lc problem: No.0698 (doocs#3447)
No.0698.Partition to K Equal Sum Subsets
1 parent 3e47705 commit f7895bb

File tree

8 files changed

+334
-93
lines changed

8 files changed

+334
-93
lines changed

‎solution/0600-0699/0698.Partition to K Equal Sum Subsets/README.md‎

Lines changed: 110 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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
212211
function 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+
returntrue;
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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /