LeetCode刷题日记之设计循环双端队列-链表实现

前面已经写过一篇使用数组实现的循环双端队列,当时说后续会写一下链表实现,现在补上,这个其实很早就写了,但是忘了发。。。

双向链表

大概介绍下双向链表的设计。

首先看下单向链表,我们只需要知道头结点就可以了,所以我们直接用就可以。

再来双向链表,它方向变多了,因此一个指针不够了,我们需要有两个指针,一个指向前面节点pre,一个指向后面节点next。这样一个双向链表节点就有了。

我们首先想一下为啥要有双向链表?

因为单链表有局限性。**我们常说链表插入的时间复杂度是O(1),这里的链表是指的双向链表,**如果是单向链表,在它前面插入元素的时间复杂度是O(n),但链表只能找到它后面的元素,因此你需要遍历整个链表找到它前面的那个元素。但是如果你这个节点是双向的,我就能在O(1)的时间找到前面的节点,那么时间复杂度就变成了O(1),这也是为啥工业上一般用的双向链表的原因。

对于双向链表最多的操作就是头尾节点插入和删除,所以自然想到需要定义两个双向链表节点head和tail,除了head和tail节点在加上链表初始因子和实际元素计数两个属性,这时候一个简单的双向链表就出来了,工业上为了使用方便一般会单独封装一个类这就是JAVA里的LinkedList。当然你也可以自己写两个变量,看你自己。我这里是自己写的两个变量头结点head,尾节点tail。元素添加就是放到head后面或者tail前面。

我一开就有一个疑问,为啥要往head和tail里面加元素而不是往外面加元素?

因为API的设计,双向链表都是往头和尾增删节点,往里面加元素你能很快的定义到头结点和尾结点进行操作,如果往外面加元素,那么你需要遍历链表找到头节点和尾结点在进行操作,麻烦很多。

思考

这道题的链表实现就是用双向链表,每个节点里面有前后两个指针。链表写法如果不熟的情况下会觉得很难,如果弄熟了其实会觉得比数组简单。因为不需要做那么多越界判断,它的判空和判满会比数组简单很多很多很多,但是相应的它也有自己的难点,就是节点的添加和删除,这块建议在写的时候画一下图,模拟一下,就会清晰很多。

首先是构建头尾两个节点,然后用一个size记录最大元素个数,一个count记录有多少元素。当在前面插入就是放在head后面,在后面插入就是放在tail前面。

对于插入节点,需要调整四个指针,前面的后指针,插入节点的前后指针,后面节点的前指针。插入时注意head.next=node必须放到node.next=head.next后面,不然head那条链就断了。整个后面都找不到了。

删除节点就简单很多,比如说删除头结点,只需要将头结点的下下个节点前指针指向自己,然后将头节点后指针指向下下节点,tail.pre.pre.next = tail; tail.pre = tail.pre.pre;只需要改动两个地方,删除节点的头尾指针其实可以不动,因为你后面也用不到并找不到它了。

代码
/**
 * 链表实现
 */
class MyCircularDeque {
        private DoubleListNode head;
        private DoubleListNode tail;
        private int count;
        private int size;
    public MyCircularDeque(int k) {
        this.size = k;
        head = new DoubleListNode(0);
        tail = new DoubleListNode(0);
        head.next = tail;
        tail.pre = head;
    }
    public boolean insertFront(int value) {
        if (isFull()) {
            return false;
        }
        DoubleListNode node = new DoubleListNode(value);
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
        node.pre = head;
        count++;
        return true;
    }
    public boolean insertLast(int value) {
        if (isFull()) {
            return false;
        }
        DoubleListNode node = new DoubleListNode(value);
        node.pre = tail.pre;
        tail.pre.next = node;
        tail.pre = node;
        node.next = tail;
        count++;
        return true;
    }
    public boolean deleteFront() {
        if (isEmpty()) {
            return false;
        }
        head.next.next.pre = head;
        head.next = head.next.next;
        count--;
        return true;
    }
    public boolean deleteLast() {
        if (isEmpty()) {
            return false;
        }
        tail.pre.pre.next = tail;
        tail.pre = tail.pre.pre;
        count--;
        return true;
    }
    public int getFront() {
        if (isEmpty()) {
            return -1;
        }
        return head.next.val;
    }
    
    public int getRear() {
        if (isEmpty()) {
            return -1;
        }
        return tail.pre.val;
    }
    public boolean isEmpty() {
        return count == 0;
    }
    public boolean isFull() {
        return count == size;
    }
    class DoubleListNode{
        private DoubleListNode pre;
        private DoubleListNode next;
        private int val;
        public DoubleListNode(int val) {
            this.val = val;
        }
    }
}

这里可以看到链表实现的判空就是count0,判满就是countsize,对比数组判空和判满是不是简单了特别多?第一次写的时候都给我震惊了。

最后强烈建议双向链表里的节点删除和添加建议多画图,画了图会清晰很多,然后看着写就行。

总结

链表这个实现没懂之前觉得很麻烦,懂了就会觉得很简单,这块没啥技巧,多练多思考就行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值