diff --git a/2021/README.md b/2021/README.md
index 300ba54c41b58f686369fa093041f22cf4f57a31..b5d150c07f23b3bcd917fc8f2726e19128d6852b 100644
--- a/2021/README.md
+++ b/2021/README.md
@@ -193,3 +193,16 @@ Oh, yes. This is *much* faster. Still taking a while to get through part 2, but
 in a few seconds we got past the point that the StringBuilder approach took a
 couple of hours while I was in a meeting. But we still ran out of memory when
 growing the new polymer on iteration 28 of the sample data.
+
+Perhaps instead of creating strings, we can keep track of the positions of each
+element in the polymer. Computationally, this will increase the asymptotic
+complexity, probably by two orders of magnitude, but asymptotic complexity isn't
+everything: memory management was definitely a HUGE constant factor. Keeping
+track of the positions of each element in the polymer will use very, very little
+memory.
+
+Yup, this has definitely slowed down computation. And, now that I think about it,
+I have to create as many indices as the original string's length was. This isn't
+a savings. At. All.
+
+What am I missing?
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day14.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day14.java
index cabe3f2a9ebaec051987756bcbf1fa3322aa017c..6cc4c916e82ccd8999b62f3f7b2372b89bce52ca 100644
--- a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day14.java
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day14.java
@@ -2,10 +2,11 @@ package edu.unl.cse.bohn.year2021;
 
 import edu.unl.cse.bohn.Puzzle;
 
-import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 
 @SuppressWarnings("unused")
 public class Day14 extends Puzzle {
@@ -46,68 +47,69 @@ public class Day14 extends Puzzle {
         return initialMolecule;
     }
 
-    @SuppressWarnings("CommentedOutCode")
-    private String growMolecule(String molecule) {
-//        StringBuilder growingMolecule = new StringBuilder();
-//        String pattern = "";
-//        for (int i = 0; i < molecule.length() - 1; i++) {
-//            pattern = molecule.substring(i, i + 2);
-//            growingMolecule.append(pattern.charAt(0)).append(rules.get(pattern));
-//        }
-//        growingMolecule.append(pattern.charAt(1));
-//        return growingMolecule.toString();
-        char[] oldElements = molecule.toCharArray();
-        char[] newElements = new char[2 * molecule.length() - 1];
-        for (int i = 0; i < oldElements.length - 1; i++) {
-            newElements[2 * i] = oldElements[i];
-            newElements[2 * i + 1] = rules.get(molecule.substring(i, i + 2));
+    private long producePolymer(List<String> data, int numberOfSteps) {
+        Map<Character, List<AtomicLong>> molecule = new HashMap<>();
+        long index = 0;
+        for (char element : parseData(data).toCharArray()) {
+            if (!molecule.containsKey(element)) {
+                molecule.put(element, new LinkedList<>());
+            }
+            molecule.get(element).add(new AtomicLong(index++));
         }
-        newElements[newElements.length - 1] = oldElements[oldElements.length - 1];
-        return new String(newElements);
-    }
-
-    private Map<Character, Long> countElements(String molecule) {
-        Map<Character, Long> counts = new HashMap<>();
-        for (char element : molecule.toCharArray()) {
-            if (!counts.containsKey(element)) {
-                counts.put(element, 0L);
+        for (char element : molecule.keySet()) {
+            System.out.println("\t\t" + element + ": " + molecule.get(element));
+        }
+        for (int i = 0; i < numberOfSteps; i++) {
+            System.out.println("\tGrowing...\t" + i);
+            growMolecule(molecule);
+            if (i < 4) {
+                for (char element : molecule.keySet()) {
+                    System.out.println("\t\t" + element + ": " + molecule.get(element));
+                }
             }
-            counts.put(element, counts.get(element) + 1);
         }
-        return counts;
+        long leastFrequentOccurrence = molecule.keySet().stream()
+                .mapToLong(element -> molecule.get(element).size()).min().orElseThrow();
+        long mostFrequentOccurrence = molecule.keySet().stream()
+                .mapToLong(element -> molecule.get(element).size()).max().orElseThrow();
+        return mostFrequentOccurrence - leastFrequentOccurrence;
     }
 
-    private long producePolymer(List<String> data, int numberOfSteps) {
-        String molecule = parseData(data);
-        for (int i = 0; i < numberOfSteps; i++) {
-            System.out.print("Molecular growth (" + i + ")--\toriginal size: " + molecule.length());
-            molecule = growMolecule(molecule);
-            System.out.println("\tnew size: " + molecule.length());
-//            if (molecule.length() < 60) {
-//                String expectedString = switch (i) {
-//                    case 0 -> "NCNBCHB";
-//                    case 1 -> "NBCCNBBBCBHCB";
-//                    case 2 -> "NBBBCNCCNBBNBNBBCHBHHBCHB";
-//                    case 3 -> "NBBNBNBBCCNBCNCCNBBNBBNBBBNBBNBBCBHCBHHNHCBBCBHCB";
-//                    default -> "??";
-//                };
-//                System.out.println("\texpected: " + expectedString);
-//                System.out.println("\t  actual: " + molecule);
-//                assert (molecule.equals(expectedString));
-//            }
+    private Character getCharAt(Map<Character, List<AtomicLong>> molecule, long index) {
+        return molecule.keySet().stream()
+                .filter(e -> molecule.get(e).stream().anyMatch(position -> position.get() == index))
+                .findFirst().orElse(null);
+    }
+
+    private void incrementIndicesAfter(Map<Character, List<AtomicLong>> molecule, long start) {
+        for (char element : molecule.keySet()) {
+            molecule.get(element).stream()
+                    .filter(position -> start <= position.get())
+                    .forEach(AtomicLong::getAndIncrement);
         }
-        Map<Character, Long> elementCounts = countElements(molecule);
-        char leastFrequentElement = molecule.charAt(0);
-        char mostFrequentElement = molecule.charAt(0);
-        for (char element : elementCounts.keySet()) {
-            if (elementCounts.get(element) < elementCounts.get(leastFrequentElement)) {
-                leastFrequentElement = element;
+    }
+
+    @SuppressWarnings("UnusedReturnValue")
+    private Map<Character, List<AtomicLong>> growMolecule(Map<Character, List<AtomicLong>> molecule) {
+        boolean stillGrowing = true;
+        long index = 0;
+        char[] pattern = {getCharAt(molecule, index), getCharAt(molecule, ++index)};
+        do {
+            incrementIndicesAfter(molecule, index);
+            char newElement = rules.get(new String(pattern));
+            if (!molecule.containsKey(newElement)) {
+                molecule.put(newElement, new LinkedList<>());
             }
-            if (elementCounts.get(element) > elementCounts.get(mostFrequentElement)) {
-                mostFrequentElement = element;
+            molecule.get(newElement).add(new AtomicLong(index++));
+            Character nextElement = getCharAt(molecule, ++index);
+            if (nextElement == null) {
+                stillGrowing = false;
+            } else {
+                pattern[0] = pattern[1];
+                pattern[1] = nextElement;
             }
-        }
-        return elementCounts.get(mostFrequentElement) - elementCounts.get(leastFrequentElement);
+        } while (stillGrowing);
+        return molecule;
     }
 
     @Override