经典数据结构题目-链表

链表

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 fast = head;
        ListNode slow = null;
        while(fast != null){
            ListNode next = fast.next;  // 注意点一
            fast.next = slow;
            slow = fast;
            fast = slow;
        }
        return last;
    }
  • 注意点
    • 注意点一:操作链表时,修改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;
    }

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
posted @ 2024-01-14 23:45  gg12138  阅读(19)  评论(0编辑  收藏  举报