链表常见操作及解题思路

1.迭代。
链表最常见的操作就是迭代。

 while (head.next != null) {
            head = head.next;
        }

2.链表转化为数组
涉及到下标的问题,都可以将链表转化为数组解决,数组的每一个元素都是一个节点。。
示例题目LeetCode 876. 返回链表的中间节点

 public ListNode middleNode(ListNode head) {
        ListNode[] arr = new ListNode[100];
        int t = 0;
        while (head.next != null) {
            arr[t++] = head;
            head = head.next;
        }
        return arr[t / 2];
    }

类似的,输出链表中倒数第k个结点。也可以采用转化为数组的思路。

3.删除链表的一个节点
将当前节点node的指针next指向下一个节点的下一个节点,就相当于删除下个节点了。

node.next=node.next.next;  

示例题目 :
一个有序的链表删除重复数据。

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null || head.next==null){
            return head;
        }
        ListNode node=head;
        while(node!=null && node.next!=null ){
            if( node.val==node.next.val ) {
           //删除重复的节点,不进行迭代,因为重复的值可能出现多次
                node.next=node.next.next;
            }else {
           //迭代
                node=node.next;
            }
 
        }
        return head;
    }
}

3.哈希解法
可以将链表的节点,作为HashMap的key,再用HashMap的containsKey()来判断当前节点是否已经存在。

示例,判断链表是否有环:

public boolean hasCycle(ListNode head) {
	if(head==null) {
		return false;
	}
	Map<ListNode,Integer> map=new HashMap<>();
	while(head!=null) {
		if(map.containsKey(head)) {
			return true;
		}
		map.put(head,1);
		head=head.next;
	}
	return false;
}

4.双指针操作
4.1快慢指针
使用两个指针fast、slow,用不同的起点/速度去遍历。
使用双指针,要注意判断走得快的指针是否空指针异常。
还要注意,while循环的条件判断可以是常规的 node!=null ,也可以是fast!=slow,或者是fast!=null , fast.next!=null之类。

示例题目:输入一个链表,输出该链表中倒数第k个结点。

public ListNode FindKthToTail(ListNode head,int k) { //5,{1,2,3,4,5}
       if(k<0 || head==null ) {
              return head;
         }
        ListNode p, q;
        p = q = head;
        int i = 0;
       //p指针先跑,并且记录节点数,当p指针跑了k-1个节点后,pre指针开始跑,
       //当p指针跑到最后时,pre所指指针就是倒数第k个节点
        for ( ; p != null; i++) {
            p = p.next;
            if (i >= k)
                q = q.next;         
        }
        return i < k ? null : q;
    }

示例题目:判断一个链表,是否为环形链表。
思路:处理环形链表时,也可以使用快慢指针,一个走得快,一个走得慢,如果是环形链表,那么这两个指针最终会相遇。

public boolean hasCycle(ListNode head) {
  ListNode slow = head, fast = head;
  //fast指针走得比较快,要小心空指针。。
  while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
    
    if (slow == fast) 
        return true;
  }
  
  return false;
}

4.2前后节点指针
上一个节点prev,当前节点curr。
示例:链表删除指定的数据

 /* Example:
 * Input:  1->2->6->3->4->5->6, val = 6
 * Output: 1->2->3->4->5
 */
public class LeetCode203 {
        public ListNode removeElements(ListNode head, int val) {
            //通过fakeHead.next记住链表,以便返回结果
            ListNode fakeHead = new ListNode(-1);
            fakeHead.next = head;
            //curr是当前节点,prev是上一个节点。
            ListNode curr = head, prev = fakeHead;
            while (curr != null) {
                if (curr.val == val) {
                    prev.next = curr.next;
                } else {
                    prev = prev.next;
                }
                curr = curr.next;
            }
            return fakeHead.next;
        }
}

4.新建链表解决问题,或者在原来的链表上解决。
5.反转链表
(1)就地反转。(2)新建链表反转。
(3)可以通过栈实现,将链表数据放入栈里面,再拿出来。也就是通过Stack,push()进去,再pop()出来。
(4)通过递归解决。
详情见: https://www.cnblogs.com/expiator/p/10630481.html

