【算法】约瑟夫环问题

news2024/10/19 10:25:59

据说著名的犹太历史学家Josephus有过以下故事, 罗马人占领乔塔帕特, 39个犹太人与Josephus和他的朋友躲在洞中,其中39个犹太人决定自杀,
,他们的自杀方式是41个人绕成一圈,第一个人报数1,报数到3的人自杀。然后新一个人重新报数为1。最终活下来的人可以自由选择自己的命运。当剩下约瑟夫斯和他朋友时,说服了对方,选择向罗马军队投降,不再自杀。
约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。其实,这是一个数学问题。

前置准备

  • 数据结构-链表
  • 本题至少需要了解链表删除节点的逻辑,和循环链表的概念

初阶部分:链表组织结构, 模拟上述的过程求解。(本解法在洛谷中只能过部分用例。考察基本的coding)😏
进阶部分:数学和递归,迭代解法。 给定洛谷题中不需要链表组织了, 但建议读者写一份链表版本的结构代码

题目:约瑟夫环

在这里插入图片描述

直观解法:模拟

一种直观的解法。
[1,n]的数据以循环单链表的形式存储。然后模拟计数过程自杀过程。
定义一个count变量, 当count报到k的时候就执行链表的删除, 然后重置count

  • 定义一个Main类, 里面定义一个内部类Node
static class Node {
		int val;
		Node next;

		public Node(int val) {
			this.val = val;
		}
	}

构建[1,n]的循环单链表

public static Node createList(int n) {
		if(n < 1) {
			return null;
		}
		//构建[1...n]的序列
		Node head = new Node(1);
		Node tail = head;
		for(int i=2;i<=n;i++) {
			tail.next = new Node(i);
			tail = tail.next;
		}
		tail.next = head;//成环。
		return head;
	}

模拟约瑟夫环逻辑, 返回最后存在的节点

public static Node josephusKilll(Node head, int k) {
		//链表为空,单节点的循环链表,输入k不合理的逻辑。
		if(head==null||head.next==head || k < 1) {
			return head;
		}
		Node last = head;//last是head的前驱节点
		while(last.next!=head) {
			last = last.next;
		}
		int count = 0;//计数变量
		while(head != last) {
			if(++count == k) {
				last.next = head.next;//删除head引用节点
				count = 0;//重新计数
			}
			else {
				last = last.next;
			}
			head = last.next;
		}
		return head;
	}

compute函数:返回最后存活节点的编号

public static int compute(int n, int k) {
		Node head = createList(n);//创建单向循环链表
		head = josephusKilll(head, k);//返回约瑟夫环最后的节点。
		return head==null?-1:head.val;
	}

main函数处理输入输出逻辑

public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StreamTokenizer in = new StreamTokenizer(br);
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		in.nextToken();
		int n = (int) in.nval;
		in.nextToken();
		int k = (int) in.nval;
		out.println(compute(n,k));
		out.close();
		br.close();
	}

Main类

import java.io.*;

public class Main {
	static class Node {
		int val;
		Node next;

		public Node(int val) {
			this.val = val;
		}
	}
	public static Node createList(int n) {
		if(n < 1) {
			return null;
		}
		//构建[1...n]的序列
		Node head = new Node(1);
		Node tail = head;
		for(int i=2;i<=n;i++) {
			tail.next = new Node(i);
			tail = tail.next;
		}
		tail.next = head;//成环。
		return head;
	}
	public static Node josephusKilll(Node head, int k) {
		//链表为空,单节点的循环链表,输入k不合理的逻辑。
		if(head==null||head.next==head || k < 1) {
			return head;
		}
		Node last = head;//last是head的前驱节点
		while(last.next!=head) {
			last = last.next;
		}
		int count = 0;//计数变量
		while(head != last) {
			if(++count == k) {
				last.next = head.next;//删除head引用节点
				count = 0;//重新计数
			}
			else {
				last = last.next;
			}
			head = last.next;
		}
		return head;
	}
	public static int compute(int n, int k) {
		Node head = createList(n);//创建单向循环链表
		head = josephusKilll(head, k);//返回约瑟夫环最后的节点。
		return head==null?-1:head.val;
	}

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StreamTokenizer in = new StreamTokenizer(br);
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		in.nextToken();
		int n = (int) in.nval;
		in.nextToken();
		int k = (int) in.nval;
		out.println(compute(n,k));
		out.close();
		br.close();
	}
}

提交代码到洛谷上
在这里插入图片描述

呃, 超时了。

模拟时间复杂度分析

