链表算法及LeetCode24-25解析

链表简介

链表分为单链表、双链表:
单链表:链表中的每个元素实际上是一个单独的对象,而所有的对象通过每个元素中的引用字段(next)字段链接在一起;
单链表
双链表:与单链表不同的是,双链表中每个元素通过两个引用字段链接在一起;
双链表
链表的出现,在某种程度上是为了避免数组的一大缺陷:即分配数组的时候需要分配一段连续的内存空间;但鱼和熊掌不可兼得,链表也牺牲了数组的一些优点:链表不能通过下标快速查询元素。因此在考虑是否需要用链表解决算法问题时,务必想清楚该问题是否需要频繁的查询和遍历

链表优缺点

优点

  • 灵活地分配内存空间;
  • 添加、删除元素快,时间复杂度O(1);(前提:单链表中该元素的前一个元素已知;双链表中该元素的前一个元素或后一个元素已知【后续技巧中创建临时表头的意义所在】)

缺点

  • 不能像数组那样通过下标迅速访问元素,每次查询需要从链表的头开始向后遍历,直到查询到对应元素;时间复杂度O(n);

是否采用链表权衡:

  • 如果待解问题中需要频繁快速查询,则链表并不适合(可考虑数组);
  • 待解问题中,数据元素个数不确定,经常需要进行数据的添加、删除,则链表更合适;

解题技巧

  1. 利用快慢指针(有时候需要用到3个指针)
    • 链表的反转;
    • 寻找倒数第K个元素;
    • 寻找链表中中间位置的元素;
    • 判断链表是否有环 ;
  2. 构建一个临时链表头(一般应用在要返回一个新的链表头的题目中)
    • 给定两个有序链表,进行整合排序;
    • 将链表的奇数偶数分开后,返回前半部分为奇数、后半部分为偶数的新的链表;

训练方法

在纸上或白板上画出链表元素之间的指引关系,手动完成增删改等操作,这样有助于理解抽象过程,在面试过程中清楚地表达自己的解题思路;(亲测高效

经典算法题解

LeetCode24

LeetCode24

方法一:递归
代码:
	/**
     * 递归
     * @param head
     * @return
     */
    public static ListNode swapPairs(ListNode head){
        if(null == head || null == head.next) return head;
        ListNode next = head.next;
        head.next = swapPairs(head.next.next);
        next.next = head;
        return next;
    }
 
方法二:非递归
思路:
  1. 创建临时链表头newHead,使其next指向head;
  2. 创建pre指针,初始指向newHead;
  3. 交换pre后地两个指针pre.next(node1)和pre.next.next(node2);
  4. pre指针指向node1;
  5. 重复3,4;
  6. 边界条件:pre后面不足两个节点;
代码:
	/**
     * 非递归
     * @param head
     * @return
     */
    public static ListNode swapPairs2(ListNode head){
        ListNode newHead = new ListNode(-1);
        newHead.next = head;
        ListNode pre = newHead;
        while(null != pre.next && null != pre.next.next){
            ListNode n1 = pre.next;
            ListNode n2 = pre.next.next;
            ListNode next = n2.next;
            n1.next = next;
            n2.next = n1;
            pre.next = n2;

            pre = n1;
        }
        return newHead.next;
    }
 
LeetCode25

LeetCode25

思路
  1. 创建临时链表表头newHead,newHead.next=head;
  2. 创建左右指针l、r,初始指向newHead;
  3. 创建计数器cnt = 0;
  4. r一步步向右移动遍历链表,同时计cnt++计数。
    遍历过程中若cnt==k:调用Helper(l,r);【目的:反转l.next至r之间的链表,并返回该子链表反转后的末尾节点】
    l、r指针指向该末尾节点;
    计数器清零;
  5. 返回newHead.next;(因为反转后head已经不是表头,这也是创建临时表头的目的之一)
    【Helper(l,r)方法】
    目的:反转l.next至r之间的链表,并返回该子链表反转后的末尾节点。
    例如对于链表-1->1->2->3,左指针指向-1,右指针指向3,Helper函数执行后,1至3链表被反转,并返回-1->3->2->1的末尾节点1.
    递归思路:
    反转[l.next,r]子链表,即将l.next与( [l.next.next,r]反转后的子链表)交换。
    临界条件:l.next== r,即待反转节点只有一个,直接返回r;
    细节解惑为什么Helper方法参数中,左指针指向待反转链表表头的前一个节点,而非表头?
    反例解析:假设对于链表-1->1->2->3,我们需要反转子链表1->2。
    此时如果我们传递的左右指针:左指针指向1,右指针指向2。
    反转操作:ListNode rNext = 2.next; //3
    2.next = 1;
    1.next = rNext; //3
    此时就出现问题,1->2反转后的子链表2->1,无法与子链表表头的的前一个节点-1链接,
    即-1.next = 2无法完成,因为参数指针没有指向子链表表头前一个节点的指针,导致断链;
代码
/**
     * 将链表head每K个一组进行反转
     * @param head
     * @param k
     * @return
     */
public ListNode reverseKGroup(ListNode head, int k) {
        ListNode newHead = new ListNode(-1);
        newHead.next = head;
        ListNode l = newHead,r = newHead;
        int cnt = 0;
        while(r != null){
            if(cnt == k){
                r = reverHelper(l,r);
                l = r;
                cnt = 0;
            }
            cnt++;
            r = r.next;
        }
        return newHead.next;
    }

    /**
     * 将[l.next,r]反转,并返回反转后地链表rail
     * @param l
     * @param r
     * @return
     */
    public  ListNode reverHelper(ListNode l,ListNode r){
        if(l.next == r){
            return r;
        }
        r = reverHelper(l.next,r);
        //ListNode temp1 = l.next;
        ListNode temp2 = r.next;
        r.next = l.next;
        l.next = l.next.next;
        r.next.next = temp2;
        return r.next;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值