This question feels a bit contrived, but a way to explore a lot of challenges with linked lists. I have an answer to this in my archive.
So, full disclosure: most of my archive is actually in C++, rather than the more approachable Python or (occasionally, for my students) Javascript. I think I do have a few earlier solutions in C++, but this is the first “big” one, I guess? Previously I’ve also been translating my archive, but as my external obligations are increasing I have to drop some features. So this is in C++. Also from the archive: a lot of my notes were intentionally embedded in comments.
class Solution {
public:
// it seems we need to know the length, to begin with.
// and then we do an interesting partition sort of algorithm.
// the value of the LL nodes play no part in this?
// Sure...
int len(ListNode* r) {
int l = 0;
while (r) {
l++;
r = r->next;
}
return l;
}
// OK, and then we divide things into K.
// Ah that things differ by less than 1 is sort of an interesting constraint.
// So if k > l, that's sort of easy: we know we need length 1.
// if k < l < 2k, then we know we'll have a few length-2, and the rest length 1.
// So this suggests some kind of modulo. k > 0, so that's safe.
// l / k is the length of the shorter lists.
// l % k is how many lists need one more.
// This feels like an obsfucated remainder check. But actually it's OK. I kind of like
// this partition problem.
ListNode* peel(ListNode** root, int l) {
// forgot a deref!
if (!(*root)) return nullptr; // assert l == 1?
ListNode* p = *root;
ListNode* t = *root;
// Here I'm being a bit risky. Not sure if there's a weird case
// where if we improperly peel we'll null-deref. I think if everything
// goes right we'll only ever be "off by one", which is effectively
// captured by our basecase there.
//
// In other words, this is not a very robust peel! Relies on qualities
// of the list guaranteed by the caller.
for (int i = 0; i < l-1; ++i) {
t = t->next;
}
*root = t->next;
t->next = nullptr;
return p;
}
vector<ListNode*> splitListToParts(ListNode* root, int k) {
const int l = len(root);
int shortLength = l / k;
int listsWithExtras = l % k;
// next up: helper function "peel";
vector<ListNode*> result;
ListNode* r = root;
for (int i = 0; i < k; ++i) {
int n = shortLength;
if (i < listsWithExtras) {
n++;
}
result.push_back(peel(&r, n));
}
return result;
}
};
Notes
A few notable things:
- The
len
helper, it seems to be required? It’s unfortunate that we can’t do this online. - I’m having second thoughts about my implementation of “peel”. I think I can avoid the pointer-to-pointer, though for C that’s not a huge deal. Maybe inlining the function would make that clearer.
- You can see my careful-ish reasoning about how to calculate each peel length, I think that’s an interesting comment.
As always, all linked list things are collected here.