由于0<n,k<10^6 。
删除一个节点要循环k次,一共要删除n-1个节点。
总的时间复杂度 O ( n k ) O(nk) O(nk), 当k,n都取最大时,k与n是线性关系,时间复杂度可认为 O ( n 2 ) O(n^2) O(n2)
因此, 对于简单小的数据测试量还可以, 但规模一大逃不开超时的命运。

解决方案2

方案1的方法, 在于不知道最后哪个节点能活下来, 只能靠着计数模拟的方式, 一步一步淘汰节点, 通过存在留下的节点确定最终节点。
那么, 有没有更高效的方式。比如,给我一个通项公式直接求出来, 或者递推公式递推一下求出最后的节点。

假设环形链表是1->2->3->4->5->1, 节点数是5,报数m是3。
那么最终节点是4活下来。

节点编号
11
22
33
44
55

干掉报数为3的节点, 这里标记为-表示被干掉了。

节点编号
13
24
3-
41
52

继续

节点编号
1-
21
3-
42
53

重复

节点编号
1-
21 & 3
3-
42
5-

最后,节点数小于m了,要重复报数了。
发现节点2报了3,干掉节点2。
最终节点4存活。
且节点4最后编号是1。

节点编号
41

好的, 我们记录一下最终存活节点的编号。
记作函数Num

编号函数Num(i), i是当前存活的节点数量
Num(1) = 1
Num(2)
Num(i-1)
Num(i)

可以得出上述节点4的编号规律
4->1
4->2
4->2
4->1
4->4

🆗,我们用自然智慧归纳出这个公式。
最后存活节点的编号是1, 请记住我是从编号1开始推导的。

老编号删除s后的新编号
1i-s+1
2i-s+2
s-2i-2
s-1i-1
s-
s+11
s+22
ii-s
  • 接下来有些神奇操作。
  1. 所有小于S的老编号对于的新编号满足
    o l d = n e w + s − i old = new + s - i old=new+si这个表达式。
  2. 所有大于S的老编号对于新编号满足
    o l d = n e w + s old = new + s old=new+s这个表达式。
  • 可以统一两个公式吗?
    答案是可以的。
    对于2的关系式,
    只需要 o l d = ( n e w + s − i − 1 ) % i + 1 old = (new + s - i - 1) \%i + 1 old=(new+si1)%i+1
    对于1的关系式, o l d = ( n e w + s − i ) % i + 1 old = (new + s - i)\%i + 1 old=new+si)%i+1这个表达式也是成立的。因为$0<=(new + s - i - 1)<S<=i。
    因为取模x的范围是[0,x],所以需要整体右移(-1)和上移(+1)进行调整, 不能单纯%i,因为会改变范围造成错误。
编号和报数的关系

s = ( m − 1 ) % i + 1 s = (m-1) \%i +1 s=m1%i+1

m:报数s:编号
11
ii
i+11
2ii

跟上面老编号和新编号的推导一样,通过取模和拼凑出来[1...i]的范围。

n与m的关系

老编号 = (新编号 + s − 1 ) % i + 1 老编号 = (新编号 + s - 1) \%i + 1 老编号=(新编号+s1)%i+1
s = ( m − 1 ) % i + 1 s = (m-1) \%i +1 s=m1%i+1
推导:
老编号 = ( 新编号 + ( m − 1 ) % i ) % i + 1 老编号 = (新编号 + (m-1)\%i) \%i +1 老编号=(新编号+(m1)%i)%i+1
化简:
老编号 = ( 新编号 + m − 1 ) % i + 1 老编号 = (新编号 + m-1) \%i +1 老编号=(新编号+m1)%i+1

f ( n , k ) , n 为当前节点总数, k 为报数 f(n,k), n为当前节点总数,k为报数 f(n,k),n为当前节点总数,k为报数
f ( n , k ) = ( f ( n − 1 , k ) + m − 1 ) % i + 1 f(n,k) = (f(n-1,k) + m - 1)\%i + 1 f(n,k)=(f(n1,k)+m1)%i+1

代码

迭代法重写compute函数即可, 不需要改上面的main函数

public static int compute(int n, int k){
		int ans = 1;//初始编号为1
		for(int i=2;i<=n;i++) {
			ans = (ans + k-1)%i + 1;//自下而上递推公式往上推。
		}
		return ans;
	}

perfect
在这里插入图片描述

递归法
本题递归过不了, 栈溢出报RE了。

public static int compute(int n, int k) {
		if(n==1) {
			return 1;//编号从1开始
		}
		else {
			return (compute(n-1,k)+k-1)%n+1;
		}
	}
参考和小小吐槽

本题唯一凹点是数学。
程序员代码面试指南这本书给了图解分析出解析式的推导
严格的数学推导

最后, 本题是线性递推,递归子问题严格不会重叠。 不知道为什么有些题标着动态规划的标签。🧐

最后

