约瑟夫环问题
经典约瑟夫环问题
¶有
暴力法
¶使用链表进行模拟,起身离开的复杂度为
- 时间复杂度:
- 额外空间复杂度:
树状数组
¶用一个数组
: 编号为 的人还坐在那里 : 编号为 的人已经起身离开
则可以使用树状数组维护前缀和[1],报数相当于快速找到某个指定位置开始的区间
在前期
- 时间复杂度(宽松上界):
- 额外空间复杂度:
123456789101112131415161718192021import { type BinarySearchTree, createBinarySearchTree } from './bit'
export function lastRemaining(N: number, M: number): number { if (N <= 0) return -1 if (N === 1) return 1
const bit: BinarySearchTree = createBinarySearchTree(N) for (let i = 1; i <= N; ++i) bit.add(i, 1)
let p = 0 for (let n = N; n > 0; --n) { let total = M % n for (total = total === 0 ? M : total; total > 0; ) { const _end = Math.min(N, p + total) total -= bit.sum(_end) - bit.sum(p) p = _end === N ? 0 : _end } bit.add(p === 0 ? N : p, -1) } return (p + N - 1) % N}
树状数组 + 二分
¶为了快速跳过中间的
- 时间复杂度:
- 额外空间复杂度:
下面的代码中假定当三分之一的人起身离开后,
12345678910111213141516171819202122232425262728293031323334353637import { type BinarySearchTree, createBinarySearchTree } from './bit'
export function lastRemaining(N: number, M: number): number { if (N <= 0) return -1 if (N === 1) return 1
const bit: BinarySearchTree = createBinarySearchTree(N) for (let i = 1; i <= N; ++i) bit.add(i, 1)
let p = 0 for (let n = N, threshold = Math.round((N * 2) / 3); n > 0; --n) { let total = M % n for (total = total === 0 ? M : total; total > 0; ) { let _end = p + total const target = total + bit.sum(p)
if (n < threshold) { if (_end < N) { let lft = _end let rht = N + 1 while (lft < rht) { const mid = (lft + rht) >> 1 if (bit.sum(mid) < target) lft = mid + 1 rht = mid } _end = lft < N ? lft : N } }
if (_end > N) _end = N total = target - bit.sum(_end) p = _end === N ? 0 : _end } bit.add(p === 0 ? N : p, -1) } return (p + N - 1) % N}
递推
¶考虑这样一种操作:每次有人起身离开时,将TA右手边的人重新编号为
为方便表述,不妨记圈中剩余
即
不妨设
由
递推写法
- 时间复杂度:
- 额外空间复杂度:
03.ts | 6 lines.123456export function lastRemaining(N: number, M: number): number {if (N <= 0) return -1let ans = 0for (let n = 2; n <= N; ++n) ans = (ans + M) % nreturn ans}- 时间复杂度:
递归写法
- 时间复杂度:
- 额外空间复杂度:
04.ts | 10 lines.12345678910export function lastRemaining(N: number, M: number): number {if (N <= 0) return -1return round(N)function round(n: number): number {if (n === 1) return 0const y = round(n - 1)return (y + M) % n}}- 时间复杂度:
不定步长的约瑟夫问题
¶在经典约瑟夫问题的基础上,每次起身离开的人报的的数不同。其中第
不难发现,前面提到的 暴力法、前缀和-树状数组 算法仍然适用。这里仅就 递推法 进行讨论。
递推
¶对比游戏规则,不难想到上文中提到的映射关系
需要更新为
则不难得到新的递推方程:
递推写法
- 时间复杂度:
- 额外空间复杂度:
05.ts | 6 lines.123456export function lastRemaining(N: number, M: number[]): number {if (N <= 0) return -1let ans = 0for (let n = 2; n <= N; ++n) ans = (ans + M[N - n]) % nreturn ans}- 时间复杂度:
递归写法
- 时间复杂度:
- 额外空间复杂度:
06.ts | 10 lines.12345678910export function lastRemaining(N: number, M: number[]): number {if (N <= 0) return -1return round(N)function round(n: number): number {if (n === 1) return 0const y = round(n - 1)return (y + M[N - n]) % n}}- 时间复杂度: