经典数据结构题目-链表

链表

707. 设计链表

  • 解题思路

    • 参考官方的单向链表,设置一个成员变量作为虚拟头节点,一个成员变量size保存有效节点数
  • 代码

public MyLinkedList() {
  size = 0;
  head = new ListNode(0);
}


public int get(int index) {
  if (index < 0 || index >= size) {
    return -1;
  }
  // 借助虚拟头节点,可以较为方便地获取到index对应的节点
  ListNode cur = head;
  for (int i = 0; i <= index; i++) {
    cur = cur.next;
  }
  return cur.val;
}

public void addAtHead(int val) {
  addAtIndex(0, val);
}

public void addAtTail(int val) {
  addAtIndex(size, val);
}

public void addAtIndex(int index, int val) {
    if (index > size) {
      return;
    }
    index = Math.max(0, index);
    size++;
  	// 定位到插入位置的前一个元素
    ListNode pred = head;
    for (int i = 0; i < index; i++) {
      pred = pred.next;
    }
  	// 插入元素
    ListNode toAdd = new ListNode(val);
    toAdd.next = pred.next;
    pred.next = toAdd;
}

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;
}

206. 反转链表

解法一

  • 思路

    • 快慢指针进行遍历,快指针先存下当前节点的next,再将当前节点的next指向slow的。两指针再一起往前移动
  • 代码

    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode curr = head;
        while(curr != null){
            ListNode next = curr.next; // 注意点一
            curr.next = pre;
            pre = curr;
            curr = next;
        }
        return pre;
    }
  • 注意点
    • 注意点一:操作链表时,修改next指针时,注意是否需要保存下来,防止找不到了

解法二

  • 思路
    • 使用递归
      • 终止条件。只剩下一个节点,节点为空
      • 递归参数。入参 ,需要反转的链表头;出参,反转后的头
      • 当前层处理。接入到反转后的链表尾,next指针指向空
  • 代码
 public ListNode reverseList(ListNode head) {
   			// 终止条件
        if (head == null || head.next == null) return head;
   			// 递归
        ListNode p = reverseList(head.next);
   			// 当前层处理
        head.next.next = head;
        head.next = null; //注意点一
        return p;
    }
  • 注意点

    • head.next的next指针重新指回head,需要将head的next指针指向null防止,出现环形链表

92. 反转链表 II

解法一

  • 思路

    • 通过一次遍历,定位出反转 开始 和 结束 关键的四个节点
    • 遍历的过程,对left和right区间的进行反转
    • 最后再根据四个关键节点进行重新拼接
  • 代码

     public ListNode reverseBetween(ListNode head, int left, int right) {
         // 定位出四个关键节点
         ListNode leftBefore = null;
         ListNode leftNode = null;
         ListNode rightAfter = null;
         ListNode rightNode = head; // 注意点一
 
         // 定位四个关键节点同时进行反转
         ListNode fast = head;
         ListNode slow =null;
         int index = 1;
         while(index <= right){
             ListNode next = fast.next;
             // 记录下反转开始
             if(index == left){
                 leftBefore = slow;
                 leftNode = fast;
             }
             // 开始进行反转
             if(index >= left){
                 fast.next = slow;
             }
             // 快慢指针移动
             slow = fast;
             fast = next;
             index ++;            
         }
         rightAfter = fast;
         rightNode = slow;
 
         // 四个关键节点进行重新拼接
         leftNode.next = rightAfter;
         if(leftBefore == null){ // 注意点二
             return rightNode;
         }
         leftBefore.next = rightNode;
         return head;
     }
  • 注意点

    • 注意点一:因为结果将rightNode作为头返回,当只有一个元素时且要进行反转时,需要将原头节点返回
    • 注意点二:left从1开始时,会导致leftBefore为空

