数据结构与算法:链表

本文深入探讨了链表数据结构,包括单链表的设计、双指针技巧在环形链表、相交链表等问题的应用。重点介绍了如何高效地进行链表节点的插入、删除和查找,以及经典的链表反转和元素移除问题。此外,还涉及了双链表的设计与操作。这些内容涵盖了链表基础与高级算法,对于提升链表操作的熟练度和理解深度非常有帮助。
摘要由CSDN通过智能技术生成

题目来自LeetBook 系列:《链表》——https://leetcode-cn.com/leetbook/read/linked-list/x6ybqh/

单链表

No.707 设计链表

https://leetcode-cn.com/problems/design-linked-list/
第一次写很多细节需要注意。头部和尾部添加别单独写,通过add调用更简单。注意add对于index的判断区间可以有size,这是大问题。

	class MyLinkedList {
		  int size;
		  ListNode head;  
		  
		  //构造器
		  public MyLinkedList() {
		    size = 0;
		    head = new ListNode(0);
		  }
		  
		  //获取链表中第 index 个节点的值。如果索引无效,则返回-1。
		  public int get(int index) {
			  if(index<0 || index>=size){
				  return -1;
				  }
		    	
			  ListNode curr = head;
			  for(int i=0; i<=index; i++){
				  curr = curr.next;
				  }
			  return curr.val;
			  }
		  

		  //指定位置增加
		  public void addAtIndex(int index, int val) {
		    	if(index > size) return;
		    	if(index < 0) index = 0;
		    	
		    	size++;
		    	ListNode pred = head;
		    	for(int i=0; i<index; i++) pred = pred.next;
		    	ListNode addone = new ListNode(val);
		    	
		    	addone.next = pred.next;
		    	pred.next = addone;		    	
		  }
		  
		  //将值为 val 的节点追加到链表的第一个元素。
		  public void addAtHead(int val){
			  addAtIndex(0, val);
		  }
		  
		  //将值为 val 的节点追加到链表的最后一个元素。
		  public void addAtTail(int val) {
			  addAtIndex(size, val);
		  }

		  //删除指定位置
		  public void deleteAtIndex(int index) {
			  if(index<0 || index>=size) return;
			  size--;
			  ListNode pred = head;
			  for(int i=0; i<index; i++) pred = pred.next;
			  
			  pred.next = pred.next.next;
		  }
	
	}

下面是懒得改对的第一遍版本

	class MyLinkedList {
		
		int size;//链表大小
		ListNode head; //链表头节点
		
	    public MyLinkedList() {//构造器
	    	size = 0;
	    	head = new ListNode(0);
	    }
	    
	    /**
	     * get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
	     */
	    public int get(int index) {
	    	if(index <0 || index >= size || size == 0){
	    		return -1;
	    	}
	    	ListNode curr = head;
	    	for(int i=0; i<=index; i++){
	    		curr = curr.next;
	    	}
	    	return curr.val;
	    }
	    /**
	     * addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。
	     * 插入后,新节点将成为链表的第一个节点。
	     */
	    public void addAtHead(int val) {

	    	ListNode newone = new ListNode(val);
	    	if(size == 0){
	    		head.next = newone;
	    	}else{
		    	newone.next = head.next;
		    	head.next = newone;
	    	}
	    	size++;
	    }
	    /**
	     * addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
	     * @param val
	     */
	    public void addAtTail(int val) {
	    	ListNode curr = head;
	    	ListNode newtail = new ListNode(val);
	    	if(size == 0){
	    		head.next = newtail;
	    	}else{
	    		for(int i=0; i<size; i++){
	    			curr = curr.next;
	    			//System.out.println("第" + i + "个数为" + curr.val); 
	    		}
	    		//System.out.println("已指向最后一个"); 
	    		curr.next = newtail;
	    	}
	    	size++;
	    }
	    
	    /**
	     * addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。
	     * 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
	     * 如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
	     */
	    public void addAtIndex(int index, int val) {
	    	//System.out.println("当前i:");
	    	if(index > size-1){//如果 index 大于链表长度,则不会插入节点
	    		return;
	    	}else if(index <= 0){//小于等于0就在头插
	    		addAtHead(val);	    	
	    	}else{
	    		System.out.println("当前i:");
	    		ListNode curr = head;
	    		ListNode newone = new ListNode(val);
	    		for(int i=0; i<index; i++){	 
	    			curr = curr.next;
	    			System.out.println("当前i:" + i);
		    		if(i == index-1){
		    			ListNode nextone = curr.next;
		    			curr.next = newone;
		    			newone.next = nextone;
		    			size++;
		    			break;
		    		}
		    	}
	    	}
	    }
	    /**
	     * deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
	     * @param index
	     */
	    public void deleteAtIndex(int index) {
	    	if(index < 0 || index >= size){
	    		return;
	    	}else if(index == 0){
	    		ListNode oldone = head.next;
	    		head.next = oldone.next;
	    		size--;
	    	}else{
	    		ListNode curr = head;
	    		for(int i=0; i<=index; i++){
	    			curr = curr.next;
	    			if(i == index-1){
	    				ListNode nextone = curr.next;
	    				ListNode nexttwo = nextone.next;
	    				curr.next = nexttwo;
	    				size--;
	    				break;
	    			}
	    		}
	    	}

	    }
	}

