当前位置: 首页 > article >正文

算法记录——链表

2.链表

2.1判断是否是回文链表

1.方法一:利用栈反转链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        Stack<ListNode> listNodes = new Stack<>();
        ListNode p = head;
        //利用栈反转链表,判断是否是回文链表
        while (p != null) {//将链表中所有元素入栈
            listNodes.push(p);
            p = p.next;
        }
        while (!listNodes.empty()) {
            if (listNodes.pop().val == head.val) {//
                head = head.next;
            } else {
                return false;
            }
        }
        return true;
    }
}

2.方法2:利用快慢指针

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
    //代表快指针,一次走两步
        ListNode fast = head;
        //代表慢指针,一次走一步
        ListNode slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //退出循环时,如果链表节点是奇数个,快指针在尾节点,慢指针在中点。如果是偶数个,快指针还是在尾节点,慢指针在中点前一个。
        //把右半部分链表反转
        slow = reverseList(slow.next);
        while (slow != null) {
            if (head.val != slow.val) return false;//值不相同,直接返回false
            head = head.next;
            slow = slow.next;
        }
        return true;
    }
    //反转链表
    public static ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

2.2 模板题:反转链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        //cur:用于遍历链表元素的指针,代表当前遍历到的节点,初始化当然为head了
        ListNode cur = head;
        //pre:代表当前cur节点,反转后应该指向的节点。因为cur初始在head,反转以后就是尾节点了指向null,所以pre初始化为null
        ListNode pre = null;
        while(cur != null){//当元素还没遍历完的时候
            //在cur指向pre前,用于保存cur.next,防止链表找不到了。
            ListNode temp = cur.next;
            //让当前节点cur,指向pre
            cur.next = pre;
            //让pre变为反转链表的最前面一个节点
            pre = cur;
            //让cur移动到原链表的头节点
            cur = temp;
        }
        // 注意:pre的含义还是反转链表的头节点!
        return pre;
    }
}

复杂度分析:
时间复杂度 O(N)O(N)O(N) : 遍历链表使用线性大小时间。
空间复杂度 O(1)O(1)O(1) : 变量 pre 和 cur 使用常数大小额外空间。

已经是最优的解法了,还有一种递归方法就不赘述了。

2.3 分割链表(将链表分为小于某个值,等于某个值,大于某个值)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
if (head == null || head.next == null) return head;

        //代表小于目标值区域的头和尾
        ListNode h1 = null;
        ListNode t1 = null;
        //代表大于等于目标值的头和尾
        ListNode h2 = null;
        ListNode t2 = null;
        //用于保存head的下一个节点
        //注意:这里最后拼接好了以后,小于区域的头就是整个链表的新的头节点,因此,head可以作为遍历链表的指针。
        ListNode next = head.next;
        while (head != null) {//遍历
            next = head.next;
            head.next = null;
            if (head.val < x) {//如果当前节点的val小于目标值
                if (h1 == null) {//如果当前节点是小于区域的第一个节点
                    h1 = head;
                    t1 = head;
                } else {
                    t1.next = head;
                    t1 = head;
                }
            } else {
                if (h2 == null) {//如果当前节点是大于区域的第一个节点
                    h2 = head;
                    t2 = head;
                } else {//其他情况就把该节点尾插法插入链表中
                    t2.next = head;
                    t2 = head;
                }
            }
            head = next;
        }

        //进行小于区域链表和大于等于区域链表的拼接
        if (h2 == null) {//如果没有大于等于区域
            return h1;
        }
        if (h1 == null) {//如果没有小于区域
            return h2;
        }
        //如果两种区域都有,则让小于区域的尾指针指向大于等于区域的头指针
        t1.next = h2;

        return h1;
    }
}

