打败算法 —— 圆圈中最后剩下的数字

本文参考

出自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秒)

原文地址:https://www.cnblogs.com/kuluo/p/12600898.html