解法二

  • 思路
    • 递归
      • 终止条件。当left等于1时,以当前为头的N个元素进行反转即可。可参考反转链表题
      • 递归参数。入参,以下一个节点为头的链表,因为少了一个元素,反转的位置都减一。出参,反转后的链表头
      • 当前层处理。头的next指针指向新的链表头
  • 代码
    ListNode afterN = null;

      public ListNode reverseBetween(ListNode head, int left, int right) {
          if(left == 1){
              return reverseN(head,right);
          }
          head.next = reverseBetween(head.next,left-1,right-1);
          return head;
      }

      public ListNode reverseN(ListNode head, int n){
          if(n == 1){
              afterN = head.next; //注意点一
              return head;
          }
          ListNode newHead = reverseN(head.next, n-1);
          head.next.next = head;
          head.next = afterN; 
          return newHead;
      }
  • 注意点
    • 注意点一。反转N个元素后,这N个元素可能是在原本链表的中间,需要记录下来,待整个反转后重新接回

61. 旋转链表

  • 思路
    • 向右移动k个位置,当节点数 n < k 时,实则移动 k %n 格即可。因此需要计算节点数n进行计算
    • 移动k格,实则在 n-k格处断开,n-k+1作为新头,n-k最为新尾,n节点指向老的头节点
  • 代码
    public ListNode rotateRight(ListNode head, int k) {
        if(k == 0 || head == null || head.next == null){ //注意点一
            return head;
        }
        // 统计数量、定位到最后的元素
        int n = 1;
        ListNode oldTail = head;
        while(oldTail.next != null){
            oldTail = oldTail.next;
            n ++;
        }
        if(k % n == 0){
            return head;
        }
        // 计算移动到截断处
        int move = n - k % n;
        ListNode newtail = new ListNode(); // 注意点二
        newtail.next = head;
        while(move > 0){
            newtail = newtail.next;
            move --;
        }
        // 进行截断重新拼接
        ListNode newHead = newtail.next;
        newtail.next = null;
        oldTail.next = head;
        return newHead; 

    }
  • 注意点
    • 注意点一:以上思路,需考虑边界情况是否适用。头为空、一个节点、k为0等
    • 注意点二:移动的格数,是从一个虚拟头节点开始的,因此需要new一个新的ListNode

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

解法一

  • 思路

    • 先统计链表的总数k,新增一个dummy头节点,移动到索引位k-n的节点,删除下一个节点即可
  • 代码

        public ListNode removeNthFromEnd(ListNode head, int n) {
            // 统计总个数
            int k = 0;
            ListNode ptr = head;
            while(ptr != null){
                k++;
                ptr = ptr.next;
            }
            // 移动到k-n位置
            ListNode dummy = new ListNode();
            dummy.next = head;
            ptr = dummy;
            int move = k - n;
            while(move -- > 0){
                ptr = ptr.next;
            }
            // 删除节点
            ptr.next = ptr.next.next;
            return dummy.next;
        }
    

解法二

  • 思路
    • 使用快慢指针,从虚拟节点开始,快指针走了n+1步,快慢再走k - (n+1)布,即快指针指向为空,慢指针抵达移除节点的前一个
  • 代码
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode();
        dummy.next = head;
        ListNode fast = dummy;
        ListNode slow = dummy;
        // 快指针移动 n+1 格
        while(n -- > -1 && fast != null){
            fast = fast.next;
        }
        // 快慢指针一起移动
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }

160. 相交链表

  • 思路
    • 两个指针分别从两个链表头同步开始遍历,为空时从另外一个链表头开始遍历
    • 如果存在相交,两指针走过的节点数相同,最终会指向同一节点。否则不相交
  • 代码
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode ptrA = headA;
        ListNode ptrB = headB;
        boolean changeA = false;
        boolean changeB = false;
        while(ptrA != null && ptrB != null){
            if(ptrA == ptrB){
                return ptrA;
            }
            // a指针默认移动,首次遇到空指针,改变到headB上移动
            if(ptrA.next == null && !changeA){
                ptrA = headB;
                changeA = true;
            }else{
                ptrA = ptrA.next;
            }
            // 同b指针
            if(ptrB.next == null && !changeB){
                ptrB = headA;
                changeB = true;
            }else{
                ptrB = ptrB.next;
            }
        }
        return null;
    }
posted @ 2024-01-14 23:45  gg12138  阅读(19)  评论(0编辑  收藏  举报