2.4 随机链表的赋值

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        //创建一个map,key为老链表的节点。val为新链表的节点
        HashMap<Node,Node> map = new HashMap<Node,Node>();
        Node cur = head;
        //遍历链表,设置map的key和value
        while(cur != null){
            map.put(cur,new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        //再次遍历老链表,给新链表设置每一个节点的next和random
        while(cur != null){
            //cur 老链表节点
            //map.get(cur) cur对应的新链表
            map.get(cur).next = map.get(cur.next);//设置新链表的next
            map.get(cur).random = map.get(cur.random);//设置新链表的random
            cur = cur.next;
        }
        return map.get(head);
    }
}

2.5环形链表的判断

方法一:利用HashSet集合。

思路:遍历当前链表,每次遍历判断当前节点是否已经存在于set集合中。如果不存在,则把当前节点放入集合。如果已经存在,说明当前节点就是第一个入环节点。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        //创建一个set,用于存放链表中已经遍历了的节点
        HashSet<ListNode> set = new HashSet<>();
        while(head != null){
            //如果当前节点已经存在于set,说明存在环形结构
            if(set.contains(head)) return true;
            set.add(head);
            head = head.next;
        }
        return false;
    }
}

方法二:快慢指针

开始时,快慢指针都在头节点的位置。快指针一次走两步,慢指针一次走一步。

如果没有环结构,快指针一定先走到尾节点。

如果有环结构,快慢指针会在换里相遇。而相遇所要走的卷数不会大于两圈。

相遇以后,快指针/慢指针到头节点的位置。两个指针开始一次走一步。最终两个指针会在第一次入换节点相遇!(原理就不证明了)

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null || head.next.next == null) return null;
        //定义慢指针,一次走一步
        ListNode n1 = head.next;
        //定义快指针,一次走两步
        ListNode n2 = head.next.next;
        while(n1 != n2){//当n1 n2不相遇时循环,所以我开始时没有把两个指针都设置在头节点的位置
            if(n2.next == null || n2.next.next == null){//说明没有环结构,直接返回空
                return null;
            }
            n1 = n1.next;//慢指针一次走一步
            n2 = n2.next.next;//慢指针一次走两步
        }
        //快指针移到头节点,开始一次走一步
        n2 = head;
        while(n1 != n2){//当两个指针相遇时,就走到了第一个入环节点
            n1 = n1.next;
            n2 = n2.next;
        }
        return n1;
    }
}

2.6 链表相交

思路:两个单链表,如何判断有没有相交点呢?

1.先遍历两个链表,到尾节点时停止。如果这时候,两个链表的尾节点都不想等。说明二者不相交。

2.如果二者尾节点是同一个,则计算二者链表长度的差值。让长的链表先走差值个距离。然后,短的链表从头开始走,二者一定会在相交点相遇!

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //定义两个指针用于遍历两条链表
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int n = 0;//用于记录两条链表的差值
        while(cur1.next != null){
            cur1 = cur1.next;
            n++;
        }
        while(cur2.next != null){
            cur2 = cur2.next;
            n--;
        }
        if(cur1 != cur2){//尾节点都不想等,说明二者不相交
            return null;
        }
        //这样遍历完两条链表,n就是两条链表的长度差
        cur1 = n > 0 ? headA : headB;//让cur1指向两条链表中长的那一条
        cur2 = cur1 == headA ? headB : headA;//让cur2指向两条链表中短的那一条
        n = Math.abs(n);//n取绝对值
        while(n != 0){//让长的那条链表先移动两条链表差值的距离,再一起走,就会在相交部分汇合!
            cur1 = cur1.next;
            n--;
        }
        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }
}

2.7.两数相加

思路:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
   public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
            ListNode res = new ListNode();
            ListNode cur = res;
            int carry = 0;
            //当l1、l2中有一个不是空节点,或者还有进位,就继续循环
            while (l1 != null || l2 != null || carry != 0) {
                if (l1 != null) carry += l1.val;
                if (l2 != null) carry += l2.val;
                cur.next = new ListNode(carry % 10);//carry%10 就是该点的val
                cur = cur.next;
                carry = carry / 10;// carry/10 就是下一个点的进位
                if (l1 != null) l1 = l1.next;//l1 没有遍历完
                if (l2 != null) l2 = l2.next;
            }
            return res.next;
        }
}

