题目:
有一个整型数组a[n]顺序存放0 ~ n-1,要求每隔两个数删掉一个数,到末尾时循环至开头继续进行,求最后一个被删掉的数的原始下标位置。
以 8 个数(n=8)为例:{0,1,2,3,4,5,6,7},
0->1->2(删除)->3->4->5(删除)->6->7->0(删除),如此循环直到最后一个数被删除。
思路:
这题有3种解法,
第一种就是构建循环链表模拟删数字的过程,但是时间空间复杂度较高,
第二种是用LinkedList模拟删数字过程,相较于第一种就是代码量会少一点,
第三种是用动态规划求解,首先写出状态转移方程,
设f(n)为数组长度为n时按每隔m个数删除一个数,最后一个剩下的数的数值,
f(n-1)为数组长度为n-1时按每隔m个数删除一个数,最后一个剩下的数的数值,依此类推,
则对于长度为n的数组,第一次删除的数为m,那么下一轮遍历是从m+1开始,即剩下要遍历的元素按顺序为 m+1, m+2, ... n-1, 0, 1, ... m-1 ①,
对于长度为n-1的数组,一开始要遍历的元素按顺序为 0, 1 ... n-m-2, n-m-1, n-m, ... n-2 ②,
因为①和②中元素的个数相等,现在将①②中的元素一一对应,
又因为①②都是每隔m个数删除一个数,所以①中最后剩下的数f(n)和②中最后剩下的数f(n-1) 一定在同一个位置,
通过找规律可以发现①和②中对应的数的关系为 ①中对应的数 = (②中对应的数+m+1) % n,
而刚才已经推出了f(n)和f(n-1)就是对应的数,所以f(n) = (f(n-1)+m+1) % n, 到此状态转移方程就求出来了
边界的话很明显是 f(1) = 0,
有了状态转移方程和边界就可以用动态规划算法(写for循环)高效地求出答案.
下面给出第一种和第三种解法的完整代码:
1 package ustb.huawei; 2 3 4 /* 5 题目:有一个整型数组a[n]顺序存放0 ~ n-1,要求每隔两个数删掉一个数,到末尾时循环至开头继续进行,求最后一个被删掉的数的原始下标位置。 6 7 以 8 个数(n=8)为例:{0,1,2,3,4,5,6,7}, 8 9 0->1->2(删除)->3->4->5(删除)->6->7->0(删除),如此循环直到最后一个数被删除。 10 11 */ 12 public class DeleteNum { 13 14 private static class LinkedNode { 15 16 int value; 17 LinkedNode next; 18 19 LinkedNode(int value) { 20 21 this.value = value; 22 } 23 24 public int getValue() { 25 return value; 26 } 27 } 28 29 30 31 32 /** 33 * 34 * 解法1: 使用循环链表模拟删数字的过程, 35 * @param n n为数组的长度 36 * @param m 每隔m个数删除一个数字 37 * @return 38 */ 39 public static int getLastNum_1(int n, int m) { 40 41 if (n < 1 || m < 1) { 42 throw new RuntimeException("输入的参数不合法"); 43 } 44 45 //初始化循环链表 46 LinkedNode head = new LinkedNode(0); 47 LinkedNode tail = head; 48 for (int i = 1; i < n; i++) { 49 tail.next = new LinkedNode(i); 50 tail = tail.next; 51 } 52 53 //上面的循环出来后tail指向 new LinkedNode(n-1);所以下面让tail.next指向head,即完成了循环链表的初始化 54 tail.next = head; 55 56 //模拟删数字过程 57 LinkedNode currNode = head; 58 int count = 1; 59 while (currNode != currNode.next) { //当只剩一个节点时就会跳出循环 60 if (count == m) { //每遍历m个数删一个数 61 currNode.next = currNode.next.next; 62 currNode = currNode.next; 63 count = 1; 64 } else { //还没遍历到m个数就继续遍历 65 currNode = currNode.next; 66 count++; 67 } 68 } 69 70 return currNode.getValue(); 71 } 72 73 74 75 76 /* 77 解法2:找出规律,用公式求解 78 公式为: 79 f(n) = (f(n-1) + m + 1) % n; 80 f(1) = 0; 81 其中f(n)为数组长度为n时按每隔m个数删除一个数,最后一个剩下的数的数值, 82 */ 83 public static int getLastNumByMath(int n, int m) { 84 85 if (n < 1 || m < 1) { 86 throw new RuntimeException("输入的参数不合法"); 87 } 88 89 int lastNum = 0; //N为1时的lastNum 90 91 for (int i = 2; i < n+1; i++) { //求出N为n时的lastNum 92 lastNum = (lastNum + m + 1) % i; 93 } 94 95 return lastNum; 96 } 97 98 99 100 //测试 101 public static void main(String[] args) { 102 103 System.out.println(getLastNum_1(8, 2)); 104 105 System.out.println(getLastNumByMath(8, 2)); 106 107 108 } 109 110 }