[algorithm - Sword finger Offer] 62. The last number left in the circle (ring linked list; Joseph Ring; dynamic programming)

Sword finger Offer 62. The last remaining number in the circle - LeetCode

Released: 12:18:52, September 12, 2021

Problem description and examples

The N numbers 0,1, ···, n-1 are arranged in a circle, starting from the number 0, and the m-th number is deleted from the circle each time (counting from the next number after deletion). Find the last number left in the circle.

For example, the five numbers 0, 1, 2, 3 and 4 form a circle. Starting from the number 0, delete the third number each time, then the first four deleted numbers are 2, 0, 4 and 1 in turn, so the last remaining number is 3.

Source: LeetCode
Link: https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof
The copyright belongs to Lingkou network. For commercial reprint, please contact the official authorization, and for non-commercial reprint, please indicate the source.

Example 1:
Input: n = 5, m = 3
Output: 3

Example 2:
Input: n = 10, m = 17
Output: 2

Tips:
1 <= n <= 10^5
1 <= m <= 10^6

My solution

It is obvious that the structure model of the topic is a ring structure, and when it comes to the ring structure, I immediately think of the ring linked list. Moreover, the title also involves the deletion of elements. According to the experience I learned from the previous question (see the reference blog below), I think it is more appropriate to use the linked list structure to store elements at this time.

reference resources: [algorithm LeetCode] 146. LRU caching mechanism (bidirectional linked list; Map; stack)_ Lai nianan's blog - CSDN blog

Update: September 12, 2021 21:47:03

I'm still too young. Seeing that this problem is a simple one, I don't think there are so many twists and turns. I didn't expect it to be so difficult. It turns out that this problem is a famous mathematical problem: Joseph Ring problem. I still feel very interesting, but I really can't think of the solution without timeout... I can only lament the beauty of mathematics and algorithms.

[end of update]

My problem solution 1 (circular linked list - unable to pass due to timeout)

/**
 * @param {number} n
 * @param {number} m
 * @return {number}
 */
function ListNode(val) {
  this.val = val;
  this.next = null;
}

function createList(num) {
  let head = new ListNode(0);
  let rear = head;
  for(let i = 1; i < num; i++) {
    rear.next = new ListNode(i);
    rear = rear.next;
  }
  rear.next = head;
  return rear;
}

function deleteNode(list, step) {
  let beforeTarget = list;
  while(step > 1) {
    beforeTarget = beforeTarget.next;
    step--;
  }
  beforeTarget.next = beforeTarget.next.next;
  return beforeTarget;
}

var lastRemaining = function(n, m) {
  let list = createList(n);
  while(list.next !== list) {
    list = deleteNode(list, m);
  }
  return list.val;
};

Submit records
26 / 36 Pass test cases
 Status: time limit exceeded
 Time: 2021/09/12 12:22

The last input test case

Run the above timeout test case in Edge. After running for a long time, the console outputs the following results:

Running results in browser (circular linked list)

My problem solution 2 (array - unable to pass due to timeout)

/**
 * @param {number} n
 * @param {number} m
 * @return {number}
 */

var lastRemaining = function (n, m) {
  let arr = Array.from({ length: n }).map(
    (current, index) => index
  );
  let start = 0;
  let target = 0;
  while (arr.length > 1) {
    let remainingSteps = m - (arr.length - start);
    if (remainingSteps > 0) {
      // Note here that the number of remaining steps is just arr.length. For details, see [Supplement 1] below
      target = remainingSteps % arr.length ? remainingSteps % arr.length - 1 : arr.length - 1;
      start = remainingSteps % arr.length ? target : 0;
    } else {
      target = start + m - 1;
      start = remainingSteps === 0 ? 0 : target;
    }
    // Originally, I used delete and filter to implement it here. Later, it was faster to directly use splice. See [Supplement 2] below for details
    arr.splice(target, 1);
  }
  return arr[0];
};


Submit records
3 / 36 Pass test cases
 Status: time limit exceeded
 Time: September 12, 2021 15:44:13

[Supplement 1]
I thought this could pass, but I didn't think it could pass because of timeout. I guess it should be the internal implementation of Array.filter() function, which takes more time when encountering a large amount of data. And it's strange that the running result in the browser is different from that of the previous method implemented with linked list. Is it really my wrong idea? Seeing that the number of test cases passed is less than last time, it should be my implementation method is wrong.

