算法通关村第一关|白银|链表经典问题【持续更新】

1.两个链表第一个公共子节点

1.1 可以考虑将链表元素存储到一个集合里,然后一边遍历第二个链表,一边检测集合中是否存在该结点。本题只需要记录一个结点是否存在,而不需要其他的数据,所以使用 HashSet 。

public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB){
	Set<ListNode> set = new HashSet<>();
    while (headA != null) {
        set.add(headA);
        headA = headA.next;
    }

    while (headB != null) {
        if (set.contains(headB))
            return headB;
        headB = headB.next;
    }
    return null;
}

1.2 可以考虑使用栈保存链表的结点,栈底为链表的头结点,栈顶为链表的尾结点,然后比较两个栈的栈顶元素是否相等,如果相等就一起出栈,直到最后一次相等的元素一起出栈,这个元素就是两个链表的第一个公共子结点。

public ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB){
	Stack<ListNode> stackA = new Stack();
    Stack<ListNode> stackB = new Stack();
    while (headA != null) {
        stackA.push(headA);
        headA = headA.next;
    }
    while (headB != null) {
        stackB.push(headB);
        headB = headB.next;
    }

    ListNode preNode = null;
    while (stackB.size() > 0 && stackA.size() > 0) {
        if (stackA.peek() == stackB.peek()) {
            preNode = stackA.pop();
            stackB.pop();
        } else {
            break;
        }
    }
    return preNode;
}

1.3 将两个链表一前一后拼接在一起,两种拼接方式的长度是一样的,而且两个拼接后的链表最后几位是相同的,也就是说相同的第一位就是公共节点,可以采用双指针。两个指针最终走的长度是相等的。

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
	if (pHead1 == null || pHead2 == null) {
        return null;
    }
    ListNode p1 = pHead1;
    ListNode p2 = pHead2;
    while (p1 != p2) {
        p1 = p1.next;
        p2 = p2.next;
        if (p1 != p2) {
            if (p1 == null) {
                p1 = pHead2;
            }
            if (p2 == null) {
                p2 = pHead1;
            }
        }
    }
    return p1;
}

1.4 用长的链表的长度减去短的链表的长度,得到两个链表的差值。长的链表可以先走这个差值,然后两个链表一起走,就能同时到达公共节点处。

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2){
	if (pHead1 == null || pHead2 == null) {
        return null;
    }
    ListNode current1 = pHead1;
    ListNode current2 = pHead2;
    int l1 = 0, l2 = 0;
    while (current1 != null) {
        current1 = current1.next;
        l1++;
    }
    while (current2 != null) {
        current2 = current2.next;
        l2++;
    }
    current1 = pHead1;
    current2 = pHead2;
    int sub = l1 > l2 ? l1 - l2 : l2 - l1;
    if (l1 > l2) {
        int a = 0;
        while (a < sub) {
            current1 = current1.next;
            a++;
        }
    }
    if (l1 < l2) {
        int a = 0;
        while (a < sub) {
            current2 = current2.next;
            a++;
        }
    }
    while (current2 != current1) {
        current2 = current2.next;
        current1 = current1.next;
    }
    return current1;
}

2.判断链表是否为回文序列

2.1 将链表元素全部压栈,然后一边出栈,一边重新遍历链表。

public boolean isPalindrome(ListNode head){
	ListNode temp = head;
	Stack<Integer> stack = new Stack();
	while (temp != null) {
        stack.push(temp.val);
        temp = temp.next;
    }
	while (head != null) {
        if (head.val != stack.pop()) {
            return false;
        }
        head = head.next;
    }
	return true;
}

2.2 也可以使用集合中的 ArrayList 存储每个 val 值,然后使用 get 方法对前后进行比较。

public boolean isPalindrome(ListNode head) {
	List<Integer> vals = new ArrayList<Integer>();
    ListNode currentNode = head;
    while(currentNode != null){
        vals.add(currentNode.val);
        currentNode = currentNode.next;
    }
    int front = 0;
    int back = vals.size() - 1;
    while(front < back){
        if(vals.get(front) != vals.get(back)){
            return false;
        }
        front++;
        back--;
    }
    return true;
}

3.合并有序链表

3.1 创建一个新的链表进行迭代合并两个有序链表。

public ListNode mergeTwoLists(ListNode list1, ListNode list2){
	ListNode prehead = new ListNode(-1);
    ListNode prev = prehead;
    while (list1 != null && list2 != null) {
        if (list1.val <= list2.val) {
            prev.next = list1;
            list1 = list1.next;
        } else {
            prev.next = list2;
            list2 = list2.next;
        }
        prev = prev.next;
    }
    prev.next = list1 == null ? list2 : list1;
    return prehead.next;
}

