常见链表相关算法-java语言实现

本文将介绍:

  1. 单链表反转

  2. 求中间节点

  3. 删除倒数第K个节点
  4. 合并两个有序链表
  5. 检测环,并找到入口点
  6. 两个单链表相交的起始节点
  7. 奇偶链表
  8. 回文链表

Node定义:

public static class Node {
    private int data;
    private Node next;

    public Node(int data, Node next) {
      this.data = data;
      this.next = next;
    }

    public int getData() {
      return data;
    }
  }

1、单链表反转

原文(C++语言实现):https://blog.csdn.net/feliciafay/article/details/6841115

方法:使用3个指针遍历单链表,逐个链接点进行反转。

使用p和q两个指针配合工作,使得两个节点间的指向反向,同时用r记录剩下的链表。

p = head;

q = head.next;

head.next = null;


现在进入循环体,这是第一次循环。

r = q.next;

q.next = p;

p = q;

q =r;


第二次循环。

r = q.next

q.next = p;    


p = q;

q = r


第三次循环。。。。。

具体代码如下

static Node reverseCsdn(Node head){
    //少于两个节点没有必要反转
    if(head == null || head.next == null){
      return head;
    }
    Node p,q,r;
    p = head;
    q = head.next;
    head.next = null; //旧的头指针就是新的尾指针,next需要指向null
    while (q != null){
      r = q.next;//先保留下一个step要处理的指针
      q.next = p;//然后pq交替工作进行反向
      p = q;
      q = r;
    }
    head = p;//最后q必然指向null,所以返回了p作为新的头指针
    return head;
  }

2、求中间节点


使用快慢指针,快指针走到尾节点时,慢指针恰好在链表中间。

// 求中间结点
  public static Node findMiddleNode(Node list) {
    if (list == null) return null;
    Node fast = list;
    Node slow = list;
    while (fast.next != null && fast.next.next != null) {
      fast = fast.next.next;
      slow = slow.next;
    }
    return slow;
  }

3、删除倒数第K个节点

为了能够只遍历一次就能找到倒数第k个节点,可以定义两个指针:

  (1)第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动

  (2)从第k步开始,第二个指针也开始从链表的头指针开始遍历

  (3)由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点

  下图展示了在有6个结点的链表上找倒数第3个结点的过程:

       

代码如下:

// 删除倒数第K个结点
  public static Node deleteLastKth(Node list, int k) {
    Node fast = list;
    int i = 1;

    while (fast != null && i < k) {
      fast = fast.next;
      ++i;
    }

    if (fast == null) return list;
    Node slow = list;
    Node prev = null;

    while (fast.next != null) {
      fast = fast.next;
      prev = slow;
      slow = slow.next;
    }

    if (prev == null) {
      list = list.next;
    } else {
      prev.next = prev.next.next;
    }
    return list;
  }

4、合并两个有序链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
//合并两个有序链表
  public static Node mergeTwoLists(Node la, Node lb){
      if(la == null) return lb;
      if(lb == null) return la;

      Node head = null;
      if(la.data <= lb.data){
          head = la;
          head.next = mergeTwoLists(la.next, lb);
      } else {
          head = lb;
          head.next = mergeTwoLists(la, lb.next);
      }
      return head;
  }

5、检测环,并找到入口点

(1)判断是否有环:

对于这个问题我们可以采用“快慢指针”的方法。就是有两个指针fast和slow,开始的时候两个指针都指向链表头head,然后在每一步操作中slow向前走一步即:slow = slow->next,而fast每一步向前两步即:fast = fast->next->next。

由于fast要比slow移动的快,如果有环,fast一定会先进入环,而slow后进入环。当两个指针都进入环之后,经过一定步的操作之后二者一定能够在环上相遇,并且此时slow还没有绕环一圈,也就是说一定是在slow走完第一圈之前相遇。

slow每次向前走一步,fast向前追了两步,因此每一步操作后fast到slow的距离缩短了1步,这样继续下去就会使得

两者之间的距离逐渐缩小:...、5、4、3、2、1、0 -> 相遇。又因为在同一个环中fast和slow之间的距离不会大于换的长度,因此

到二者相遇的时候slow一定还没有走完一周(或者正好走完以后,这种情况出现在开始的时候fast和slow都在环的入口处)。

(2)找入口点

思路: 
如果单链表有环,当slow指针和fast指针相遇时,slow指针还没有遍历完链表,而fast指针已经在环内循环n(n>=1)圈了,假设此时slow指针走了s步,fast指针走了2s步,r为fast在环内转了一圈的步数,a为从链表头到入口点的步数,b为从入口点到相遇点的步数,c为从相遇点再走c步到达入口点,L为整个链表的长度。

