【算法竞赛-初级】基础数据结构-链表篇

news/2024/6/18 21:20:47 标签: java, 链表, 数据结构, 算法
	这个系列的文章主要是用来记录系统学习算法的心得思路,主要参考书籍是罗勇军老师的算法竞赛。
	现在目前目标是完成该书的初级部分,这篇文章是第一章基本数据结构中的链表部分。后续将持续更新该系列,欢迎大家一键三连!😁	

这里写目录标题

  • 第一章、基础数据结构
    • 1.1、链表(动态、静态、STL链表
      • 1)从尾到头打印链表(LeetCode 剑指Offer 06)
      • 2)链表中倒数第k个节点(LeetCode 剑指Offer 22)
      • 3)反转链表(LeetCode 剑指Offer 24)
      • 4)合并两个有序链表(LeetCode 21)
      • 5)复杂链表的复制(LeetCode 剑指Offer 35)
      • 6)两个链表的第一个公共节点(LeetCode 剑指Offer 52)
      • 7)删除链表中的节点(LeetCode 237)

第一章、基础数据结构

1.1、链表(动态、静态、STL链表

题目:从尾到头打印链表链表中倒数第k个节点、反转链表、合并两个有序链表、复杂链表的复制、两个链表的第一个公共节点、删除链表中的节点

1)从尾到头打印链表(LeetCode 剑指Offer 06)

1、思想:首先,head为null的话,肯定是没有元素,直接返回new int[]{};其次,new一个栈Stack<Integer> stack = new Stack<>(),遍历链表,通过当前链表的大小(有几个元素),并将元素存入栈中;最后,新建一个大小为栈大小的int[]数组,通过栈(先进后出)中的元素逐个弹出,即可完成我们的要求,在这个过程中,时间复杂度为O(n)、空间复杂度也有O(n);

2、改进:因为从尾到头,就是反过来,所以我们可以第一次遍历的时候统计好数组的大小,然后不借助栈,直接让结果数组下标从size-1开始存,这样也能实现链表的反转。

java">class Solution {
    public int[] reversePrint(ListNode head) {
        if(head == null){
            return new int[]{};
        }
        int sum = 0;
        Stack<Integer> stack = new Stack<Integer>();
        ListNode cur = head;
        while(cur != null){
            sum++;
            stack.push(cur.val);
            cur = cur.next;
        }
        int[] res = new int[sum];
        sum = 0;
        while(!stack.empty()){
            res[sum] = stack.pop();
            sum++;
        }
        return res;
    }
}

2)链表中倒数第k个节点(LeetCode 剑指Offer 22)

0、知识点:双指针

1、思想:很巧妙的双指针思想,我们定义两个指针,first和second,让first先走k步,再让first和second同时走(直到first为null),此时second指针所指向的位置就是倒数第k个节点的位置,因为只做一次遍历,所以时间复杂度为O(n),空间复杂度为O(1),只有两个指针和一个步数的变量count。

2、改进:这算是一种挺好的解决办法,在LeetCode上,能做到时间和内存打败:100%和91%。

java">class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        // 很巧妙的双指针思想:让指针1走k步,再让指针2和指针1同时走(直到指针1指向null)
        int count = 0;
        ListNode first = head, second = head;
        while(count < k){
            first = first.next;
            count++;
        }
        while(first != null){
            first = first.next;
            second = second.next;
        }
        return second;

    }
}

3)反转链表(LeetCode 剑指Offer 24)

0、知识点:原地置换(创新点)

1、思想:第一个想到的就是头插法,即通过维护两个指针pre和cur,不断的将cur经过的节点断开,再将其通过头插法插回原数组中,使用虚拟头节点(ListNode zero = new ListNode(-1))可以减少对头节点的处理,其中需要注意的是pre和cur的更新策略,我自己的做法,pre只需要在第一次移动的时候跟着移动,这样pre就能始终保持在我们要断开节点的前一位,while循环里做的步骤(断开节点、取出断开节点、更新cur、头插断开节点、更新pre(只需一次)),时间复杂度为O(n),空间复杂度为O(1),但是这个算法实现的话,时间上是没问题的可以击败100%,空间上的话只有击败30%。

2、改进:原地置换,简单来说就是改变指针的指向,同样的有pre和cur指向,初始:cur指向head,而pre指向null,然后cur指向指向pre,pre暂存cur值,不断的迭代就可以实现指针的转向,这个地方有很详细的图文讲解:原地置换详解。

