算法刷题 链表专题_day01

156 阅读3分钟

复习算法 和 数据结构

链表任务列表

1 删除链表的第N个节点

  • 思路 :
    • 解法一(朴素) : 第一次遍历得链表长度,枚举到倒数第N个节点并删除
    • 解法二(快慢指针) : 快指针先走N个,然后一起走,快指针永远快于慢指针N个节点。快指针走到链表尾部 慢指针得下一个节点即为要删除的节点。
    • 注意只有一个节点的删除问题。
ac_code 区域

解法一:

小边界问题: p != null 包含尾部 p.next != null 不包含尾部

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode p = head;
        int length = 0;
        // p != null 包含尾部
        // p.next != null 不包含尾部
        // 这点要注意
        while (p != null) {
            p = p.next;
            length ++;
        }
        // 检查参数不合法的情况
        if ( n > length) return head;
        if (n == length) return head.next;
        int idx = 0;
        for (p = head ; p.next != null ; p = p.next) {
            idx ++;
            // 如果找到这个点
            if (idx == length - n) {
                p.next = p.next.next;
                break;
            }

        }
        System.out.println(length);
        return head;
    }
}

解法二:快慢指针法

快指针细节依然是 边界问题

if (f == null) return head.next; f == null 极端情况 0 的时候走到了 null 即为删除 第一个节点

    class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode f = head;
        ListNode s = head;
        // 先让 f 走 n 步
        while (n -- > 0) {
            f = f.next; 
            // 合法性的判断
            if (f == null) return head.next;
        }
        for (; f.next != null; f = f.next,s = s.next) ;
        s.next = s.next.next;
        return head;

    }
}

总结 : 两者 都要求边界处理的能力 , 以及链表头指针的删除。

2 删除链表重复项

  • 思路 :
    • 解法一 : 哈希表存值,和出现频率 ,双重遍历 暴力解决。
    • 解法二 : 快慢指针法 快指针前遍历 不同值 连接即可解决

code

解法二 code:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) return null;
        ListNode s = head;
        ListNode f = head;
        for ( ; f != null ; f = f.next){
            // 不同值 逻辑 处理  慢指针 依次连接快指针。
            if ( s.val != f.val) {
                s.next = f;
                s = s.next;
            }
        }
        // 这里必须要断开 ,这里不断的 话 , 后面全是重复元素时 s的next 指针永远指的是重复的值。
        s.next = null;
        return head;
    }
}

3 翻转链表

  • 思路 :
    • 解法1 :迭代 想清楚每步的 链表对应关系即可解答
    • 解法2 :递归

code

解法 1

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode pre = null;
        ListNode next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
}

解法 2

class Solution {
    public ListNode reverseList(ListNode head) {
       return dfs (head);
    }
     private ListNode  dfs (ListNode head) {
      // 递归 三要素 : 递归出口  递归到底的条件 head 为新头
        if (head == null || head.next == null) return head;
      // 递归 到底 终止条件 为 新头
        ListNode newHead = dfs(head.next);
        // 递归最后一层结束 此时的head 为 尾部前一个节点 如此出栈
        // 重复执行相同的操作 这是计算机最擅长的
        head.next.next = head;
        // 这里 想象一下 如果不断开节点 由 1 2 3 4 5 来说
        // 5 为 newHead  4 为head  4 和 5 之间就是双向箭头  闭环链表 为图
        // 所以此处应该断开 指向新头的节点 保留 逆向的指针
        head.next = null;
        return newHead;
    }
}

4 翻转链表Ⅱ

  • 思路 :
    • 解法一 : 迭代 同上一题相似 , 只不过多了 区间限制 双指针闭区间 限制区域即可。
class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {

        int k = n - m;
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;

        while (m-- > 1){
            pre = pre.next;
        }

        ListNode cur = pre.next;
        while (k-- > 0){
            ListNode next = cur.next;
            cur.next  = next.next;
            next.next = pre.next;
            pre.next  = next;
        }

        return dummy.next;
    }
}

晚安