链表高频面试算法题解析

五种方法解决两个链表的公共节点的问题

方法一:利用 Set 集合解决

解决思路

利用Set集合存储链表head1,然后再遍历另一个链表head2并且将其每一个元素都与Set集合中的节点进行匹配,若匹配成功则说明找到两个链表的公共节点了,返回head2

public static ListNode findFirstNodeBySet(ListNode head1, ListNode head2) {
    Set<ListNode> listNodes = new HashSet<>();
    while (head1 != null) {
        listNodes.add(head1);
        head1 = head1.next;
    }
    while (head2 != null) {
        if (listNodes.contains(head2)) {
            return head2;
        }
        head2 = head2.next;
    }
    return null;
}

方法二:利用 Hash 查找

解题思路

和方法一类似

public static ListNode findFirstNodeByHashMap(ListNode head1, ListNode head2) {        
    if (head1 == null || head2 == null) {
        return null;
    }

    HashMap<ListNode, Integer> hashMap = new HashMap<>();
    while (head1 != null) {
        hashMap.put(head1, null);
        head1 = head1.next;
    }

    while (head2 != null) {
        if (hashMap.containsKey(head2)) {
            return head2;
        }
        head2 = head2.next;
    }
    return null;
}

方法三:利用栈寻找公共子节点

解题思路

创建两个栈stackHead1stackHead2,分别将两个链表中的节点加入栈中,再同时遍历两个栈,当两个栈顶元素相同时,则两个栈同时进行出栈操作

public static ListNode findFirstNodeByStack(ListNode head1, ListNode head2) {
    if (head1 == null || head2 == null) {
        return null;
    }

    Stack<ListNode> stackHead1 = new Stack<>();
    Stack<ListNode> stackHead2 = new Stack<>();

    while (head1 != null) {
        stackHead1.push(head1);
        head1 = head1.next;
    }

    while (head2 != null) {
        stackHead2.push(head2);
        head2 = head2.next;
    }

    ListNode tempNode = null;
    while (stackHead1.size() > 0 && stackHead2.size() > 0) {
        if (stackHead1.peek() == stackHead2.peek()) {
            tempNode = stackHead1.pop();
            stackHead2.pop();
        } else {
            break;
        }
    }

    return tempNode;
}

方法四:拼接两个字符串

解题思路
现有字符串AB
A:1-2-3-4-5-6
B:11-22-4-5-6
按照ABBA的方式分别进行拼接可得到
AB:1-2-3-4-5-6-11-22-4-5-6
BA:11-22-4-5-6-1-2-3-4-5-6

定义两个指针,在遍历完一个链表之后,调整指针继续遍历另一个链表

public static ListNode findFirstNodeByCombine(ListNode head1, ListNode head2) {
    if (head1 == null || head2 == null) {
        return null;
    }

    ListNode p1 = head1;
    ListNode p2 = head2;

    while (p1 != p2) {
        p1 = p1.next;
        p2 = p2.next;
        if (p1 != p2) {
            if (p1 == null) {
                p1 = head2;
            }
            if (p2 == null) {
                p2 = head1;
            }
        }
    }

    return p1;
}

方法五:差和双指针

解题思路

先定义两个指针current1current2利用循环分别求出两个链表的长度size1size2,求差得到sub,然后调整两个指针让其重新指向链表头,将指向长度较长的链表指针先移动sub长度,最后再同时移动两个指针寻找两个链表的共同节点

public static ListNode findFirstNodeBySub(ListNode head1, ListNode head2) {

    int size1 = 0;
    int size2 = 0;
    ListNode current1 = head1;
    ListNode current2 = head2;

    while (current1 != null) {
        size1++;
        current1 = current1.next;
    }

    while (current2 != null) {
        size2++;
        current2 = current2.next;
    }

    int sub = size1 > size2 ? size1 - size2 : size2 - size1;
    current1 = head1;
    current2 = head2;

    if (size1 > size2) {
        while (size1 != size2) {
            current1 = current1.next;
            size1--;
        }
    } else {
        while (size1 != size2) {
            current2 = current2.next;
            size2--;
        }
    }

    while (current1 != current2) {
        current1 = current1.next;
        current2 = current2.next;
    }

    return current1;
}

判断链表是否回文

方法一:栈解决

解题思路

