打败算法 —— 圆圈中最后剩下的数字
本文参考
出自LeetCode上的题库 —— 圆圈中最后剩下的数字,乍看这道题用循环链表解决,奈何输入的数值太大,程序不是超出内存限制就是超出时间限制,力扣上已经有大大通过数学公式解决了这道题,不过很不服气的我还是尝试了好几次循环链表(没成功_(:D)∠)_)
https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/
圆圈中最后剩下的数字问题
0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字是2、0、4、1,因此最后剩下的数字是3。
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
限制:
1 <= n <= 10^5
1 <= m <= 10^6
解题思路
甜姨(@Sweetiee)在题解中提了一下下,LinkedList删除一个节点的时间复杂度为O(1),但是要找到对应的节点需要一个个遍历,时间复杂度为O(n),而使用ArrayList勉强可以不超时,虽然ArrayList查找一个节点的时间复杂度为O(1),删除一个节点的时间复杂度为O(n),但是因为删除节点时,后继节点的前移是连续内存空间的拷贝,效率会比LinkedList快
不过我想,我们可以提高链表的查询效率呀!通过双向循环链表,一定程度上可以减少要查找的节点个数
我们可以用一个整型记录当前结点的数量,这个值还可以作为跳出while循环的条件。当移动的步长大于当前节点的数量时,可以对移动次数进行缩减,去掉冗余的"转圈圈"次数,经过测试还是不够快,测试数值在50000+节点数量时就报了超时错误。所以节点不能只是单纯的向前移动,如果要删除的点更偏向于当前节点的前部,那么应当向前移动,这也是设计成双向的原因。
即便如此仍然没能打败70000+的测试数值,双向循环链表在执行70000+节点数量时,大概需要2 ~ 4秒的时间(欲哭无泪)
双向循环链表算法
// 内部类
private class Node {
private int nodeValue; // 当前节点值
private Node nextNode; // 下一个节点
private Node preNode; // 前一个节点
public Node(int nodeValue) {
this.nodeValue = nodeValue;
this.nextNode = null;
this.preNode = null;
}
}
public int lastRemaining(int n, int m) {
if (m == 1) {
return n - 1;
}
// 构造双向循环链表
Node root = new Node(0);
Node tail = root, preNode;
for (int i = 1; i < n; i++) {
preNode = tail;
Node newNode = new Node(i);
tail.nextNode = newNode;
tail = newNode;
tail.preNode = preNode;
}
tail.nextNode = root;
root.preNode = tail;
Node currNode = root;
int nodeNum = n;
while (nodeNum != 1) {
// 去掉"转圈圈"的冗余次数
int moveTimes;
if (m < nodeNum) {
moveTimes = m - 1;
} else if (m % nodeNum == 0) {
moveTimes = nodeNum - 1;
} else {
moveTimes = m % nodeNum - 1;
}
// 决定向前还是向后移动
if (moveTimes > nodeNum / 2) {
for (int i = 1; i <= nodeNum - moveTimes; i ++) {
currNode = currNode.preNode;
}
} else {
for (int i = 1; i <= moveTimes; i++) {
currNode = currNode.nextNode;
}
}
// 删除节点
Node tmp = currNode.preNode;
tmp.nextNode = currNode.nextNode;
currNode = currNode.nextNode;
currNode.preNode = tmp;
nodeNum--;
}
return currNode.nodeValue;
}
测试用例:
71989, 111059
输出:
34203(耗时2 ~ 4秒)