删数_牛客网【两种方法实现】

链接:https://www.nowcoder.com/questionTerminal/f9533a71aada4f35867008be22be5b6e
来源:牛客网

有一个数组a[N]顺序存放0~N-1,要求每隔两个数删掉一个数,到末尾时循环至开头继续进行,求最后一个被删掉的数的原始下标位置。以8个数(N=7)为例:{0,1,2,3,4,5,6,7},0->1->2(删除)->3->4->5(删除)->6->7->0(删除),如此循环直到最后一个数被删除。

输入描述:

每组数据为一行一个整数n(小于等于1000),为数组成员数,如果大于1000,则对a[999]进行计算。

输出描述:

一行输出最后一个被删掉的数的原始下标位置。

示例1

输入

8

输出

6

第一种思路【队列实现】:

因为是每隔两个数删除一个数,所以我们可以先将n个数(0到n-1)依次入队列;然后每次依次取两个数出队列,然后重新入队列,再删除队头元素,一直循环,直到队列只剩一个数,取得此时的队头元素 。

//队列实现
    private static int deleteNum2(int n) {
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < n; i++) {//1. 依次将n个数入队列
            queue.add(i);
        }
        while (queue.size() != 1) {
            int count = 2;
            while (count-- != 0) {
                int tmp = queue.peek();//此步取得队首元素是为了后面让它重新入队列
                queue.poll();//队头元素出队列
                queue.add(tmp);//让刚取得的队首元素入队列

            }
            queue.poll();
        }
        return queue.peek();//取得此时的队头元素
    }

第二种思路【约瑟夫环实现】:

我们可以让n个数构成一个有n个节点的循环链表,然后从第一个节点开始,每隔一个节点删除一个节点,直到循环链表中只剩一个节点,返回此节点的val值。 

需要注意的是,为了删除某一个节点,我们需要每走一步都保存一下此节点的前一个节点。

如何判断循环链表只剩一个节点了:当一个节点的next指向自己时表明只剩一个节点了。

//约瑟夫环实现
    private static int deleteNum(int n) {
        ListNode root = new ListNode(0);
        ListNode tail = root;//连接每个节点所用
        for (int i = 1; i < n; i++) {//构成一个单链表
            tail.next = new ListNode(i);
            tail = tail.next;
        }
        tail.next = root;//让尾节点的next指向第一个节点,以构成循环链表
        ListNode node = root;
        while (node.next != node) {//如果node.next==node,表明此时循环链表只剩一个节点
            ListNode pre = null;//用来记录每个节点的前一个节点,方便删除这个节点
            int count = 2;
            while (count-- != 0) {
                pre = node;//记录node节点的前一个节点
                node = node.next;
            }
            pre.next = node.next;//删除node节点,即让node节点的前一个节点的next指向node节点的next
            node = pre.next;//从node节点的下一个节点继续
        }
        return node.val;
    }

最后附上两种方法的完整代码:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class DeleteNum {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            if (n > 1000) {
                n = 999;
            }
            System.out.println(deleteNum1(n));
        }
    }

    //约瑟夫环实现
    private static int deleteNum1(int n) {
        ListNode root = new ListNode(0);
        ListNode tail = root;//连接每个节点所用
        for (int i = 1; i < n; i++) {//构成一个单链表
            tail.next = new ListNode(i);
            tail = tail.next;
        }
        tail.next = root;//让尾节点的next指向第一个节点,以构成循环链表
        ListNode node = root;
        while (node.next != node) {//如果node.next==node,表明此时循环链表只剩一个节点
            ListNode pre = null;//用来记录每个节点的前一个节点,方便删除这个节点
            int count = 2;
            while (count-- != 0) {
                pre = node;//记录node节点的前一个节点
                node = node.next;
            }
            pre.next = node.next;//删除node节点,即让node节点的前一个节点的next指向node节点的next
            node = pre.next;//从node节点的下一个节点继续
        }
        return node.val;
    }

    //队列实现
    private static int deleteNum2(int n) {
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < n; i++) {//1. 依次将n个数入队列
            queue.add(i);
        }
        while (queue.size() != 1) {
            int count = 2;
            while (count-- != 0) {
                int tmp = queue.peek();//此步取得队首元素是为了后面让它重新入队列
                queue.poll();//队头元素出队列
                queue.add(tmp);//让刚取得的队首元素入队列

            }
            queue.poll();
        }
        return queue.peek();//取得此时的队头元素
    }
}

class ListNode {
    int val;
    ListNode next;

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

了解完按固定间隔删数之后,假如现在有另外一种需求:

有一个数组a[N]顺序存放0~N-1,要求第一次间隔一个数删掉一个数,第二次间隔两个数删掉一个数,第三次间隔三个数删掉一个数,...... ,到末尾时循环至开头继续进行,求最后一个被删掉的数的原始下标位置。以10个数为例:{0,1,2,3,4,5,6,7,8,9};0->1(删除)->2->3->4(删除)->5->6->7->8(删除)->9->0->1->2->3(删除)。如此循环,直到最后一个数被删除。

假如输入为10,则输出为9.

有了上边按固定间隔删数数的思路之后,在解答这道题的时候,我们只需要每次把删除的间隔改改就行啦。

完整源代码如下:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Delete {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        while (scanner.hasNext()){
            int n=scanner.nextInt();
            System.out.println(delete(n));
        }
        scanner.close();
    }

    private static int delete1(int n) {
        ListNode root = new ListNode(0);
        ListNode tail = root;//连接每个节点所用
        for (int i = 1; i < n; i++) {//构成一个单链表
            tail.next = new ListNode(i);
            tail = tail.next;
        }
        tail.next = root;//让尾节点的next指向第一个节点,以构成循环链表
        ListNode node = root;
        int count=0;
        while (node.next!=node){//如果node.next==node,表明此时循环链表只剩一个节点
            count+=1;//每次删除数之前改变间隔
            int c=count;//因为每次删数时间隔都会变,所以我们需要用一个数来记录它,以此让节点往后走间隔步
            ListNode pre=null;
            while (c--!=0){
                pre=node;//记录node节点的前一个节点
                node=node.next;
            }
            pre.next = node.next;//删除node节点,即让node节点的前一个节点的next指向node节点的next
            node = pre.next;//从node节点的下一个节点继续
        }
        return node.val;
    }

    private static int delete(int n) {
        Queue<Integer> queue=new LinkedList<>();
        for(int i=0;i<n;i++){
            queue.add(i);
        }
        int count=0;
        while (queue.size()!=1){
            count+=1;//每次删除数之前改变间隔
            int c=count;//因为每次删数时间隔都会变,所以我们需要用一个数来记录它,以此让队列出入元素
            while (c--!=0){
                int tmp = queue.peek();//此步取得队首元素是为了后面让它重新入队列
                queue.poll();//队头元素出队列
                queue.add(tmp);//让刚取得的队首元素入队列
            }
            queue.poll();
        }
        return queue.peek();
    }
}

class ListNode {
    int val;
    ListNode next;

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值