1.两个链表的第一个公共子节点
两个链表的头节点已知,相交之后成为一个单链表,但是相交位置未知,并且相交之前的节点数也未知,请设计算法找到两个链表的合并点
链表节点
static class ListNode {
public int val;
public ListNode next;
ListNode(int x) {
val = x;
next = null;
}
//初始化两个链表
//la 为 1 2 3 4 5
//lb 为 11 22 4 5
}
1.1Hash和集合
首先将第一个链表存放在Map里,然后在遍历第二个链表的同时检测当前元素是否在Map里。集合同理
1.1.1通过Hash辅助查找
/**
* 方法1:通过Hash辅助查找
*
* @param pHead1
* @param pHead2
* @return
*/
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) return null;
HashMap<ListNode, Integer> map = new HashMap<>();
ListNode node1 = pHead1;
ListNode node2 = pHead2;
while (node1 != null) { //将第一个链表存入map
map.put(node1,null);
node1 = node1.next;
}
while (node2 != null) { //比较
if (map.containsKey(node2)) {
return node2;
}
node2 = node2.next;
}
return null;
}
1.1.2通过集合辅助查找
基本与Hash一致
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
Set<ListNode> set = new HashSet<>();
ListNode node1=headA;
ListNode node2=headB;
while (node1!=null){
set.add(node1);
node1=node1.next;
}
while(node2!=null){
if(set.contains(node2)){
return node2;
}
node2=node2.next;
}
return null;
}
1.2使用栈
将两个链表同时压栈,若两链表相交之后成为单链表,则必有相交部分的元素先出栈,若出栈的元素不相同了,则公共节点是当前出栈元素的前节点
public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode node1=headA;
ListNode node2=headB;
ListNode preNode = null; //前节点
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
while (node1!=null){ //压栈
stack1.push(node1);
node1=node1.next;
}
while (node2!=null){
stack2.push(node2);
node2=node2.next;
}
while (stack1!=null||stack2!=null){
if(stack1.peek()!=stack2.peek()){ //若栈顶元素不相同,返回前节点
return preNode;
}
preNode=stack1.pop();
stack2.pop();
}
return null;
}
2.判断链表是否为回文序列
2.1使用栈
2.1.1全部压栈
将链表全部压栈,然后一边出栈一边遍历链表,若有一个不相同,则不是回文序列
public boolean isPalindrome(ListNode head) {
Stack<ListNode> stack = new Stack<>();
ListNode node = head;
while(node!=null){//压栈
stack.push(node);
node=node.next;
}
while(head!=null){
if(stack.pop().val!=head.val){//比较
return false;
}
head=head.next;
}
return true;
}
2.1.2优化
获取链表长度并全部压栈,比较的时候只遍历一半的元素
public boolean isPalindrome(ListNode head) {
Stack<ListNode> stack = new Stack<>();
ListNode node = head;
int len=0;
while(node!=null){
stack.push(node);
node=node.next;
len++; //记录链表长度
}
for(int i=0;i<len/2;i++){//遍历一半
if(stack.pop().val!=head.val){
return false;
}
head=head.next;
}
return true;
}
3.合并有序链表
3.1合并两个有序链表
新建一个链表,分别遍历两个链表,每次都选最小的节点链接到新链表
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummyNode = new ListNode(-1);//虚拟节点
ListNode node = dummyNode;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val){
node.next=list1;
list1=list1.next;
}else{
node.next=list2;
list2=list2.next;
}
node=node.next;
}
node.next=list1==null ? list2:list1;
return dummyNode.next;
}
3.2合并多个链表
两两合并,之后再逐步合并
public ListNode mergeLists(ListNode[] lists) {
ListNode res = null;
for(ListNode list : lists){
res = mergeTwoLists(list,res);
}
return res;
}
3.3举一反三
遍历list1找到替换区间的前后两个节点,再找到list2的尾节点,接着将链表连接起来可以了
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode node1 = list1;
ListNode node2 = list2;
while(node2.next!=null){
node2=node2.next;
}
for(int i=1;i<a;i++){
node1=node1.next;
}
ListNode pre = node1; //替换区间的前节点
for(int i=0;i<=b-a+1;i++){
node1=node1.next;
}
ListNode end = node1;//替换区间的后节点
pre.next=list2;
node2.next=end;
return list1;
}
4.双指针专题
4.1寻找中间节点
普通解法:遍历一遍求链表长度,再将链表遍历一半(即:len/2)可解
public ListNode middleNode(ListNode head) {
ListNode node = head;
int len = 0;
while(node!=null){
node=node.next;
len++;
}
for(int i =0;i<len/2;i++){
head = head.next;
}
return head;
}
运用快慢指针解法
用两个指针slow和fast同时遍历链表,slow一次走一步,fast一次走两步,当fast走到头时,slow一定在链表中间。
public ListNode middleNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast!=null&&fast.next!=null){//注意判空
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
当给定元素个数为偶数时,在链表问题中,使用双指针解法会返回中间偏后的节点(上述示例的4),而在数组问题中会返回中间偏前的节点(上述示例的3),原因如下图所示:
4.2寻找倒数第k个元素
我们先将fast遍历到k+1个节点,slow在头节点,之后同时走,当fast指向null时,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旋转链表
public ListNode rotateRight(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
ListNode temp = head;
int len = 0;
while(temp!=null){
temp = temp.next;
len++;
}
if(len<2||k%len==0){
return head;
}
while( k%len > 0){//快指针指向第k%len节点
fast = fast.next;
k--;
}
while(fast.next!=null){
fast = fast.next; //链表尾节点
slow = slow.next; //倒数第k%len+1个节点
}
ListNode res = slow.next;
slow.next = null;//防止链表循环
fast.next = head;//尾连接头(示例一 5连接1)
return res;
}