Fork me on GitHub

约瑟夫问题

LeetCode 剑指offer62

0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。 —— 【约瑟夫问题】维基百科

 

一、模拟单向环形链表

据题解大佬所言,LinkedList会超时,因为链表remove中删除复杂度为1,但查找到该索引需n;ArrayList相反是查找为1,删除为n。不过因为ArrayList删除是拷贝的后面元素是连续地址的,而链表需要大量访问非来纳许地址,因此耗时。

复杂度为O(n2)

class Solution {
    public int lastRemaining(int n, int m) {
        ArrayList<Integer> list = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            list.add(i);
        }
        int idx = 0;
        while (n > 1) {
            idx = (idx + m - 1) % n;
            list.remove(idx);  // 注意remove后idx自动后移
            n--;
        }
        return list.get(0);
    }
}

 

二、数学解法

f(n,m)与f(n-1,m)之间的关系:当n个数先删掉第一个坐标(m-1)%n时,可以看成n-1个数组成的环,其中首元素坐标为m。而f(n-1,m)的首元素坐标为0,因此 f(n,m) = ( f(n-1,m) + m ) % n。

class Solution {
    public int lastRemaining(int n, int m) {
        int ans = 0;
        // 最后一轮剩下2个人,所以从2开始反推
        for (int i = 2; i <= n; i++) {
            ans = (ans + m) % i;
        }
        return ans;
    }
}

 

三、马拉车算法 Manacher's algorithm

参考链接

构造T,在每个字符两边添加‘#’,n+(n+1)统一成奇回文。 构造P对应以T中每个字符为中心的回文长度,寻找最大值。

1、最大半径减1等于最长回文串的长度

2、最长回文字符的起始位置(s中的索引)是中间位置(T或P中的索引)减去半径(P中的值+1)再除以2

这里的P为回文长度,因此无需与半径做转换。

public class Solution {
 2     // Transform S into T.
 3     // For example, S = "abba", T = "^#a#b#b#a#$".
 4     // ^ and $ signs are sentinels appended to each end to avoid bounds checking
 5     String preProcess(String s) {
 6         int n = s.length();
 7         if (n == 0) return "^$";
 8 
 9         String ret = "^";
10         for (int i = 0; i < n; i++)
11         {
12             ret += "#" + s.substring(i, i + 1);
13         }
14         
15         ret += "#$";
16         return ret;
17     }
18     public String longestPalindrome(String s) {
19         String T = preProcess(s);
20         int length = T.length();
21         int[] p = new int[length];
22         int C = 0, R = 0;
23         
24         for (int i = 1; i < length - 1; i++)
25         {
26             int i_mirror = C - (i - C);
27             int diff = R - i;
28             if (diff >= 0)//当前i在C和R之间,可以利用回文的对称属性
29             {
30                 if (p[i_mirror] < diff)//i的对称点的回文长度在C的大回文范围内部
31                 { p[i] = p[i_mirror]; }
32                 else
33                 {
34                     p[i] = diff;
35                     //i处的回文可能超出C的大回文范围了
36                     while (T.charAt(i + p[i] + 1) == T.charAt(i - p[i] - 1))
37                     { p[i]++; }
38                     C = i;
39                     R = i + p[i];
40                 }
41             }
42             else
43             {
44                 p[i] = 0;
45                 while (T.charAt(i + p[i] + 1) == T.charAt(i - p[i] - 1))
46                 { p[i]++; }
47                 C = i;
48                 R = i + p[i];
49             }
50         }
51 
52         int maxLen = 0;
53         int centerIndex = 0;
54         for (int i = 1; i < length - 1; i++) {
55             if (p[i] > maxLen) {
56               maxLen = p[i];
57               centerIndex = i;
58             }
59         }
60         return s.substring((centerIndex - 1 - maxLen) / 2, (centerIndex - 1 - maxLen) / 2 + maxLen);        
61     }
62 }

 

posted @ 2020-08-06 22:10  Faded828x  阅读(156)  评论(0编辑  收藏  举报