双指针技巧

No.141 环形链表

https://leetcode-cn.com/problems/linked-list-cycle/
快慢指针,慢的被套圈就是有环。注意while终止条件!!

	public boolean hasCycle2(ListNode head) {
	    if (head == null)
	        return false;
	    //快慢两个指针
	    ListNode slow = head;
	    ListNode fast = head;
	    while (fast != null && fast.next != null) {
	        //慢指针每次走一步
	        slow = slow.next;
	        //快指针每次走两步
	        fast = fast.next.next;
	        //如果相遇,说明有环,直接返回true
	        if (slow == fast)
	            return true;
	    }
	    //否则就是没环
	    return false;
	}

No.142 环形链表2

https://leetcode-cn.com/problems/linked-list-cycle-ii/
双指针非常的巧妙啊: a=c+(n-1)(b+c)a=c+(n−1)(b+c)
在这里插入图片描述

public ListNode detectCycle(ListNode head) {
	    if (head == null)
	        return null;
	    
	    //快慢两个指针
	    ListNode slow = head;
	    ListNode fast = head;
	    ListNode ans = head;
	    while (fast != null && fast.next != null) {	      
	        slow = slow.next;
	        fast = fast.next.next;
	        
	        if (slow == fast){
	        	while (ans != slow){
	        		ans = ans.next;
	        		slow = slow.next;
	        	}
	        	return ans;
	        }
	    }
	    //否则就是没环
	    return null;
	}

No.160 相交链表

https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
三元运算符真的很炫。

	//简洁且美丽
	public ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
	//错误且繁琐!!
	public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        
		ListNode pa = headA;
		ListNode pb = headB;
		while(pa == pb || (pa == null && pb == null)){
			if(pa.next != null){
				pa = pa.next;
			}else{
				pa = headB;
			}
			
			if(pb.next != null){
				pb = pb.next;
			}else{
				pa = headA;
			}
		}
		
		if(pa == pb){
			return pa;
		}
		
		if(pa == null && pb == null){
			return null;
		}
    }

No.19 删除链表倒数第N个结点

  1. 整个遍历一遍求总数,然后正着删
  2. 双指针
	//遍历一遍求总数正着删
	public ListNode removeNthFromEnd(ListNode head, int n) {
		ListNode dummy = new ListNode(0,head);
		int length = getLength(head);		
		ListNode cur = head;
		for(int i = 1; i < length - n + 1; ++i) {
			cur = cur.next;
		}		
		cur.next = cur.next.next;	
		ListNode ans = dummy.next;
		return ans;
    }
    public int getLength(ListNode head) {
        int length = 0;
        while (head != null) {
            ++length;
            head = head.next;
        }
        return length;
    }

//注意最后返回的是头指针的.next,而不是head,因为head可能被删除了
		public ListNode removeNthFromEnd3(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        ListNode first = head;
        ListNode second = dummy;
        for (int i = 0; i < n; ++i) {
            first = first.next;
        }
        while (first != null) {
            first = first.next;
            second = second.next;
        }
        second.next = second.next.next;
        ListNode ans = dummy.next;
        return ans;
    }

经典问题