3.2 合并K个链表:先将两个链表合并,然后循环调用该方法将K个链表都合并进去。

//使用上一步的合并的方法
public ListNode mergeKLists(ListNode[] lists){
    ListNode res = null;
	for (ListNode list : lists) {
        res = mergeTwoLists(res, list);
    }
    return res;
}

3.3 用一个链表替换掉另一个链表的某一部分:找到对应的结点。

public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2){
	ListNode pre1 = list1, post1 = list1, post2 = list2;
    int i = 0, j = 0;
    while (pre1 != null && post1 != null && j < b) {
        if (i != a - 1) {
            // pre1停在list1中下标为a-1的地方
            pre1 = pre1.next;
            i++;
        }
        if (j != b) {
            post1 = post1.next;
            j++;
        }
    }
    // 刚出循环的post1指向的正是list1的下标为b的地方
    // post1.next即b的下一位
    post1 = post1.next;
    while (post2.next != null) {
        // 找到list2的尾结点
        post2 = post2.next;
    }
    pre1.next = list2;
    post2.next = post1;
    return list1;
}

4.双指针

4.1 寻找中间结点:快指针走两步,慢指针走一步。本写法遇到偶数的时候,慢指针最后会移动到中间偏后的位置,如果循环条件改为fast.next.next是否为空,就可以让慢指针最后移动到中间偏前的位置。

public ListNode middleNode(ListNode head) {
	ListNode slow = head, fast = head;
	while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
	return slow;
}

4.2 寻找倒数第K个元素:用fast指针提前走,和slow指针形成一个间隔为K个结点的区间,然后一起移动。

public ListNode getKthFromEnd(ListNode head, int k){
	ListNode fast = head;
    ListNode slow = head;
    while (fast != null && k > 0) {
        fast = fast.next;
        k--;
    }
    while (fast != null) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

4.3.1 旋转链表:还是卡一个区间,一起移动。

public ListNode rotateRight(ListNode head, int k) {
	if (head == null || k == 0) {
        return head;
    }
    ListNode temp = head;
    ListNode fast = head;
    ListNode slow = head;
    int len = 0;
    while (head != null) {
        head = head.next;
        len++;
    }
    if (k % len == 0) {
        return temp;
    }
    //这里不存在链表的长度小于K的问题,所以循环条件不需要给fast判空
    while ((k % len) > 0) {
        fast = fast.next;
        k--;
    }
    while (fast.next != null) {
        fast = fast.next;
        slow = slow.next;
    }
    ListNode res = slow.next;
    slow.next = null;
    fast.next = temp;
    return res;
}

4.3.2 旋转链表:使用链表反转。【持续更新】。

5.删除链表元素

5.1 删除特定结点:使用虚拟节点dummyHead。

public ListNode removeElements(ListNode head, int val){
	ListNode dummyHead = new ListNode(0);
    dummyHead.next = head;
    ListNode cur = dummyHead;
    while (cur.next != null) {
        if (cur.next.val == val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return dummyHead.next;
}

5.2 删除倒数第n个结点:同样使用双指针卡区间,只不过这次是删除结点。

public ListNode removeNthFromEnd(ListNode head, int n) {
	ListNode dummy = new ListNode(0);
    dummt.next = head;
    ListNode fast = head;
    ListNode slow = dummy;
    for (int i = 0; i < n; i++) {
        fast = fast.next;
    }
    while (fast != null) {
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return dummy.next;
}

5.3.1 删除重复元素:重复元素保留一个,发现下一个元素与当前元素相同就cur.next = cur.next.next将其删除。

public ListNode deleteDuplicates(ListNode head) {
	if (head == null) {
        return head;
	}
	ListNode cur = head;
	while (cur.next != null) {
        // 一直遇到重复的就一直比较,直到不重复了才将cur移动到下一位不重复的地方
        if (cur.val == cur.next.val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
	return head;
}

5.3.2 删除重复元素:重复元素都不要,需要创建一个dummy,比较下一个和下一个的下一个的val是否相同,相同则从下一个开始

public ListNode deleteDuplicates(ListNode head) {
	if (head == null) {
        return head;
	}
	ListNode dummy = new ListNode(0, head);
	ListNode cur = dummy;
	while (cur.next != null && cur.next.next != null) {
        if (cur.next.val == cur.next.next.val) {
            int x = cur.next.val;
            while (cur.next != null && cur.next.val == x) {
                cur.next = cur.next.next;
            }
        } else {
            cur = cur.next;
        }
    }
	return dummy.next;
}

如果对您有帮助,请点赞关注支持我,谢谢!❤
如有错误或者不足之处,敬请指正!❤

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值