报数问题
时间限制:3000MS
内存限制:589824KB
题目描述:
当n个人排成一条直线,从左到右的编号分别为1到n。现在从第1个人开始报数,在报数过程中,如果有人报到m则出列,下一个人将继续从1开始报数,第n个人报完数之后再接着往回报数,即倒数第2个人继续报下一个数,当报到第1个人后,第2个人在接着报数。如此循环,直到只留下一个人为止、
例如当n=2,m=3时,第1个人报1,第2个人报2,接下来第1个人报3,出列,留下第2个人。
当输入n和m时,请问通过(n-1)轮报数后,最后留下的那个人的编号是多少?
输入描述
单组输入。
输入两个正整数n和m,n<=10000,m<=1000。两个正整数之间用空格隔开
输出描述
输出最后留下的那个人的编号
样例输入
5 3
样例输出
1
解答
从题目描述中的红色部分,可以发现这道题并不是一个循环的报数问题,在头部和尾部并不是循环一直走的,所以不能使用约瑟夫环来解决这个问题,需要使用双向链表来解决这个问题。
1.题目要求
首先最重要的是读懂题目在干什么。
这是题目要求的每次输出的值,一定要注意到最后一个人是向前报数,到第一个人时又要向后报数。最后一次输出1,可以明显的看到可以用双向链表解决这个问题。
2.解题思路
确定了要使用双向链表后,就得如果创建双向链表了,从这个题中可以看出,一定和头结>点与尾结点有密切的联系,所以我直接创建双向链表时,定义了它的头结点 first
和它的尾结点 last
,然后这样就比较容易的判断到它是否到了头和尾,对于删除哪个结点,这个比较简单,定义一个index
变量,在遍历循环时候一直累加,如果是输入m
的倍数,则这个结点需要删除。删除时针对头结点和尾结点的删除我特意写了两个方法,因为双向链表的头结点与尾结点的删除和其他的节点删除不一样。最重要的是,向前遍历和向后遍历。这里可以设置一个标志位flag
。默认时flag为true,flag为true时向后遍历,flag为false时向前遍历。所以如果走到最后一个节点或者删除了最后一个节点时,flag就需要设为flase向前;如果走到第一个节点或者删除第一个节点,flag就需要设为true,向后遍历。
3.代码实现
3.1 java版本
编程环境:IDEA 2018.2.4 x64
JDK版本:java version “1.8.0_144”
思路在代码中的实现都有些注释,看完注释就行。
import java.util.Scanner;
/**
* @author zzy
* @time 2021/8/17
* @description 双向链表 解决报数问题
*/
public class DoubleLinkList {
/**
* 头
*/
private Node first;
/**
* 尾
*/
private Node last;
public DoubleLinkList(){
first = null;
last = null;
}
/**
* 从双向链表的尾部插入数据
* @param value 添加int型数字
*/
public void insertLast(int value){
Node newNode = new Node(value);
//如果头结点为空,直接让头结点等于新的节点
if (first == null) {
first = newNode;
}else {
//建立连接
last.next = newNode;
newNode.previous = last;
}
//把最后个节点设置为最新的节点
last = newNode;
}
/**
* 判断双向链表是否为空
* @return 如果头结点为空既双向链表为空,则返回true
*/
public boolean isEmpty(){
return first == null;
}
/**
* 删除头节点
* 要去除两个指针,一个指向下个的next ,一个是next的previous指向前面的
* @return
*/
public Node deleteFirst(){
if (first == null) {
throw new RuntimeException("链表数据不存在");
}
Node temp = first;
if (first.next == null) {
last = null;
}else {
first.next.previous = null;
}
first = temp.next;
return temp;
}
/**
* 删除尾节点
* 要去除两个指针,一个指向下个的next ,一个是next的previous指向前面的
* @return
*/
public Node deleteLast(){
if (first == null) {
throw new RuntimeException("链表数据不存在");
}
Node temp = last;
if (first.next == null) {
last = null;
//把第一个删除
first = null;
}else {
last.previous.next = null;
}
last = temp.previous;
return temp;
}
/**
* 显示所有的数据
*/
public void display(){
if (first == null) {
return;
}
Node current = first;
while(current != null){
current.display();
current = current.next;
}
}
public static void main(String[] args) {
//新建一个双向链表
DoubleLinkList linkList = new DoubleLinkList();
//输入n
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
//按1-n的序号顺序插入链表,模拟n个人的座位
for (int i = 1; i <= n; i++) {
linkList.insertLast(i);
}
//设置一个指针指向头结点
Node temp = linkList.first;
/*
m 为叫到m号出列的人
index为标志位,判断是不是到了第m号人
num为计算删除了多少值,如果num==n-1,那么说明双向链表中只有一个值,需要退出删除的循环
flag标志位判断双向链表向前遍历还是向后遍历 false时向前遍历,true向后遍历 默认向后遍历
*/
int m, index = 0, num = 0;
boolean flag = true;
//输入m值
m = scanner.nextInt();
while (true) {
index++;
//如果temp走到最后一个节点,将flag赋值为false 既向前遍历
if (temp == linkList.last) {
flag = false;
}else if(temp == linkList.first){
flag = true;
}
if (index % m == 0) {
System.out.printf("删除的值: ");
temp.display();
if (temp == linkList.last) {
//如果待删除的节点为尾结点,则删除尾结点
linkList.deleteLast();
flag = false;
} else if (temp == linkList.first) {
//如果待删除的节点为头结点,则删除头结点
linkList.deleteFirst();
flag = true;
} else {
//如果删除的既不是头也不是尾,则安装正常的节点删除
temp.previous.next = temp.next;
temp.next.previous = temp.previous;
}
//计算删除节点的个数
num++;
}
//判断当前遍历的方向
if (flag) {
//如果为true,则向后遍历
temp = temp.next;
} else {
//如果为false,则向前遍历
temp = temp.previous;
}
if (num == n - 1) {
//删除的个数num等于N-1时,说明双向链表中,只有一个值
break;
}
}
//输出双向链表中的最后一个值
System.out.printf("最后剩余的值:");
linkList.display();
}
}
class Node{
public Node previous;
public Node next;
public long data;
public Node(long data){
this.data = data;
}
public void display(){
System.out.println(data);
}
}
3.2 c版本
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct line {
int data;
struct line* prior;
struct line* next;
}line;
//初始化
line* initLine(line* head, int n);
//显示
void display(line* head);
//删除头节点
line* delHeadNode(line* head);
//删除尾节点
line* delTailNode(line* head);
//找到尾节点
line* findTailNode(line* head);
void solution();
int main() {
solution();
return 0;
}
void solution() {
line* head = NULL;
int n; //人数
scanf("%d", &n);
head = initLine(head, n);
line* temp = head;
line* tail = findTailNode(head); //确定尾节点
int m, index = 0, num = 0; //index为标志位,判断是不是到了第m号人 num删除的人数
bool flag = true; // false时向前遍历,true向后遍历 默认向后遍历
scanf("%d", &m);
while (1) {
index++;
//如果temp走到最后一个节点,将flag赋值为false 既向前遍历
if (temp == tail) {
flag = false;
//如果temp走到第一个节点,将flag赋值为true 既向后遍历
}else if(temp == head){
flag = true;
}
//找到需要删除的值
if (index % m == 0) {
printf("删除的值:%d\n", temp->data);
if (temp == tail) {
//如果待删除的节点为尾结点,则删除尾结点
head = delTailNode(head);
//重新确定尾节点
tail = tail->prior;
//temp指向新的尾节点
temp = tail;
//改变遍历方向
flag = false;
}else if (temp == head) {
//如果待删除的节点为头结点,则删除头结点
head = delHeadNode(head);
//temp指向新的头节点
temp = head;
//改变遍历方向
flag = true;
}else {
//如果删除的既不是头也不是尾,则正常的节点删除
temp->prior->next = temp->next;
temp->next->prior = temp->prior;
}
num++;
}
if (flag) {
temp = temp->next; //向前遍历
}else {
temp = temp->prior;
}
if (num == n - 1) { //向后遍历
break;
}
}
display(head);
}
line* initLine(line* head, int n) {
head = (line*)malloc(sizeof(line));
head->prior = NULL;
head->next = NULL;
head->data = 1;
line* list = head;
for (int i = 2; i <= n; i++) {
line* body = (line*)malloc(sizeof(line));
body->prior = NULL;
body->next = NULL;
body->data = i;
list->next = body;
body->prior = list;
list = list->next;
}
return head;
}
line* delHeadNode(line* head) {
if (head == NULL || head->next == NULL) {
return NULL;
}
head->next->prior = NULL;
return head->next;
}
line* delTailNode(line* head) {
if (head == NULL) {
return NULL;
}
line* temp = head;
while (temp != NULL) {
if (temp->next == NULL) {
temp->prior->next = NULL;
break;
}
temp = temp->next;
}
return head;
}
line* findTailNode(line* head) {
if (head == NULL) {
return NULL;
}
line* tail = head;
while (1) {
if (tail->next == NULL) {
break;
}
tail = tail->next;
}
return tail;
}
void display(line* head) {
line* temp = head;
while (temp) {
if (temp->next == NULL) {
printf("%d\n", temp->data);
}
else {
printf("%d ", temp->data);
}
temp = temp->next;
}
}
4.问题总结
在做这道题时,最开始只想到了在删除最后一个节点和删除第一个节点时让遍历方向改变。没有在普通遍历到第一个节点和最后一个节点时改变方向,所以一直提示空指针异常,也就是指针指飞了。这么一个小小的问题用了一个小时才解决。最开始看到报数问题就想到的是约瑟夫环,循环链表。仔细一读题,才发现不是。所以算法题不能被某个题所约束啊…