链表版本的josephusKilll, 感谢您的阅读。

public static Node josephusKilll(Node head, int k) {
    // 如果链表为空,或者链表中只有一个节点,或者k不合理(k < 1),直接返回原始链表
    if (head == null || head.next == head || k < 1) {
        return head;
    }

    // 初始化指针,指向链表的第二个节点,并开始计算链表中节点的数量
    Node cur = head.next;
    int n = 1; // 初始化节点数为1(因为head节点也算在内)
    
    // 遍历链表,计算链表的总长度n
    while (cur != head) { 
        n++;  // 每次循环增加1,表示链表的节点数量
        cur = cur.next;  // 指针移动到下一个节点
    }

    // 使用compute(n, k)函数,计算约瑟夫环中最后幸存者的编号
    // 该编号是从1开始计算的
    n = compute(n, k);

    // 根据计算出的编号,移动到链表中对应的节点位置
    // n表示最终幸存者的位置,需要遍历链表n-1次
    while (--n != 0) {
        head = head.next;  // 移动到下一个节点
    }

    // 最终head指向的是约瑟夫环中最后剩下的节点,将该节点的next指向自己
    // 这样就将链表中其他节点删除,形成单节点循环链表
    head.next = head;

    // 返回幸存的节点
    return head;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2217459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RK3588的demo板学习

表层的线宽是3.8mil: 换层之后线宽变成了4.2mil: (说明对于一根线&#xff0c;不同层线宽不同) 经典&#xff1a; 开窗加锡&#xff0c;增强散热&#xff0c;扩大电流&#xff1a; R14的作用&#xff1a;与LDO进行分压&#xff0c;降低LDOP的压差从而减小其散热&#xff1a;第…

如何系统的从0到1学习大模型?有哪些书籍推荐?

大模型应用得好&#xff0c;不仅需要海量的基础数据、大规模算力、综合人工智能发展成果的技术&#xff0c;还需要政产学研用各方的共同推进。 大模型不仅能生成结果、生成数据&#xff0c;更能传递价值观。应用于我国的大模型需要懂中文、懂中国文化、懂中国国情。大模型是全…

【Linux系统编程】环境基础开发工具使用

目录 1、Linux软件包管理器yum 1.1 什么是软件包 1.2 安装软件 1.3 查看软件包 1.4 卸载软件 2、Linux编辑器-vim 2.1 vim的概念 2.2 vim的基本操作 2.3 vim的配置 3、Linux编译器-gcc/g 3.1 gcc编译的过程​编辑​编辑​编辑 3.2 详解链接 动态链接 静态链接 4…

纯HTML实现标签页切换

纯HTML实现标签页切换 实现原理&#xff1a; HTML结构&#xff1a; 使用无序列表&#xff08;<ul>&#xff09;创建标签导航。每个标签是一个列表项&#xff08;<li>&#xff09;&#xff0c;包含一个链接&#xff08;<a>&#xff09;。每个链接指向对应的内…

商品计划:零售企业的痛点破解与运营优化指南

在现代零售业的激烈竞争中&#xff0c;商品计划不仅是企业盈利的关键&#xff0c;更是解决众多痛点的有效途径。零售企业在运营过程中常常面临各种挑战&#xff0c;如财务问题、库存管理、市场分析等。而科学、系统的商品计划可以帮助企业有效应对这些挑战&#xff0c;提升整体…

气膜:冰雪产业的创新解决方案—轻空间

随着冰雪运动的普及和发展&#xff0c;如何在不同季节和地区有效开展冰雪项目&#xff0c;成为了行业内的一个重要课题。气膜作为一种新兴的建筑形式&#xff0c;凭借其独特的优势&#xff0c;正在逐渐成为冰雪产业的创新解决方案。 优越的建筑特性 气膜建筑以其轻便、快速搭建…

Web Storage:数据储存机制

前言 在HTML5之前&#xff0c;开发人员一般是通过使用Cookie在客户端保存一些简单的信息的。在HTML5发布后&#xff0c;提供了一种新的客户端本地保存数据的方法&#xff0c;那就是Web Storage&#xff0c;它也被分为&#xff1a;LocalStorage和SessionStorage&#xff0c;它允…

【黑马redis高级篇】持久化

//来源[01,05]分布式缓存 除了黑马&#xff0c;还参考了别的。 目录 1.单点redis问题及解决方案2.为什么需要持久化&#xff1f;3.Redis持久化有哪些方式呢&#xff1f;为什么我们需要重点学RDB和AOF&#xff1f;4.RDB4.1 定义4.2 触发方式4.2.1手动触发save4.2.2被动触发bgsa…

软件工程:需求规格说明书(图书管理系统)

目录 1 导言 1.1 编写目的 1.2 参考资料 2 项目介绍 2.1 项目背景 2.2 项目目标 3 应用环境 3.1 系统运行网络环境 ​编辑 3.2 系统软硬件环境 4 功能模型 4.1 功能角色分析 4.1.1 图书管理员 4.1.2 普通读者 4.1.3 邮件系统 4.2 功能性需求 4.2.1 预定图…

AI+Xmind彻底解决你的思维导图

在写作领域、老师授课、产品经理等都会使用到思维导图&#xff0c;如果是一个个拖拉撰写太麻烦了。 本篇内容小索奇就教会大家利用AI结合Xmind制作思维导图。 先打开我们的AI软件 这里小索奇用ChatGPT&#xff08;可以使用kimi&#xff0c;豆包等大模型都可以&#xff09; P…

中小型医院网站开发:Spring Boot入门

2 相关技术简介 2.1 Java技术 Java是一种非常常用的编程语言&#xff0c;在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中&#xff0c;Java的身影无处不在&#xff0c;并且拥有旺盛的生命力。Java的跨平台能力十分强大&#xff0c;只需一次编译&#xff0c;…

上市公司资产误定价Misp计算数据-含参考资料及代码(2006-2023年)

数据说明&#xff1a;参考《经济研究》期刊游家兴&#xff08;2012&#xff09;老师的做法&#xff0c;先根据行业内所有公司推算出公司的基础价值&#xff0c;进而通过对公司的实际价值与基础价值进行对比&#xff0c; 来衡量公司相对于业内同行的误定价水平&#xff0c;具体大…

D39【python 接口自动化学习】- python基础之函数

day39 函数的返回值 学习日期&#xff1a;20241016 学习目标&#xff1a;函数&#xfe63;-52 函数的返回值&#xff1a;如何得到函数的执行结果&#xff1f; 学习笔记&#xff1a; return语句 返回值类型 def foo():return abc var foo() print(var) #abc# 函数中return函…

python实现录屏功能

python实现录屏功能 将生成的avi文件转为mp4格式后删掉avi文件 参考感谢&#xff1a;https://www.cnblogs.com/peachh/p/16549254.html import os import cv2 import time import threading import numpy as np from PIL import ImageGrab from pynput import keyboard from da…

统一认证与单点登录:简化用户体验的关键解决方案

引言 在数字化时代&#xff0c;企业往往需要管理多个应用和系统&#xff0c;随之而来的是用户密码和身份认证管理的复杂性。统一认证&#xff08;Single Sign-On, SSO&#xff09;作为一种身份管理解决方案&#xff0c;不仅可以减少用户在多个系统间切换登录的麻烦&#xff0c…

选择合适的SSL证书

随着我们在线业务的增长&#xff0c;确保网站安全变得越来越重要。对于许多人来说&#xff0c;保护网站安全的想法似乎令人望而生畏&#xff0c;尤其是在有各种SSL证书可用的情况下。您可能想知道哪一个最适合您的业务需求或如何浏览这些选项。 除了SSL证书之外&#xff0c;使…

Nuxt.js 应用中的 app:resolve 事件钩子详解

title: Nuxt.js 应用中的 app:resolve 事件钩子详解 date: 2024/10/17 updated: 2024/10/17 author: cmdragon excerpt: app:resolve 是 Nuxt.js 中的生命周期钩子,在解析 app 实例后调用。这个钩子允许开发者在应用完全初始化后执行一些自定义操作,比如注册插件、设置中…

D40【python 接口自动化学习】- python基础之函数

day40 练习&#xff1a;函数实现电商购物车功能 学习日期&#xff1a;20241017 学习目标&#xff1a;函数 - 53 如何利用函数实现电商购物车功能&#xff1f; 学习笔记&#xff1a; 购物车功能分析 定义商品为列表 实现商品的展示 # 商品 products[[1000,iphone,phone,1200…

(MySQL)索引

注&#xff1a;此博文为本人学习过程中的笔记 1.简介 1.1.概念 MySQL的索引是一种数据结构&#xff0c;它可以帮助数据库高效地查询&#xff0c;更新数据表中的数据。索引通过一定的规则排列数据表中的记录&#xff0c;使得对表的查询可以通过对索引的搜索来加快查询 MySQ…

NOIP2023(惨烈)做题记(泪奔::>_<::)

P9868 [NOIP2023] 词典 1.这道题倒是做出来了&#xff0c;大概思路如下&#xff1a; 对于每一个字符串&#xff0c;可以存储一个 k 和 k2​ 分别表示这个字符串包含的字符中的字典序最小字符与字典序最大字符&#xff0c;这一步可以初始就处理好。 然后判断每一个字符串是否…