haskell 基础题解(21)

约瑟夫环

【题目】n 个孩子编号从1到n,顺时针站一圈。从1号孩子开始1,2,3顺时针报数。报到 3 的孩子退出游戏。他的顺时针方向下一个孩子重新开始1,2,3报数。。游戏直到剩下一个孩子,求剩下的这个孩子的编号。

这个问题如果用命令式语言的思考习惯,最先想到的就是模拟。作一个数列,装入1~n 的数,然后,模拟游戏的规则,不断地删去某个元素,直到只剩下一个元素。
haskell 也可以这么做,但这不是它的 style,因为它没有办法修改状态,所以,只能传入一个状态参数,再传出一个变化了的状态。如果希望简洁描述,就要用到 State Monad 了。概念抽象。这此以后再表吧。

先上一个 java 的模拟式想法的:


import java.util.*;
public class T21
{
	static int ysf(int n){
		List lst = new ArrayList();
		for(int i=0; i<n; i++) lst.add(i+1);
		
		while(lst.size()>1){
			lst.add(lst.remove(0));
			lst.add(lst.remove(0));
			lst.remove(0);
		}
		
		return (Integer)lst.get(0);
	}
	
	public static void main(String[] args){
		for(int n=1; n<20; n++){
			System.out.println( n + " -> " + ysf(n));
		}
	}
}

为发挥 Haskell 函数式优势,另选一思路:
假设,我们已经知道了如下事实:
当有 n 个孩子游戏时,编号为 x 的孩子最后剩下。这意味着什么呢?
如果不从1 号开始报数,而是从 5 号开始报数,你能立即推算出最后剩谁吗?
很明显,剩下的编号具体是多少不重要,剩下的号与第一个报数人的编号的距离才是个重要的信息,它与从哪里开始报数是无关的。

任意两个号码,a 到 b 的距离是 (n + b - a) % n

有了上面的知识,要开动脑筋了:
如果 N-1个孩子游戏,最后剩下号码为 x
现在把第N个孩子加入到队伍中。我们怎么做,才能让他第一次就出列呢?
显然,从N-2 开始玩这个游戏就可以了。到最后剩下号码必为 x

开始报数的号结果剩下的号
N - 2x
1??

现在问题清楚起来,已知从N-2号开始报数,剩下为 x号,
那么,从1号开始报数,剩下哪个号呢?
用递归写出来如下:

ysf :: Int -> Int
ysf 1 = 1
ysf n = let delta = (n + ysf (n-1) - (n-2)) `mod` n
        in 1+delta

ok = [show i ++ " -> " ++ show (ysf i) |i<-[1..20]]
main = putStr $ unlines ok

对比一下上面的暴力结果,避免低级失误。
如果报数不是 1,2,3,而是逢 k 出列呢? 稍加改造就可以。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值