diff --git a/day-17/README.md b/day-17/README.md
new file mode 100644
index 0000000..a4ff566
--- /dev/null
+++ b/day-17/README.md
@@ -0,0 +1,26 @@
+# Day 17 efficient solution
+
+Day 17 is an instance of the subset sum problem. This problem asks whether for
+a (multi)set of integers *V*, there is a non-empty subset of integers summing up to
+exactly *s*. This problem is NP-complete.
+
+The brute force approach of this is trying every possible set in the powerset
+of *V* to see if they match. This is inpractical however, because a powerset of
+a set of size *n* contains 22 sets.
+
+In the exercise, we have 20 buckets, and 220 is still
+brute-forcable. There is a smarter approach.
+
+We split the list of buckets in two lists of (approximately) the same size. We
+then take the powersets of those two lists and compute the sum for each entry.
+This leaves us with a total of 2n / 2 + 1 entries. We then sort both
+sublists on the total value of each entry.
+
+Finally, we iterate of the first list, and use binary search to see whether
+there is an appropriately sized entry in the second list. This gives us a final
+complexity of *n* times 2*n*/2, allowing the solution to be computed
+instantly.
+
+The algorithm above can be modified to find all combinations, not just one, in
+time proportional to the number of solutions. This is implemented in the final
+program.
diff --git a/day-17/solution.py b/day-17/solution.py
index cda8055..88bafff 100644
--- a/day-17/solution.py
+++ b/day-17/solution.py
@@ -1,21 +1,17 @@
from __future__ import print_function, division
import fileinput
from collections import defaultdict
+import bisect
-buckets = []
+def value(buckets, choice):
+ total = 0
+ for value in buckets:
+ if choice % 2 == 1:
+ total += value
-for line in fileinput.input():
- buckets.append(int(line))
+ choice //= 2
-def works(bucketCombination, target):
- for idx, value in enumerate(buckets):
-
- if bucketCombination % 2 == 1:
- target -= value
-
- bucketCombination = bucketCombination // 2
-
- return target == 0
+ return total
def ones(x):
n = 0
@@ -27,11 +23,40 @@ def ones(x):
return n
+def partition(a_list):
+ pivot = len(a_list) // 2
+
+ return a_list[:pivot], a_list[pivot:]
+
+def partitionList(buckets):
+ result = [(value(buckets, x), ones(x)) for x in range(1 << len(buckets))]
+ result.sort()
+ return result
+
+buckets = []
+
+for line in fileinput.input():
+ buckets.append(int(line))
+
+partition1, partition2 = partition(buckets)
+
+values1 = partitionList(partition1)
+values2 = partitionList(partition2)
+
possible = defaultdict(lambda: 0)
-for x in range(1 << len(buckets)):
- if works(x, 150):
- n = ones(x)
- possible[n] += 1
+i = 0
+
+target = 150
+
+for entry in values1:
+
+ i = bisect.bisect_left(values2, (target - entry[0], 0))
+
+ while i < len(values2) and entry[0] + values2[i][0] == target:
+ possible[entry[1] + values2[i][1]] += 1
+ i += 1
+
+print("Total possibilities:", sum(possible.values()))
+print("Minimal possibilities:", possible[min(possible.keys())])
-print(sum(possible[x] for x in possible), possible[min(possible.keys())])