java">class Solution {
    public ListNode reverseList(ListNode head) {
        // 反转链表的话可以考虑用头插法来解决,头插法的思想就是先插的排在末尾
        // 所以我们只要将链表元素逐个断开,再这个头插进来即可
        // 边界判断
        if(head == null) return null;
        // 创建虚拟头节点
        ListNode zero = new ListNode(-1);
        zero.next = head;
        // 定义双指针
        ListNode pre = zero, cur = head;
        // 判断是否是第一次更新
        boolean flag = false;
        while(cur != null){
            // 断开节点
            pre.next = cur.next;
            // 取出断开节点
            ListNode temp = cur;
            // 更新cur
            cur = pre.next;
            // 头插temp
            temp.next = zero.next;
            zero.next = temp;   
            // 更新pre: 只需要在第一次的时候更新节课
            if(flag == false){
                pre = pre.next;
                flag = true;
            }
            
        }
        // pre.next = null;
        return zero.next;
    }
}

4)合并两个有序链表(LeetCode 21)

1、思想:同时对两个链表进行遍历,直到有一方遍历指针指向null,则结束循环,将剩下的链表的值接到原来的链表中。可以通过利用原来的某个链表,不过需要额外的一个指针,链表这样注意处理的过程中节点之间的依赖性,逻辑上需要多注意!这样的思路可以做到时空复杂度为:O(n+m) 、O(1)的一个组合。

2、改进:递归(高阶解法)

java">class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode zero = new ListNode(-1);
        zero.next = list1;
        ListNode pre = zero;
        ListNode cur1 = list1, cur2 = list2;
        while(cur1 != null && cur2 != null){
            if(cur1.val > cur2.val){
                ListNode temp = cur2;
                cur2 = cur2.next;
                temp.next = pre.next;
                pre.next = temp;
                pre = temp;
            }else{
                pre = cur1;
                cur1 = cur1.next;
            }
        }
        pre.next = cur1 == null ? cur2 : cur1;
        return zero.next;
    }
}

5)复杂链表的复制(LeetCode 剑指Offer 35)

0、知识点:深拷贝

1、思想:这个问题考虑的地方在于如果是简单链表的话我们可以直接创建链表进行复制,但是由于random的指针指向的地方有可能是还没创建的节点,所以这个地方用简单链表的思想是实现不通的。这里我们选择利用哈希表,构建原有节点到新节点的映射,然后再对新节点的next和random进行连接,为了方便理解,这里贴出代码.时空复杂度为:O(n)、O(1)

java">class Solution {

    Map<Node, Node> cacheMap = new HashMap<Node, Node>();

    public Node copyRandomList(Node head) {
        if(head == null){
            return null;
        }
        Node cur = head;
        while(cur != null){
            if(!cacheMap.containsKey(cur)){
                // 相当于说复制了各个节点,并建立“原来节点-新节点”的Map映射
                cacheMap.put(cur, new Node(cur.val));
            }
            cur = cur.next;
        }
        cur = head;
        // 构建链表的next和random指向
        while(cur != null){
          	// 这里需要注意,我们不是直接用cur.next、cur.random
          	// 而是利用我们cacheMap中原有节点和新节点之间的映射,来对cacaheMap新节点(第二个泛型)的next和random进行完善补充!
            cacheMap.get(cur).next = cacheMap.get(cur.next);
            cacheMap.get(cur).random = cacheMap.get(cur.random);
            cur = cur.next;
        }
        return cacheMap.get(head);
    }
}

6)两个链表的第一个公共节点(LeetCode 剑指Offer 52)

1、思路:在第一次做的时候呢,我也没有想到是要怎么来实现,我们来假设看看,

  • 假设链表a和链表b是有公共节点的:
    • 情况1(a和b等长),那么意味着两个指针分别从a和b出发,肯定是会一起到达他们的第一个公共节点;
    • 情况2(a和b不等长),因为公共点存在(两条链表会从公共点后合并成一条链表),所以我们逆向思维,从合并后的末尾出发,是不是两个指针走的步数是一样的,但是!我们现在存在距离差,a和b的起跑线不同,所以我们要去消除这个区别,刚刚的解释就尤为重要,他们距离结尾的距离是一样的,所以我们只要让指针cur1和cur2同时从a和b的head出发,走到null的时候从另外一条链表的头节点出发,这样就可以保证他们走到最后的距离都是a+b,且公共节点对cur1和cur2在a+b的距离上,它所处的距离是等价的,这样我们就可以轻松找到第一个公共点!
    • 时空复杂度:O(n)和O(1)