2.8.合并两个有序链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
            ListNode res = new ListNode();
            ListNode cur = res;

            while (list1 != null && list2 != null) {
                if (list1.val <= list2.val) {//如果l1链表的值更小
                    cur = cur.next = list1;
                    list1 = list1.next;
                } else {
                    cur = cur.next = list2;
                    list2 = list2.next;
                }
            }
            while (list1 != null) {//如果1还没遍历完
                cur = cur.next = list1;
                list1 = list1.next;
            }
            while (list2 != null) {//如果2还没遍历完
                cur = cur.next = list2;
                list2 = list2.next;
            }
            return res.next;
        }
}

2.9 反转链表2

题解参考:leetcode

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(0, head);
        ListNode g = dummy;
        ListNode p = dummy;
        //g指向的下一个节点就是要开始反转的节点
        for (int i = 0; i < left - 1; i++) {
            g = g.next;
            p = p.next;
        }
        //p指向第left个节点
        p = p.next;
        for (int i = 0; i < right - left; i++) {
            ListNode temp = p.next;
            p.next = p.next.next;
            temp.next = g.next;
            g.next = temp;
        }

        return dummy.next;
    }
}

2.10.K个一组反转链表

        本题思路和上一题差不多。举一反三,还是主要用g、p两个指针反转链表。

        每组链表反转之前,g的next指向的都是待反转链表的第一个节点,p指向的就是待反转链表的第一个节点。
        要注意的就是每次反转完链表p指针指向的就是反转后链表的最后一个元素,同时它的next也是下一组待反转链表的第一个元素,所以每次每组反转完以后,都要把p赋值给g。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
         ListNode dummy = new ListNode(0, head);
        ListNode g = head;
        //计算一共有多少个节点,用来算要反转几组链表
        int count = 0;
        while (g != null) {
            g = g.next;
            count++;
        }
        g = dummy;
        ListNode p = g.next;
        //遍历
        for (int i = 0; i < count / k; i++) {
            p = g.next;
            //反转的每组链表
            for (int j = 1; j < k; j++) {
                ListNode temp = p.next;
                p.next = p.next.next;
                temp.next = g.next;
                g.next = temp;
            }
            //每组链表反转完,让cur的next指向下一组待反转链表第一个
            g = p;
        }

        return dummy.next;
    }
}


http://www.kler.cn/a/319388.html

相关文章:

  • 阿里云和七牛云对象存储区别和实现
  • Python提取PDF和DOCX中的文本、图片和表格
  • 阿里巴巴通义灵码推出Lingma SWE-GPT:开源模型的性能新标杆
  • Web大学生网页作业成品——婚礼婚纱网页设计与实现(HTML+CSS)(6个页面)
  • 大数据 ETL + Flume 数据清洗 — 详细教程及实例(附常见问题及解决方案)
  • 红帽认证和华为认证哪个好?看完这4点你就明白了
  • 【Linux基础IO】深入解析Linux基础IO缓冲区机制:提升文件操作效率的关键
  • MySQL—存储过程详解
  • 望繁信科技受邀出席ACS2023,为汽车行业数智化护航添翼
  • vue3自动暴露element-plus组件的ref
  • C# 找到给定点集的简单闭合路径(Find Simple Closed Path for a given set of points)
  • 203. 移除链表元素
  • David律所代理Jose Martin幽默水果版权首发维权,尚未TRO
  • MySQL安装教程
  • 240922-MacOS终端访问硬盘
  • C++_22_异常
  • C++:模版初阶
  • 手把手搞定VMware 的CentOS硬盘扩容
  • Unity 设计模式 之 行为型模式 -【中介者模式】【迭代器模式】【解释器模式】
  • 使用sqoop报错
  • Qt网络通信之TCP
  • Agile Modbus STM32裸机移植 从机使用
  • Django基础-创建新项目,各文件作用
  • npm install安装缓慢及npm更换源
  • 研究生三年概括
  • 【Linux实践】实验五:用户和组群账户管理