首页 > 编程语言 >算法记录——链表

算法记录——链表

时间:2024-09-26 17:21:08浏览次数:10  
标签:head ListNode val 记录 next 链表 算法 null

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;
    }
}

标签:head,ListNode,val,记录,next,链表,算法,null
From: https://blog.csdn.net/qq_64064246/article/details/142490910

相关文章

  • 中点算法和Bresenham算法画线
    使用EasyXc++库中点算法直线绘制//中点算法划线:横向直线绘制voidDDAline(intx1,inty1,intx2,inty2,intcolor){intx;floatdx,dy,y,k;dx=x2-x1,dy=y2-y1;if(dx==0){for(y=min(y1,y2);y<max(y1,y2);y++){......
  • 代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素、977.有序数组的平方。
    704.二分查找总结:防止int溢出:classSolution{public:intsearch(vector<int>&nums,inttarget){intleft=0;intright=nums.size()-1;while(left<=right){intmiddle=(left+right)/2;//intmid=(right-left)/......
  • 算法与数据结构——快速排序
    快速排序快速排序(quicksort)是一种基于分治策略的排序算法,运行高效,应用广泛。快速排序的核心操作是“哨兵划分”,其目标是::选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体流程如下:选取数组最左端元素作为基准数,初始化......
  • JAVA 数据结构与算法 队列的介绍与应用
    一、队列队列是一个有序列表,可以用数组或者链表来实现遵循先入先出的原则。当我们将数据存入队列时称为”addQueue”,addQueue的处理需要有两个步骤:思路分析:将尾指针往后移:rear+1,当front==rear【空】若尾指针rear小于队列的最大下标maxSize-1,则将数据存入rear所......
  • 【数据结构.总集篇】第一章 链表
    文章目录1.链表1.1线性表1.2顺序表1.3单链表链表简介单链表的介绍线性表的链式存储头节点概念为何引入头结点单链表的基本操作创建一个单链表初始化头插法尾插法删除操作打印总代码1.4单链表循环1.5双链表双链表的定义双链表上的操作初始化插入操作头插法尾插法......
  • 字符串从入门到退竞(2)——KMP 算法
    约定在文字描述中字符串下标从\(1\)开始,代码中从\(0\)开始。前缀函数对于长\(n\)的字符串\(S\),其前缀函数是一个长\(n\)的数组(数列)\(\pi\),定义如下:若\(S[1..i]\)有相等的真前缀和真后缀(称之为border),\(\pi[i]\)为其长度的最大值;若不存在,\(\pi[i]=0\)。字符串的......
  • BladeX开发入门(记录)
    BladeX物联网平台是一款高度集成的物联网解决方案,涵盖设备管理、数据采集、实时监控、数据分析以及开放API服务等核心功能。平台经过精心设计与开发,提供了全面的品类、产品和设备支持。设备注册成功后,能够轻松桥接至其他物联网云平台,实现设备的无缝集成。同时提供服务端订阅功......
  • RabbitMq 入门应用 提升性能 : 算法多阶段并行 (Python)
    大问题:我们有一个算法,它可以被分为多个阶段进行(顺序不可颠倒),每个阶段的性能和资源要求不同(且不均衡程度比较高);假设我们现在可以堆资源(较多的CPU和内存),如何将算法各个步骤拆分并进行性能均衡和实现,使得算法性能最大化以满足生产要求?多进程:由于算法有严格的顺序要求,如果是......
  • 老鼠检测、厨房老鼠检测、老鼠识别算法
    老鼠检测算法主要用于家庭、餐饮行业、仓储物流、医疗设施等领域的鼠患监控,通过图像识别技术来检测和识别视频或图像中的老鼠。这种技术可以帮助管理者实时监控老鼠活动,及时采取措施,防止鼠患带来的健康和经济损失。一、技术实现老鼠检测算法通常依赖于计算机视觉和深度学习技术,通......
  • 本科学历能找到人工智能算法岗位的工作吗?好就业吗?
    随着科技的发展,人工智能技术在各行各业的应用日益广泛,催生了大量专注于人工智能的企业,这些企业在招聘网站上发布了众多相关岗位,并且这些岗位的薪资普遍高于其他行业岗位,因此越来越多求职者渴望进入这一行业。对于同样有这一愿景的本科生来说,他们常常会问:“我本科学历能找到人工智能......