一道面试题的java解法(其实就是一道算法题):
有n个人围成一圈,编号分别为1到n,第一个人从1开始报数,报到m的人出列,然后从下一个人重新从1开始报数,报到m的人出列,如果报数到了最后一个人,下个人就继续从第一个人开始报数。求最后一个出列的人的编号?
也可以这样说:
有n个人围成一圈,编号分别为1-n,第一个人从1开始报数,报到m的人出列,就出列的人的编号顺序?
(如n为6编号则为1,2,3,4,5,6个人,m为3,出列的顺序是:3,6,4,2,5,1).
看到这个题我第一想法就是通过一个双向循环链表来解答。
用一个链表,每个节点保存了上个节点和下一个节点的对象,就像C的指针。同时这个节点还保存了自己的下标idx,和每次报数的值no,主要有如下几个类
节点类Node,定义双向循环链表的接口:IDoubleLinkList 和 实现类DoubleLinkList以及实现算法和测试的类:DoubleLinkListTest
Node类:
public class Node {
Node pre;
Object element;//存放人员的编号
Node next;
int no;//节点报数值
int idx;//节点下标
Node(Node nextval)
{
pre = nextval;
next = nextval;
}
Node(Node priorval, Object obj, Node nextval)
{
pre = priorval;
element = obj;
next = nextval;
}
public Node getPrior()
{
return pre;
}
public void setPrior(Node priorval)
{
pre = priorval;
}
public Node getNext()
{
return next;
}
public void setNext(Node nextval)
{
next = nextval;
}
public Object getElement()
{
return element;
}
public void setElement(Object obj)
{
element = obj;
}
public String toString()
{
return element.toString();
}
}
IDoubleLinkList接口:
public interface IDoubleLinkList
{
public void insert(int i, Object obj) throws Exception; // 插入
public Object delete(int i) throws Exception; // 删除
public Object getData(int i) throws Exception; // 获取i元素
public int size(); // 表数据总数
public boolean isEmpty(); // 是否为空
}
DoubleLinkList类:
public class DoubleLinkList implements IDoubleLinkList {
Node head;
Node current;
int size;
/**
* 构造函数: 初始化循环链表.
*/
DoubleLinkList()
{
head = current = new Node(null);
head.pre = head;
head.next = head;
size = 0;
}
/**
* 定位成员函数index(int i)的实现 循环从头开始查找,循环的条件是:1.定位完成j==i;2.链表查找结束了.
*
* @param i
* @throws Exception 当参数i错误时,抛出异常.
*/
public void index(int i) throws Exception
{
if (i < -1 || i > size - 1)
{
throw new Exception("i error in INDEX of DoubleLinkList.");
}
if (i == -1){
current=head;
}else{
current = head.next;
}
int j = 0;
while (current != head && j < i)
{
current = current.next;
j++;
}
}
@Override
public void insert(int i, Object obj) throws Exception
{
if (i < 0 || i > size)
{
throw new Exception("i error in INSERT.");
}
index(i - 1);//根据下标i寻找当前节点current
current.idx=i-1;
current.setNext(new Node(current.getNext(), obj, current.next.getPrior()));
current.next.next.setPrior(current.next.getNext());
size++;
}
@Override
public Object delete(int i) throws Exception {
if (size == 0)
{
throw new Exception("Link Blank in DELETE.");
}
if (i < 0 || i > size - 1)
{
throw new Exception("i error in DELETE.");
}
index(i - 1);//根据下标i寻找当前节点current
Object obj = current.next.getElement();
current.setNext(current.next.next);
current.next.setPrior(current.getNext());
size--;
return obj;
}
@Override
public Object getData(int i) throws Exception {
if (i < -1 || i > size - 1)
{
throw new Exception("i error in getData.");
}
index(i);
return current.getElement();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
}
DoubleLinkListTest类:
public class DoubleLinkListTest
{
public static void main(String agrs[])
{
DoubleLinkListTest test=new DoubleLinkListTest();
test.findPerson(6,3);
}
private void findPerson(int n,int m) {
//初始化链表对象,节点的下标,每个人的编号
DoubleLinkList doubleLinkList = new DoubleLinkList();
try
{
for (int i = 0; i < n; i++)
{
doubleLinkList.insert(i, new Integer(i + 1));
}
//注意链表的头节点为null,不算在链表的实际节点中
Node currNode = doubleLinkList.head.next;//第一个节点为头节点的下一个节点
int len = doubleLinkList.size > m ? doubleLinkList.size : m;//循环的大小,链表的长度和m的算大,就按照算来循环
for (int i = 0; i < len; i++, len = doubleLinkList.size > m ? doubleLinkList.size : m) {//找到了节点被删除后链表大小有变化所以len为变
if (currNode.getElement() == null) {//如果到了头节点则重新从第一个节点开始
currNode = doubleLinkList.head.next;
}
currNode.no = i + 1;//报数
if (currNode.no == m)
{//找到报数为m的人
System.out.println(doubleLinkList.getData(currNode.idx) + " ->");
int idx = currNode.idx;
//当前节点向后移
currNode = currNode.next;
doubleLinkList.delete(idx);//让这个人出列
if (doubleLinkList.size == 1)
{//如果只剩下最后一个人就直接出列
System.out.println(doubleLinkList.getData(0) + " ->");
break;
}
resetIdx(doubleLinkList);//重置所有下标
i = -1;//重新从0开始循环,因为for循环表达式要加1这里就赋值为-1了。
}
else
{
//当前节点向后移
currNode = currNode.next;
}
}
} catch (Exception e)
{
System.out.println(e.getMessage());
}
}
/**
* 重置链表每个节点的下标,以便下次可以方便找到节点对象
* @param doubleLinkList
*/
private static void resetIdx(DoubleLinkList doubleLinkList) {
Node currNode = doubleLinkList.head.next;
int i = 0;
while (currNode.getElement() != null) {
currNode.idx = i;
i++;
currNode = currNode.next;
}
}
}
讲了那么多,而且那么复杂,有没有更简单点的实现方式呢?
当然是有的,代码也就20行的样子,看下面精简后的算法,下面这个就是针对这个问题纯算法的实现:
public class PersonTurnout {
public void printNo(int n,int m){
boolean[] persons=new boolean[n];//初始化都为false,找到后值为true
int code=1;
int idx=0;
int size=n;
//剩下的数没有就结束循环
while(size>0){
if(!persons[idx]){//剩下的合法数
if(code==m){
persons[idx]=true;
System.out.println( idx+1);
code=0;//重新计数(下面会加1,所以这里设置为0)
size--;//找到一个后总大小减1
}
code++;//合法的数编号增1
}
idx++;//数组下递增,如果到结尾,就重置idx,重新从开始循环
if(idx==n){
idx=0;
}
}
}
public static void main(String[] args) {
PersonTurnout test=new PersonTurnout();
test.printNo(6, 3);
}
}
有人问为什么这么简单的实现,上面折腾那么多,我认为第一种实现体现了java面向对象的思想,而且使用双向循环链表可以很方便的解决像这样的一类问题。
但就针对今天这个面试问题还是第二种方法解决更简单。纯算法的实现。
转载请注明出处:http://blog.youkuaiyun.com/icefireym/article/details/30239847