▶ 将一个单链表拆分为长度尽量接近的 k 段
● 自己的代码,12 ms
■ 记链表长度为 count,目标段数为 k,quo = count / k,mod = count % k,part = mod * (quo + 1)
■ 前半截(长半截)共有 mod 组,每组 quo + 1 个元素,共 mod * (quo + 1) 个元素,这是 part 的由来;后半截(长半截)共有 k - mod 组,每组 quo 个元素,共 quo * (k - mod) 个元素
■ 当 i < part 时,第 i 元素处于前半截,组号 s = i / (quo + 1),该组最后一个元素下标为 t = (quo + 1) * (s + 1) - 1,即满足 (t + 1) % (quo + 1) == 0
■ 当 i >= part 时,第 i 元素处于后半截,组号 s = (i - part) / quo + mod = (i - mod) / quo,该组最后一个元素下标为 t = (s + 1) * quo + mod - 1 (前面所有组的元素个数,注意偏移量 mod),即满足 (t + 1 - mod) % quo == 0
1 class Solution 2 { 3 public: 4 vector<ListNode*> splitListToParts(ListNode* root, int k) 5 { 6 vector<ListNode *> table(k, nullptr); 7 if (root == nullptr) 8 return table; 9 int count, i; 10 ListNode *p, *q; 11 for (p = root, count = 1; p->next != nullptr; p = p->next, count++);// 计算结点数 12 const int quo = count / k, mod = count % k, mod * (quo + 1); 13 for (p = table[0] = root, i = 0; p != nullptr && p->next != nullptr; i++) 14 { 15 if (i < part && !((i + 1) % (quo + 1)))// p 指向了前半截某组的末尾结点 16 { 17 q = p->next, p->next = nullptr, p = q; 18 table[(i + 1) / (quo + 1)] = q; // 注意此时是在table 中挂上 q 指向的结点,相当于第 i + 1 个结点 19 } 20 else if (i >= part && !((i + 1 - mod) % quo))// p 指向了后半截某组的末尾结点 21 { 22 q = p->next, p->next = nullptr, p = q; 23 table[(i + 1 - mod) / quo] = q; 24 } 25 else 26 p = p->next; 27 } 28 return table; 29 } 30 };
● 大佬的代码,11 ms,使用简单的判断 idx < remainder 来确认切分位置
1 class Solution 2 { 3 public: 4 vector<ListNode*> splitListToParts(ListNode* root, int k) 5 { 6 if (k == 1) 7 return vector<ListNode*>{ root }; 8 vector<ListNode*> res(k, nullptr); 9 ListNode *temp; 10 int len, idx, tmp; 11 for (len = 0, temp = root; temp != nullptr; len++, temp = temp->next); 12 const int per_len = len / k, remainder = len % k; 13 14 for (idx = 0; idx < k; ) 15 { 16 tmp = per_len + (idx < remainder ? 1 : 0); 17 if (tmp == 0) 18 { 19 res[idx++] = nullptr; 20 continue; 21 } 22 for (res[idx++] = root; tmp != 1; root = root->next, tmp--); 23 temp = root->next, root->next = nullptr, root = temp; 24 } 25 26 return res; 27 } 28 };
● 大佬的方法,11 ms,号称不需要知道链表的长度。每次指针 slow 移动一格,指针 fast 移动 k 格,直到 fast 抵达链表尾部,这时 slow 大约移动了 n / k 格,即为分界点。实际上 fast 在整个过程中移动了 O(n2) 的次数,还不如提前一趟遍历计算链表的长度
1 class Solution 2 { 3 public : 4 vector<ListNode *> splitListToParts(ListNode *root, int k) 5 { 6 vector<ListNode *> res(k, nullptr); 7 ListNode *fast, *slow; 8 int i, step; 9 for (i = 0; i < k; i++) 10 { 11 if (root == nullptr) 12 break; 13 for (slow = root, fast = root, step = k;;) 14 { 15 fast = move(fast, step); 16 if (fast != nullptr) 17 slow = slow->next; 18 else 19 break; 20 } 21 res[i] = root; 22 if (slow->next != nullptr) 23 { 24 root = slow->next; 25 slow->next = nullptr; 26 } 27 else 28 break; 29 step--; 30 } 31 return res; 32 } 33 ListNode* move(ListNode *node, int step) 34 { 35 for(;step > 0;) 36 { 37 node = node->next; 38 step--; 39 if (node == nullptr) 40 break; 41 } 42 return node; 43 } 44 };