将链表中的每个元素存储到一个栈中,并记录入栈元素的个数。然后,从栈中依次出栈一半的元素。 在出栈的同时,从链表的头部开始向后遍历,并将栈中的元素与链表中的元素进行比较,这样操作的目的是比较栈中的元素与链表中的对应元素是否相等。依次可得到该链表是否为回文链表

public static boolean isBackStringByStack(ListNode head) {

    if (head == null) {
        return true;
    }

    Stack<ListNode> nodeStack = new Stack<>();
    ListNode current = head;
    int size = 0;

    while (current != null) {
        nodeStack.push(current);
        size++;
        current = current.next;
    }

    size >>= 1;
    while (size-- >= 0) {
        if (nodeStack.pop().val != head.val) {
            return false;
        }
        head = head.next;
    }
    return true;
}

方法二:快慢指针

解题思路

先定义两个指针一个快指针fast一个慢指针slow,两个指针同时开始遍历链表head,慢指针一次向后移动一位,快指针一次向后移动两位,在快指针遍历结束后,慢指针正好指向链表head的中间位置,再利用prepre指针可让pre指向反转后的前半部分链表,最后让preslow(此时slow指向后半段链表)同时向后遍历,当遇到元素不等时说明不是回文链表

public static boolean isBackStringByTwoPoints(ListNode head) {
    if (head == null) {
        return true;
    }

    ListNode slow = head, fast = head;
    ListNode pre = head, prepre = null;

    // 该 while 循环结束后 pre 将指向反转后的前一半 head 链表
    while (fast != null && fast.next != null) {
        pre = show;
        slow = slow.next;
        fast = fast.next.next;
        pre.next = prepre;
        prepre = pre;
    }

    // 针对待判断链表个数为奇数时,保证
    if (fast != null) {
        slow = slow.next;
    } 

    while (pre != null && slow != null) {
        if (pre.val != slow.val) {
            return false;
        }
        pre = pre.next;
        slow = slow.next;
    }

    return true;
}

方法三:递归解决

解题思路

利用递归让指向链表的头指针head先指向链表的最后,指针temp指向头部,然后依次比较temp.valhead.val(在递归中,head.val表示从后往前遍历的节点值,temp.val表示从前往后遍历的节点值)

static ListNode temp;

public static boolean isPalindromeByRe(ListNode head) {
    if (head == null) {
        return true;
    }
    temp = head;
    return check(head);
}

private static boolean check(ListNode head) {
    if (head == null) {
        return true;
    }
    boolean res = check(head.next) && (temp.val == head.val);
    temp = temp.next;
    return true;
}

合并链表

合并两个有序链表

解题思路

创建新链表,同时遍历两个链表将两个链表中较小的元素优先接入新链表,直到某一个链表为空后,再将另一个非空链表中未遍历到的元素接入新链表中

public static ListNode createNewList(ListNode list1, ListNode list2) {
	ListNode newListNode = new ListNode(-1);
	ListNode pre = newListNode;
	while (list1 != null && list2 != null) {
		if (list1.val <= list2.val) {
			newListNode.next = list1;
			list1 = list1.next;
		} else {
			newListNode.next = list2;
			list2 = list2.next;
		}
		newListNode = newListNode.next;
	}

	newListNode.next = list1 == null ? list2 : list1;
	return pre.next;
}

合并 k 个有序链表

解题思路

通过遍历链表数组反复调用以上合并两个有序链表的方法,依次将多个链表进行合并

public static ListNode mergeLists(ListNode[] listNodes) {
	ListNode res = null;
	for (ListNode list : listNodes) {
		res = createNewList(res, list);
	}
	return res;
}

合并两个链表

力扣 1669 题
image.png
解题思路

定义三个指针,其中利用遍历让两个指针一个指向待删除部分的前一位,一个指向待删除部分的后一位,令第三个指针指向待插入链表的最后一位,即可将待删除部分替换为待插入链表

public static ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
	int count = 0;
	ListNode preList1 = list1;
	while (count < a - 1) {
		preList1 = preList1.next;
		count++;
	}
	ListNode tailList1 = preList1;
	int sub = b - a + 1;
	while (sub > 0) {
		tailList1 = tailList1.next;
		sub--;
	}
	preList1.next = tailList1.next; // 删除中间部分链表
	tailList1 = tailList1.next;
	// 拼接链表2
	ListNode tailList2 = list2;
	// 找到链表2的尾节点
	while (tailList2.next != null) {
		tailList2 = tailList2.next;
	}
	tailList2.next = tailList1;
	preList1.next = list2;

	return list1;
}