2、改进:🈚️(注意事项:在写判断逻辑的时候要注意,先判断两者是否相等,再判断是否为null,决定是要cur = cur.next,还是cur1 = headB,最后在再写循环结束条件:cur1和cur2都指向null)

java">class Solution {
    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode cur1 = headA, cur2 = headB;
        // 只要两个指针有一个不为null,按照我们的思路,如果他们走完a+b还没找到的话,那么就是失败的
        // 而且只能一次交换遍历链表
        while(cur1 != null || cur2 != null){
            if(cur1 == cur2){
                return cur1;
            }
            if(cur1 == null){
                cur1 = headB;
            }else{
                cur1 = cur1.next;
            }
            if(cur2 == null){
                cur2 = headA;
            }else{
                cur2 = cur2.next;
            }
            if(cur1 == null && cur2 == null){
                return null;
            }
        }
        // 没找到
        return null;
    }
}

7)删除链表中的节点(LeetCode 237)

1、思路:这题是很有趣的一个问题,我们要删除这个节点,按照正常的思路就是知道这个节点的前一个节点,让前一个节点来进行删除,但是由于题目没有给出链表,所以我们灵机一动,我们只要把后一个节点的值复制给node,这样相当于要删除的节点就是node.next,我们就可以利用node来做删除操作。时空复杂度分别为:O(1)、O(1)

java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        // 知道现在的node位置,我们让下一个位置的val赋值给node,再把node的下一个节点删除
        node.val = node.next.val;
        node.next = node.next.next;

    }
}

http://www.niftyadmin.cn/n/177594.html

相关文章

YOLOV5详解

1. YOLOV5的前处理Anchor的改进 1.1 Anchor生成的改进 首先YOLOV3/V4/V5都是根据训练的数据集来生成anchor, 就是在训练之前用一个独立的程序去计算Anchor, 但是还不够好 因为自动生成的anchor是拿来整个数据集去做的&#xff0c;但是我们知道目标检测训练的时候是分batch训练…

Linux--数据链路层--ARP协议--0319-21

目录 1. 认识以太网 1.1 以太网帧格式 1.2 基于以太网帧简单模拟局域网通信 问题一&#xff1a;如果有多台主机都在发送数据呢&#xff1f; 问题二&#xff1a;发送方知不知道自己的数据被影响了呢&#xff1f; 1.3 MTU 1.3.1 MTU对IP协议的影响 1.3.2 MTU对UDP协议的影…

《计算机网络-自顶向下》02. 应用层

文章目录应用层协议原理应用程序体系结构进程通信客户和服务器进程进程与计算网络之间的接口进程寻址可供应用使用的运输层服务可靠数据传输吞吐量定时安全性因特网提供的运输服务TCP安全的 TCPUDP因特网运输协议所不提供的服务一些因特网应用所使用的运输层协议网络应用 —— …

用于电力电子器件的栅极驱动器

栅极驱动器是一种功率放大器&#xff0c;它接受来自控制器IC的低功耗输入&#xff0c;并为功率器件产生适当的高电流栅极驱动。随着对电力电子器件的要求不断提高&#xff0c;栅极驱动器电路的设计和性能变得越来越重要。 功率半导体器件是现代电力电子系统的核心。这些系统利…

【C#】给容器里控件批量初始化

系列文章 【C#】单号生成器&#xff08;定义编号规则、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成&#xff08;构建本周开始、结束日期&#xff09; 本文链接&#xff1a;https…

CentOS 7 安装 mysql 8.0

官方文档 2.5.1 Installing MySQL on Linux Using the MySQL Yum Repository 官网查找最新版本 MySQL Product Archives 复制这个链接地址&#xff0c;并下载 wget https://repo.mysql.com//mysql80-community-release-el7-7.noarch.rpm 接下来&#xff0c;按照官方文档步…

天梯赛练习集--L1-001到L1-010--python - java

python L1-001 Hello World print("Hello World!")L1-002 打印沙漏 a input().split() n int(a[0]) c 1 while(2*c**2-1<n):c1 c-1 b 2*c - 1 for i in range(c):print(" "*ia[1]*(b-2*i)) for i in range(1,c):print(" "*(c-i-1)a[1]…

动态规划专题(明天继续)

动态规划求最大值&#xff1a; 题目描述 小蓝在一个 nn 行 mm 列的方格图中玩一个游戏。 开始时&#xff0c;小蓝站在方格图的左上角&#xff0c;即第 11 行第 11 列。 小蓝可以在方格图上走动&#xff0c;走动时&#xff0c;如果当前在第 rr 行第 cc 列&#xff0c;他不能…