回溯 + 剪枝
class Solution {
int t;
public boolean makesquare(int[] matchsticks) {
int s = Arrays.stream(matchsticks).sum();
if (s % 4 != 0 || matchsticks.length < 4) return false;
t = s / 4;
Arrays.sort(matchsticks);
return dfs(matchsticks, 0, 0, 0, matchsticks.length - 1);
}
boolean dfs(int[] a, int c, int s, int st, int u) {
if (c == 4) return true;
if (s == t) return dfs(a, c + 1, 0, st, a.length - 1);
for (int i = u; i >= 0; i--) {
if ((st >> i & 1) == 1 || a[i] + s > t) continue;
if (dfs(a, c, s + a[i], st | 1 << i, u - 1))
return true;
if (s == 0 || s + a[i] == t) return false;
while (i > 0 && a[i - 1] == a[i])
i--;
}
return false;
}
}
698.划分为k个相等的子集
思路:
方法1:
同473
枚举每个子集选哪些物品
优化1:按物品从大到小枚举
优化2:枚举同一子集时,使用组合而不是排列,因为顺序对结果并没有影响
优化3:如果将一个物品放在子集的开头都无法得到答案,说明无最终答案
优化4:如果一个物品放在一个子集的最后无法得到答案,说明无最终答案(这一点由枚举顺序决定)
优化5:dfs的参数尽可能的少,能放全局的就放全局
class Solution {
int sum, k;
int[] a;
public boolean canPartitionKSubsets(int[] nums, int k) {
for (int i = 0; i < nums.length; i++)
sum += nums[i];
if (sum % k != 0)
return false;
sum /= k;
Arrays.sort(nums);
reverse(nums);
a = nums;
this.k = k;
return dfs(0, 0, 0, 0);
}
boolean dfs(int u, int val, int start, int st) {
if (u == k)
return true;
if (val == sum)
return dfs(u + 1, 0, 0, st);
for (int i = start; i < a.length; i++) {
if ((st >> i & 1) == 1 || val + a[i] > sum)
continue;
if (dfs(u, val + a[i], i + 1, st | (1 << i)))
return true;
while (i + 1 < a.length && a[i] == a[i + 1])
i++;
if (val == 0)
return false;
if (val + a[i] == sum)
return false;
}
return false;
}
void reverse(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int t = nums[l];
nums[l] = nums[r];
nums[r] = t;
l++;
r--;
}
}
}
方法2:
枚举每个物品放在哪个子集
优化1:从大到小枚举
优化2:每一层如果已经枚举过相同大小的子集,跳过即可
优化3:如果一个物品放在一个空集中都不能得到最终分类,说明无解
优化4:如果一个物品放在一个子集中恰好为目标大小但仍无法完成最终分类,说明无解
class Solution {
int n, k;
int[] nums;
int[] sum;
public boolean canPartitionKSubsets(int[] nums, int k) {
int s = 0;
for (int x : nums) {
s += x;
}
if (nums.length < k || s % k != 0)
return false;
this.n = nums.length;
this.k = k;
this.nums = nums;
this.sum = new int[k];
Arrays.sort(nums);
return dfs(nums.length - 1, s / k);
}
boolean dfs(int u, int t) {
if (u < 0)
return true;
Set<Integer> used = new HashSet<>();
for (int i = 0; i < k; i++) {
if (nums[u] + sum[i] > t || used.contains(sum[i]))
continue;
used.add(sum[i]);
sum[i] += nums[u];
if (dfs(u - 1, t))
return true;
sum[i] -= nums[u];
if (sum[i] == 0 || nums[u] + sum[i] == t)
break;
}
return false;
}
}
1723.完成所有工作的最短时间
思路:
二分 + 枚举每个工作由哪个工人完成
class Solution {
int[] jobs;
int n, k;
int[] sum;
public int minimumTimeRequired(int[] jobs, int k) {
this.n = jobs.length;
this.k = k;
this.jobs = jobs;
this.sum = new int[k];
int s = 0, max = 0;
for (int x : jobs) {
s += x;
max = Math.max(max, x);
}
Arrays.sort(jobs);
int l = Math.max(max, (s + k - 1) / k), r = s;
while (l < r) {
int mid = l + r >> 1;
Arrays.fill(sum, 0);
if (dfs(n - 1, mid))
r = mid;
else
l = mid + 1;
}
return l;
}
boolean dfs(int u, int t) {
if (u < 0)
return true;
Set<Integer> set = new HashSet<>();
for (int i = 0; i < k; i++) {
if (sum[i] + jobs[u] > t || set.contains(sum[i]))
continue;
set.add(sum[i]);
sum[i] += jobs[u];
if (dfs(u - 1, t))
return true;
sum[i] -= jobs[u];
if (sum[i] == 0 || sum[i] + jobs[u] == t)
break;
}
return false;
}
}
1240. 铺瓷砖
http://int-e.eu/~bf3/squares/view.html#13,11
NP困难问题,没有好的解法,爆搜
其它例题:
5289. 公平分发饼干 内含除回溯外的DP解法
Acwing 4219. 找倍数 注意取模运算的性质 10a % b = a % b * 10 % b
(10a + 1) % b = (10a % b + 1 % b) % b = a((% b * 10 + 1) % b