以上代码中的前两个while循环的目的主要是为了将链表list1的头指针preList1指向待删除部分的前一位,将尾指针tailList1指向待删除部分的后一位,两个指针都需要从头开始遍历,所以可以将两个while合并为一个while循环,代码如下:

public static ListNode mergeInBetweenBeGood(ListNode list1, int a, int b, ListNode list2) {
	ListNode preList1 = list1, tailList1 = list1;
	int i = 0, j = 0;

	// 将指针 prelist1 和 tailList1 指向合适的位置
	while (preList1 != null && tailList1 != null && j < b) {
		if (i != a - 1) {
			preList1 = preList1.next;
			i++;
		}
		if (j != b) {
			tailList1 = tailList1.next;
			j++;
		}
	}
	tailList1 = tailList1.next;
	ListNode tailList2 = list2;
	while (tailList2.next != null) {
		tailList2 = tailList2.next;
	}
	preList1.next = list2;
	tailList2.next = tailList1;
	return list1;
}

双指针

寻找倒数第 k 个元素

解题思路

定义两个指针,一个快指针fast一个慢指针slow,令快指针先走k步,然后快慢指针再同时向后遍历直到快指针遍历到链表的末尾时,则慢指针slow正好指向的是链表list中倒数第k个节点

private static ListNode findEndForNum(ListNode list, int k) {
	if (list == null) {
		return null;
	}
	ListNode fast = list;
	ListNode slow = list;
	while (fast != null && k > 0) {
		fast = fast.next;
		k--;
	}
	while (fast != null) {
		fast = fast.next;
		slow = slow.next;
	}
	return slow;
}

旋转链表

力扣 61 题
解题思路

先计算链表的长度,根据k值找到新链表的头结点和尾节点,再调整节点的连接顺序,即可得到旋转后的链表
如下方法用到了快慢指针,先让快指针向后移动k次,再让快慢指针同时向后移动,直到快指针移动到链表末尾为止,此时慢指针指向的正好就是需要旋转的位置

private static ListNode rotateList(ListNode list, int k) {
	if (list == null) {
		return null;
	}

	ListNode head = list;
	ListNode fast = list;
	ListNode slow = list;

	int size = 0; // 表示链表的长度
	while (list != null) {
		list = list.next;
		size++;
	}

	// k 的值和链表长度相等,与 k = 0 的情况是一样的
	// 都链表内节点不做任何移动
	if (k % size == 0) {
		return head;
	}

	while ((k % size) > 0) {
		fast = fast.next;
		k--;
	}

	while (fast.next != null) {
		fast = fast.next;
		slow = slow.next;
	}
	ListNode res = slow.next;
	slow.next = null;
	fast.next = head;
	return res;
}

细节:
将以上链表长度sizek值取模操作,主要是为了避免k值大于size,且题目中k值在等于size的整数倍时,待旋转的链表顺序不变,所以取模不仅不影响解题,还可以减少快指针移动的次数

删除节点

删除特定节点

力扣 203 题
image.png
解题思路

在删除链表时候,需要注意头结点的删除方式和正常节点的删除方式不一样,需要定义一个头结点
preHead,让其next指向该链表preHead.next = head,循环遍历链表head找到目标节点进行删除

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

删除倒数第 n 个节点

力扣 19 题
解题思路

利用快慢指针解决

public static ListNode removeNthFromEnd(ListNode head, int n) {
	ListNode preHead = new ListNode(0);
	preHead.next = head;
	ListNode fast = preHead;
	ListNode slow = preHead;
	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 preHead.next;
}

删除所有重复元素

力扣 82 题
解题思路

先判断指针cur.next指向的节点和后一位节点cur.next.next的值是否相等,如果相等则进入内循环遍历将cur.next一直向后移动,直到不再出现重复元素为止

public static ListNode deleteDuplicateNode(ListNode head) {
	if (head == null) {
		return head;
	}
	ListNode preHead = new ListNode(-1);
	preHead.next = head;
	ListNode cur = preHead;
	while (cur.next != null && cur.next.next != null) {
		if (cur.next.val == cur.next.next.val) {
			int t = cur.next.val;
			while (cur.next != null && cur.next.val == t) {
				cur.next = cur.next.next;
			}
		} else {
			cur = cur.next;
		}
	}
	return preHead.next;
}

欢迎大佬批评指正😁😁😁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

傻笑不累

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

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

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

打赏作者

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

抵扣说明:

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

余额充值