No.206 反转链表

  1. 新建一个链表头,挨个放到头前面
  2. 一个pre指前面,一个cur指现在,翻一下(还需要一个tmp存cur的下一个)
	//新建一个链表头,挨个放到头前面
	public ListNode reverseList1(ListNode head){
		ListNode ans = null;
		
		for(ListNode x = head; x!=null; x=x.next ){
			ans = new ListNode(x.val,ans);
		}
		return ans;
	}
	//一个pre指前面,一个cur指现在,翻一下(还需要一个tmp存cur的下一个)
	public ListNode reverseList2(ListNode head){
		ListNode pre = null;
		ListNode cur = head;
		while(cur != null){
			ListNode tmp = cur.next;
			cur.next = pre;
			
			pre = cur;
			cur = tmp;
		}
		return pre;
	}
	//错解,会丢失的,而且一直在1
	public ListNode reverseList(ListNode head) {
		ListNode dummy = new ListNode(0, head);
		ListNode curr = head;
		
		//dummy.next第一个结点
		//curr.next该去头的结点
		while(curr != null){
			System.out.println(curr.val);
			dummy.next = curr.next;
			curr.next = curr.next.next;
			curr.next.next = curr;		
		}		
		return dummy.next;
    }

No.203 移除链表元素

https://leetcode-cn.com/problems/remove-linked-list-elements/
一遍过

    public ListNode removeElements(ListNode head, int val) {
		ListNode dummy = new ListNode(0, head);
		ListNode pre = dummy;
		
		while(pre != null){
			//System.out.println("pre.val=" + pre.val);
			while(pre.next != null && pre.next.val == val){
				pre.next = pre.next.next;
			}
			pre = pre.next;
		}		
    	return dummy.next;
    }

No.328 奇偶链表

一遍过,前面加上空链表判断更好。注意链表的结点后面是拖着东西的。

	public ListNode oddEvenList(ListNode head) {
    	ListNode ji = new ListNode(0);
    	ListNode ou = new ListNode(0);
    	ListNode ji2 = ji;
    	ListNode ou2 = ou;
    	
    	ListNode curr = head;
    	int a = 1;
    	while(curr != null){
    		if(a%2 == 1){//奇数
    			ListNode nowji = new ListNode(curr.val);
    			ji.next = nowji;
    			ji = ji.next;
    		}else if(a%2 == 0){//偶数
    			//System.out.println("偶,a=" + a + ", curr.val = " + curr.val);
    			ListNode nowou = new ListNode(curr.val);
    			ou.next = nowou;
    			ou = ou.next;
    		}
    		a++;
    		curr = curr.next;
    	}
    	ji.next = ou2.next;   	
    	return ji2.next;
    }

双链表

No.707 设计链表

https://leetcode-cn.com/problems/design-linked-list/
先判断从头开始找还是从尾开始找更近。

public class ListNode {
  int val;
  ListNode next;
  ListNode prev;
  ListNode(int x) { val = x; }
}

class MyLinkedList {
  int size;
  // sentinel nodes as pseudo-head and pseudo-tail
  ListNode head, tail;
  public MyLinkedList() {
    size = 0;
    head = new ListNode(0);
    tail = new ListNode(0);
    head.next = tail;
    tail.prev = head;
  }

  /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
  public int get(int index) {
    // if index is invalid
    if (index < 0 || index >= size) return -1;

    // choose the fastest way: to move from the head
    // or to move from the tail
    ListNode curr = head;
    if (index + 1 < size - index)
      for(int i = 0; i < index + 1; ++i) curr = curr.next;
    else {
      curr = tail;
      for(int i = 0; i < size - index; ++i) curr = curr.prev;
    }

    return curr.val;
  }

  /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
  public void addAtHead(int val) {
    ListNode pred = head, succ = head.next;

    ++size;
    ListNode toAdd = new ListNode(val);
    toAdd.prev = pred;
    toAdd.next = succ;
    pred.next = toAdd;
    succ.prev = toAdd;
  }

  /** Append a node of value val to the last element of the linked list. */
  public void addAtTail(int val) {
    ListNode succ = tail, pred = tail.prev;

    ++size;
    ListNode toAdd = new ListNode(val);
    toAdd.prev = pred;
    toAdd.next = succ;
    pred.next = toAdd;
    succ.prev = toAdd;
  }

  /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
  public void addAtIndex(int index, int val) {
    // If index is greater than the length, 
    // the node will not be inserted.
    if (index > size) return;

    // [so weird] If index is negative, 
    // the node will be inserted at the head of the list.
    if (index < 0) index = 0;

    // find predecessor and successor of the node to be added
    ListNode pred, succ;
    if (index < size - index) {
      pred = head;
      for(int i = 0; i < index; ++i) pred = pred.next;
      succ = pred.next;
    }
    else {
      succ = tail;
      for (int i = 0; i < size - index; ++i) succ = succ.prev;
      pred = succ.prev;
    }

    // insertion itself
    ++size;
    ListNode toAdd = new ListNode(val);
    toAdd.prev = pred;
    toAdd.next = succ;
    pred.next = toAdd;
    succ.prev = toAdd;
  }

