390. 消除游戏
这一题是约瑟夫环的入门级问题

看到实例之后和下面一题一样,拉成一个圆就知道为什么是类似的题了,约瑟夫环针对的问题就是一个循环的环上面,间隔n个数字进行删除的问题。
这个题的难点在于如何去分析这个循环潜藏的对称性:
以上公式有点歧义,实际上是指一个过程,f(i)是指从左往右,再从右往左这个过程,而不是进行一次“从左往右从右往左”结束之后的结果。因此成立的条件是两个例子:
vec![1, 2, 3, 4, 5, 6, 7, 8, 9]f[i] => vec![2, 4, 6, 8] => vec![2, 6] => vec![6]f'[i]=> vec![8, 6, 4, 2] => vec![8, 4] => vec![4]
本质上源于这个:
因为是对应位置的,因此无论如何都是对位相加。
下一个问题是,约瑟夫环在消除之后都会导致重新编号:
根据上面的发现,现在的问题来到了我们进行一次消除之后的数组变化:
根据递推条件,直接写代码即可:
class Solution {public int lastRemaining(int n) {return n == 1 ? 1 : 2 * (n / 2 + 1 - lastRemaining(n / 2));}}pub fn last_remaining(n: i32) -> i32 {match n {1 => 1,_ => (n/2 + 1 - Solution::last_remaining(n/2)) * 2}}
1823. 找出游戏的获胜者
这一题是典型的约瑟夫环问题,这里先看一下模拟的办法,比较锻炼过程思维:
核心是,我们由于每次绕回来之后会遇见已经遍历过的:
- 需要进行标记
- 需要取模实现循环
pub fn find_the_winner(n: i32, k: i32) -> i32 { // 加10因为出现了溢出 let mut checked = vec![false; n as usize + 10]; // cnt记录选取到了几个人,cur记录当前是第几个人 // 为了方便取模,我们调整点编号从 00 开始,在返回答案时再重新调整为从 11 开始。 let (mut cur, mut cnt) = (0, 0); while cnt != n-1 { // 走k个,注意,因为走过的没有被去除,因此还是在里面走,需要多走 for i in 0..k-1 { cur += 1; while checked[cur % n as usize] { cur += 1; } } // 去一个 checked[cur % n as usize] = true; cnt += 1; cur += 1; // 注意更新这个,for里确保了cur一定不是走过的,这里需要确保下一个 // 开始的位置不能是找过的 while checked[cur % (n as usize)] { cur += 1; } } (cur as i32) % n + 1 }
pub fn find_the_winner(n: i32, k: i32) -> i32 { if n <= 1 { return n } let ans = (Solution::find_the_winner(n-1, k) + k) % n; match ans { 0 => n, _ => ans } }