Run result 1 in browser (array)

Then I found a small error through debugging. After correction, the result of running in the browser is as follows. It is still timeout in LeetCode.

Run results in browser 2 (array)

The corresponding changes are as follows:

// Original writing
target = remainingSteps === arr.length ? arr.length - 1 : remainingSteps % arr.length - 1;
start = remainingSteps === arr.length ? 0 : target;

// Corrected writing
target = remainingSteps % arr.length ? remainingSteps % arr.length - 1 : arr.length - 1;
start = remainingSteps % arr.length ? target : 0;

[Supplement 2]

Run result 3 (array) in browser

Submission results in LeetCode (array)

The corresponding changes are as follows:

// Original writing
delete (arr[target]);
arr = arr.filter(val => val !== undefined);

// Improved writing
arr.splice(target, 1);

After directly deleting the elements in the array with splice(), I found that the results can be output faster in the Edge browser than before, but I found that the submission on LeetCode still timed out... Do I have to go back to the linked list again?

My problem solution 3 (mathematical formula derivation / dynamic programming)

To be exact, this is not my solution, but written with reference to other people's ideas. Thank these bloggers for sharing.

/**
 * @param {number} n
 * @param {number} m
 * @return {number}
 */

var lastRemaining = function (n, m) {
  let survivor = 0;
  for(let i = 1; i <= n; i++) {
    survivor = (survivor + m) % i;
  }
  return survivor;
};

Submit records
36 / 36 Pass test cases
 Status: passed
 Execution time: 80 ms, At all JavaScript Defeated 54 in submission.44%User
 Memory consumption: 37.5 MB, At all JavaScript Beat 90 in submission.76%User
 Time: 2021/09/12 19:24	

Because there were no other ideas, I went to see other people's solutions. During the period, I saw a blog about this problem. I felt that it was very good.

reference resources: Joseph Ring formula method (recursive formula)_ No matter how difficult it is, we should stick to CSDN blog_ Joseph Ring formula

The problem mentioned in the above blog is actually a famous mathematical problem: Joseph Ring. The relevant derivation process is explained in detail, and I won't repeat it again, but the diagram of the table inside doesn't seem to be very complete, which may be a little difficult to understand, so I specially drew a new one, which can be regarded as a supplement.

Cooperate with the blog mentioned above to assist observation

The recursive form of this derivation method is very similar to the state transition equation in dynamic programming, so this method can also be understood from the perspective of dynamic programming. This idea was also put forward in the problem solution area of LeetCode. You can refer to the following problem solution:

Update: September 12, 2021 21:36:44

reference resources: You can also understand it after graduation from prenatal education. Dynamic programming solves the Joseph Ring problem - the last remaining number in the circle - LeetCode

Official explanation

Update: July 29, 2021 18:43:21

Because I consider the ownership of copyright, I won't paste the specific code in the [official solution], which can be viewed in the link below.

Update: 12:23:44, September 12, 2021

reference resources: The last number left in the circle - the last number left in the circle - LeetCode

[end of update]

Relevant reference

Update: September 12, 2021 13:35:27
reference resources: Array.prototype.filter() - JavaScript | MDN
reference resources: Array.prototype.map() - JavaScript | MDN
reference resources: Array.prototype.forEach() - JavaScript | MDN
reference resources: Array.from() - JavaScript | MDN
reference resources: js array to filter out false, null, 0, "", undefined, and NaN values_ shelomi's column - CSDN blog
reference resources: Why can the undefined array in javascript - js not be traversed
Update: September 12, 2021 17:10:45
reference resources: Joseph Ring formula method (recursive formula)_ No matter how difficult it is, we should stick to CSDN blog_ Joseph Ring formula
reference resources: You can also understand it after graduation from prenatal education. Dynamic programming solves the Joseph Ring problem - the last remaining number in the circle - LeetCode
reference resources: Java solves the Joseph Ring problem and tells you why the simulation times out- The last remaining number in the circle - LeetCode

Tags: Javascript Algorithm leetcode

Posted on Sun, 12 Sep 2021 19:05:02 -0400 by Stanza