5.2 k个一组反转链表:

     public ListNode reverseKGroup(ListNode head, int k) {
        //k个一组反转链表
        //创建一个虚拟节点
        ListNode fake = new ListNode(0);
        fake.next = head;

        //pre 代表待翻转链表的前驱,end 代表待翻转链表的末尾
        ListNode pre = fake;
        ListNode end = fake;

        while (end.next!=null) {
            //检查下剩下的还有没有 k 个
            for (int i=0;i<k && end!=null;i++) {
                end = end.next;
            }
            //记录待翻转链表的后继 end,如果 end为 null,说明已经到达末尾了,不足k个。
            if (end == null) {
                break;
            }
            
            //先记下待翻转的前驱
            ListNode start = pre.next;
            //记下待翻转部分的末尾
            ListNode next = end.next; 
            //k个一组,先断开链表
            end.next = null;
            //翻转链表, pre.next指向翻转后的链表
            pre.next = reverse(start);
            //翻转之后,start变成了已翻转部分的末尾,需要跟未翻转部分连接。
            start.next = next;
            //重置变量,继续迭代
            //pre换成下次要翻转的链表的头结点的上一个节点.
            pre = start;
            //翻转结束,将end置为下次要翻转的链表的头结点的上一个节点,继续迭代
            end = pre;

        }

        return fake.next;

    }


    public ListNode reverse(ListNode head) {
        //反转链表
        if (head==null) {
            return null;
        }
        ListNode pre = null;
        ListNode curr = head;

        //上一个节点,当前节点,下一个节点
        //记住下一个节点
        while (curr!=null) {
            ListNode next = curr.next;
            //当前节点指向上一个节点
            curr.next = pre;
            //向后迭代,当前节点变成上一个节点,下一个节点,变成当前节点
            pre = curr;
            curr = next;
            
        }

        return pre;
    }

6.链表删除指定位置的数据。
7.组装两个有序链表。.
类似于小学时,老师将两支小分队按从矮到高排成一队。

class LeetCode21 {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null ) {
            return l2;
        }
        if(l2==null) {
            return l1;
        }
        //头节点node
        ListNode node=new ListNode(0);
        ListNode resultNode=node;
        //将数据小的节点插入到新的节点中。
        while(l1!=null && l2!=null){
            if(l1.val<l2.val ) {
                resultNode.next=l1;
                l1=l1.next;     
            }else {
                resultNode.next=l2;
                l2=l2.next;
            }
             resultNode=resultNode.next;
        }
        if(l1==null) {
            resultNode.next=l2;
        }
        if(l2==null) {
            resultNode.next=l1; 
        }
        return node.next;
    }
}

8.A指针走完了一个链表,就指向另一个链表,B指针也是。最后两个指针会同时到达终点。

示例:求两个链表的相交结点。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
	if (headA == null || headB == null) {
		return null;
	}
	ListNode pA = headA;
	ListNode pB = headB;
	//如果相遇了就结束循环。
	while (pA != pB) {
		pA = pA.next;
		pB = pB.next;
		//如果两个链表都走完了所有的路,由于路程的和相同(pA+pB=pB+pA),速度相同,最终会同时到达终点。
		if (pA == null && pB == null) {
			return null;
		}
		//A链表走完就指向B链表
		if (pA == null) {
			pA = headB;
		}
		//B链表走完就指向A链表
		if (pB == null) {
			pB = headA;
		}
	}
	return pA;
}

其他:

  • 虚拟节点(哑节点、伪节点),指向链表头节点:
    添加一个哑节点(dummy node),它的 next 指针指向链表的头节点 head。这样一来,我们就不需要对头节点进行特殊的判断了。
ListNode dummy = new ListNode(0);
dummy.next = head;

如果定义好的链表,有多参数的构造方法,也可以直接:

ListNode dummy = new ListNode(0, head);

posted on 2019-05-02 22:50  乐之者v  阅读(395)  评论(0编辑  收藏  举报

导航