  /** Delete the index-th node in the linked list, if the index is valid. */
  public void deleteAtIndex(int index) {
    // if the index is invalid, do nothing
    if (index < 0 || index >= size) return;

    // find predecessor and successor of the node to be deleted
    ListNode pred, succ;
    if (index < size - index) {
      pred = head;
      for(int i = 0; i < index; ++i) pred = pred.next;
      succ = pred.next.next;
    }
    else {
      succ = tail;
      for (int i = 0; i < size - index - 1; ++i) succ = succ.prev;
      pred = succ.prev.prev;
    }

    // delete pred.next 
    --size;
    pred.next = succ;
    succ.prev = pred;
  }
}

小结

No.21 合并两个有序链表

https://leetcode-cn.com/problems/merge-two-sorted-lists/
一遍过

	public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null) return list2;
    	if(list2 == null) return list1;
    	
    	ListNode dummy = new ListNode(0);
    	ListNode cur = dummy;
    	
    	ListNode c1 = list1;
    	ListNode c2 = list2;
    	while(c1 != null && c2 != null){
    		if(c1.val >= c2.val){
    			//System.out.println("c1.val=" + c1.val + ",c2.val=" + c2.val + ",1比2大");
    			ListNode newadd = new ListNode(c2.val);
    			cur.next = newadd;
    			cur = cur.next;
    			c2 = c2.next;
    		}else if(c1.val < c2.val){
    			//System.out.println("c1.val=" + c1.val + ",c2.val=" + c2.val + ",2比1大");
    			ListNode newadd = new ListNode(c1.val);
    			cur.next = newadd;
    			cur = cur.next;
    			c1 = c1.next;
    		}
    		//show(dummy.next);
    	}    	
    	
    	if(c1 == null){
    		cur.next = c2;
    	}
    	if(c2 == null){
    		cur.next = c1;
    	}
    	return dummy.next;
    }

No.2 两数相加

https://leetcode-cn.com/problems/add-two-numbers/
思路容易有。
易错点:

  1. 不是开头是0就返回另一个
  2. 有null出现也要再算一次,因为可能有进位
  3. 最高位单独判断要不要进位
	public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    	//不要开头是0就返回另一个
    	
    	ListNode dummy = new ListNode(0);
    	ListNode curr = dummy;

    	int iften = 0;
    	//有null出现后还要算一次,防止+1出现!
    	while(l1 != null || l2 != null ){
    		int n1 = l1==null? 0 : l1.val;
    		int n2 = l2==null? 0 : l2.val;
    		int ansnow = n1 + n2 + iften;
    		System.out.println("n1=" + n1 + ",n2=" + n2 + ",和为:" + ansnow);
    		if(ansnow >= 10){
    			iften = 1;
    			ansnow = ansnow - 10;
    		}else if(ansnow < 10){
    			iften = 0;
    		}
    		if(l1 != null) l1 = l1.next;
    		if(l2 != null) l2 = l2.next;
    		ListNode nodenow = new ListNode(ansnow);
    		curr.next = nodenow;
    		curr = curr.next;
    	}
   	
    	if(iften == 1) curr.next = new ListNode(1);
    	  	
    	return dummy.next;
    }

No.430 扁平化多级双向链表(未完成)

https://leetcode-cn.com/problems/flatten-a-multilevel-doubly-linked-list/
设计递归算法

No.138 复制带随机指针的链表(未完成)

https://leetcode-cn.com/problems/copy-list-with-random-pointer/
起码用到哈希表知识

No.61 旋转链表

https://leetcode-cn.com/problems/rotate-list/
注意:

  1. 取模判断多余部分
  2. 头尾相接之前,先取模判断一下需不需要首尾相接
public ListNode rotateRight(ListNode head, int k) {
    	if (k == 0 || head == null || head.next == null) {
    		return head;
    	}
    	ListNode curr = head;
    	int n = 1;
    	while(curr.next != null){
    		curr = curr.next;
    		n++;
    	}
    	int add = n - k % n;//链表长度为n
    	if (add == n ) {
    		return head;
    	}
    	curr.next = head;//放到判断之后!!
    	curr = head;
    	for(int i=1; i<add; i++){
    		System.out.println(curr.val);
    		curr = curr.next;
    	}
    	ListNode ans = curr.next;
    	curr.next = null;
    	   	
    	//起点是倒数k-1个,末尾是倒数k个 	
    	return ans;
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平什么阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值