slow指针走的步数:
s = a + b
fast指针走的步数:
2s = s + n*r 即:s = n*r
链表的长度:
L = a + b + c = a+r
由上可得:
a + b = n*r = (n - 1)*r + r
而r = L - a,所以:
a + b = (n - 1)*r + L - a
a = (n - 1)*r + L - a - b
而L - a - b = c,所以:
a = (n -1)*r +c
综上可得:从链表头到环入口点等于(n - 1)循环内环 + 相遇点到环入口点,于是在链表头和相遇点分别设置一个指针,同时出发,每次各走一步,它们必定会相遇,且第一次相遇的点就是环入口点。

//找到环入口点
  public static Node findLoopPoint(Node list) {
    if (list == null) return null;
 
    Node fast = list,slow = list;
    while (fast != null && fast.next != null) {
      fast = fast.next.next;
      slow = slow.next;
      if (slow == fast) break;
    }
 
    if(fast == null || fast.next == null){
      return null;
    }
    //如果有环,slow指向连表头,此时fast指向相遇点
    slow = list;
    while (slow != fast){
      slow = slow.next;
      fast = fast.next;
    }
    return slow;
  }

6、找到两个单链表相交的起始节点

A:          a1 → a2
                   ↘
                     c1 → c2 → c3
                   ↗            
B:     b1 → b2 → b3

因为链表是单向链表,所以相交链表的形状就像倒置的 Y,这就有个==特点==:相交点之后的结点都相同。所以找到第一个相同的结点就是链表的第一个公共结点。

如果正向遍历链表,因为链表的长度不相同,所以无法通过的时间使他们的步伐统一。解决办法是首先比较链表两个的长度,然后让较长的链表的先走他们长度的差值,然后同一步伐一起走,知道走到的结点相同时,就找的了第一个公共结点。

时间复杂度分析:空间复杂度O(1),时间复杂度O(n)

 //计算链表的长度
private int calculateNodes(Node head){
    int val=0;
    for(;head!=null;head=head.next)
        val++;
    return val;
}
//循环遍历,需要借助于栈
public ListNode getIntersectionNode(Node headA, Node headB) {
    if(headA==null || headB==null)
        return null;
    //用来保存链表之间的差值
    int temp=0;
    ListNode res=null;

    //计算两个链表的长度,较长的链表先走几步
    int lenA=calculateNodes(headA);
    int lenB=calculateNodes(headB);
    if(lenA>lenB){
        temp=lenA-lenB;
        for(int i=0;i<temp;i++)
          headA=headA.next;
    }
    else{
        temp=lenB-lenA;
        for(int i=0;i<temp;i++)
            headB=headB.next;
    }

    //两个链表同步走,当脚下节点相同时,就找到了
    for(;headA!=null && headB!=null;headA=headA.next,headB=headB.next){
        if(headA==headB){
            res=headA;
            break;
        }

    }
    return res;
}

7、奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

示例 2:

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

说明:

  • 应当保持奇数节点和偶数节点的相对顺序。
  • 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

解题:

  我的想法是这样的,将原链表拆分成奇偶链表然后在合并

  这样的话涉及几个步骤:

  1.两个链表的初始节点,如STEP0所示。

  2.odd跳到even的下一个,even再跳到odd的下一个,然后将even串到evenHead上,如此往复(请看STEP1、STEP2)。

  3.最后,将odd的下一个串到evenHead,实现链接。

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head==null)
            return null;
        ListNode oddList=head;
        ListNode evenList=head.next;
        ListNode evenHead = evenList;
        while(evenList!=null&&evenList.next!=null)
        {
            oddList.next=evenList.next;
            oddList=oddList.next;
            evenList.next=oddList.next;
            evenList=evenList.next;
        }
        oddList.next=evenHead;
        return head;
    }
    
}

8、回文链表

请编写一个函数,检查链表是否为回文。

给定一个链表ListNode  pHead,请返回一个bool,代表链表是否为回文。

public class Palindrome {
    public boolean isPalindrome(ListNode pHead){
        ListNode fast = pHead;
        ListNode slow = pHead;
        Stack<Integer> stack = new Stack<Integer>();
        /**
         * 将链表的前半部分元素装入栈中,当快速runner
                 *(移动的速度是慢速runner的两倍)
         * 到底链表尾部时,则慢速runner已经处于链表中间位置
         */
        while(fast != null && fast.next != null){
            stack.push(slow.val);
            slow = slow.next;
            fast = fast.next.next;
        }
        //当链表为奇数个时,跳过中间元素
        if (fast != null) {
            slow = slow.next;
        }
        while(slow != null){
            //如果两者不相同,则该链表不是回文串
            if (stack.pop() != slow.val) {
                return false;
            }
            slow = slow.next;
        }
        return true;
    }
}

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值