zoukankan      html  css  js  c++  java
  • 关于LeetCode上链表题目的一些trick

    最近在刷leetcode上关于链表的一些高频题,在写代码的过程中总结了链表的一些解题技巧和常见题型。

    结点的删除

    指定链表中的某个结点,将其从链表中删除。

    由于在链表中删除某个结点需要找到该结点的前一个位置,然后将前一个结点的next指针直接绕过该结点即可删除。但找到该结点的前一个位置需要指针遍历,其实还有一种更简单的trick,就是将要删除的结点的值设为该结点的后一个的值,然后删除该结点的后一个结点(间接删除,不需要找遍历前一个指针),代码如下:

    class Solution {
        public void deleteNode(ListNode node) {
            node.val = node.next.val;
            node.next = node.next.next;
        }
    }
    

    在表头前增加虚拟结点

    很多场合下,在链表的表头前增加一个虚拟结点(dummy),并让其指向head,能简化很多操作。如在新创建一个链表或对链表进行遍历操作时,如果不增加虚拟结点,就需要处理当前结点是头结点的特殊情况(因为头结点前没有其他结点,导致操作代码不一致)。加了虚拟结点后就可以像操作其他结点一样对待头结点了,最后只需要返回虚拟结点的next就可以了。

    如LeetCode上的这一题:Remove Nth Node From End of List

    class Solution {
        public ListNode removeNthFromEnd(ListNode head, int n) {
            ListNode dummy = new ListNode(0);
            dummy.next = head;
            ListNode p = dummy;
            ListNode q = dummy;
            while(n > 0) {
                q = q.next;
                n--;
            }
            
            while(q.next != null) {
                p = p.next;
                q = q.next;
            }
            p.next = p.next.next;
            
            return dummy.next;
        }
    }
    

    链表转置

    这应该是我碰到的链表中最频繁的问题了,很多其他链表的题目可能也需要借助于链表转置这一功能,所以需要能够熟练地写出代码,这里给出包括迭代和递归两种版本的代码:

    class Solution {
        //迭代实现链表转置
        public ListNode reverseList(ListNode head) {
            ListNode prev = null;
            while(head != null) {
                ListNode next = head.next;
                head.next = prev;
                prev = head;
                head = next;
            }
            return prev;
        }
        
        //递归实现链表转置
        public ListNode reverseList2(ListNode head) {
            if(head == null || head.next == null)
                return head;
            
            ListNode next = head.next;
            //对head.next执行转置
            ListNode newHead = reverseList(next);
            //此时next变成了转置后的尾结点
            next.next = head;
            head.next = null;
            return newHead;
        }
    }
    

    快慢双指针

    有时候需要找到链表的中间位置的结点,这时就需要设置两个指针slow和fast,slow每次往前移动一个,fast移动两个。当fast为空时,slow就指向了链表的中间位置。比如leetcode上的Palindrome Linked List在判断链表是否回文时,需要找到中间位置,然后将其后半部分转置和前半部分相比较,具体实现代码如下:

    class Solution {
        public boolean isPalindrome(ListNode head) {
            if(head == null || head.next == null) {
                return true;
            }
            
            //注意不能直接转置整个链表,需要找到链表的中间,只转置后半部分
            ListNode fast = head, slow = head;
            while(fast != null && fast.next != null) {
                fast = fast.next.next;
                slow = slow.next;
            }
            //这时slow指向了链表的中间结点(注意这种trick要记住)
            slow = reverseList(slow);
            while(head != null && slow != null) {
                if(head.val != slow.val) {
                    return false;
                }
                head = head.next;
                slow = slow.next;
            }
            
            return true;
        }
        
        private ListNode reverseList(ListNode head) {
            ListNode prev = null;
            while(head != null) {
                ListNode next = head.next;
                head.next = prev;
                prev = head;
                head = next;
            }
            return prev;
        }
    }
    

    合并两个有序链表

    这也是碰到的很常见的问题了,合并两个有序链表使其仍然保持有序,一般采用双指针法,这也需要能够熟练地写出无bug的代码来。这里给出迭代和递归两种实现方式:

    class Solution {
        public ListNode mergeTwoLists(ListNode l1, ListNode l2) 	{
            if(l1 == null && l2 == null) {
                return null;
            }
            
            ListNode p = l1;
            ListNode q = l2;
            ListNode dummy = new ListNode(0);
            ListNode pp = dummy;
            while(p != null && q != null) {
                if(p.val < q.val) {
                    dummy.next = new ListNode(p.val);
                    p = p.next;
                }
                else {
                    dummy.next = new ListNode(q.val);
                    q = q.next;
                }
                dummy = dummy.next;
            }
            
            if(p != null) {
                //直接将dummy指过去
                dummy.next = p;
            }
            
             if(q != null) {
                 dummy.next = q;
            }
            
            return pp.next;
        }
        
        //使用递归更简单
        public ListNode mergeTwoLists2(ListNode l1, ListNode l2) 	{
            if(l1==null) return l2;
            if(l2==null) return l1;
            
            if(l1.val<l2.val){
                l1.next = mergeTwoLists(l1.next,l2);
                return l1;
            }
            else{
                l2.next = mergeTwoLists(l1,l2.next);
                return l2;
            }         
        }
    }
    

    目前碰到的问题就这么多了,后面再继续补充吧。

  • 相关阅读:
    第21周六
    第21周五
    第21周四
    第21周三
    C/C++中各种类型int、long、double、char表示范围(最大最小值)
    插入排序
    面向对象的5个基本设计原则
    红黑树
    Cocos2d-x学习笔记(六) 定时器Schedule的简单应用
    SNMP协议具体解释
  • 原文地址:https://www.cnblogs.com/litexy/p/9749594.html
Copyright © 2011-2022 走看看