剑指Offer______链表

193 阅读3分钟

6.从尾到头打印链表

题目描述: 输入一个链表的头节点,从尾到头反过来打印出每个节点的值, 链表的结点定义如下:

public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}

解题思路1\color{#008000}{解题思路1:}借助于栈:我们从头到尾遍历链表,可以把遍历的结果放入栈中(先进后出),每经过一个节点,把该节点放入栈中,这样输出栈就实现了从尾到头输出链表元素

//返回动态数组ArrayList
import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) { //一直指向最后一个节点
            stack.push(listNode.val);	//进栈,取当前节点的值放入栈中
            listNode = listNode.next;	// 更新当前节点为下一个节点
        }
        ArrayList<Integer> res = new ArrayList<>();
        while (!stack.empty()) {
             res.add(stack.pop());	//出栈,取出当前栈顶元素然后放入list中
        }
        return res;
    }
}
//返回数组
class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<Integer> stack=new Stack<>();
        ListNode cur=head;
        while(cur!=null){
            stack.push(cur.val);
            cur=cur.next;
        }
        int[] arr=new int[stack.size()];	//声明数组
        while(!stack.isEmpty()){
            for(int i=0;i<arr.length;i++){
            arr[i]=stack.pop();
               }
        }
    return arr; 
	}
}

栈的基本操作:\color{red}{栈的基本操作: }

stack.push( i );是将一个元素i值入栈  
stack.empty( );是判断栈中是否为空  
stack.pop( );是将栈中当前元素出栈

List集合常用方法之

list.add(e); 向列表的尾部追加指定的元素

解题思路2\color{#008000}{解题思路2:}利用递归,即每访问到一个结点的时候,先递归输出它后面的结点,再输出该结点自身,这样链表的结果就反过来了。

import java.*;
public class Solution {
    ArrayList<Integer> arrayList=new ArrayList<Integer>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode!=null){	//当前节点不为空
            this.printListFromTailToHead(listNode.next); // 往下递归
            arrayList.add(listNode.val); //将节点的val值放入arrayList列表中
        }
        return arrayList;
    }
}

22.链表中倒数第k个结点

题目描述:
输入一个链表,输出该链表中倒数第k个结点

解题思路1\color{#008000}{解题思路1:}通过初始化两个移动节点的位置距离为k,然后同时移动两个节点,知道第二个节点移动到链表的末尾时,移动节点1的位置就是链表倒数第k个节点。

/*****
public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}
*****/
public class Solution {
        public ListNode FindKthToTail(ListNode head,int k) {
        ListNode removeNode = head;
        while (k != 0) {
            if (removeNode == null) {	//k 大于链表的长度,直接返回null
                                        (removeNode都为null了k还不为0,说明k大于链表长度
                                        )
                return null;
            }
            removeNode = removeNode.next;
            k--;
        }
        while (removeNode != null) {	// 这个循环其实就是同时移动head和removeNode两个节点。
            removeNode = removeNode.next;
            head = head.next;
        }
        return head;
    }

解题思路2\color{#008000}{解题思路2:} 采用递归的方式去模拟链表从尾到头的这样一个方向,然后在从尾到头的过程中,去判断当前节点的位置,是否为倒数第k个即可。

public class Solution {
	private ListNode ans; /// 最终返回的结果
	private int sum; /// 用来记录当前节点是倒数第几个节点

	private void dfs(ListNode node, int k) {
       if (node.next != null) {
           dfs(node.next, k); /// 继续递归到下一节点。
       }
       // 下面这部分其实就是判断当前层的节点是倒数第几个节点。
       sum++;
       if (sum == k) {
           ans = node;
       }
   }

   public ListNode FindKthToTail(ListNode head, int k) {
       ans = null;
       sum = 0;
       if (head == null) {  /// 说明链表为null,就没有必要去递归的需要了
           return null;
       }
       dfs(head, k); /// 递归遍历链表
       return ans;
   }
}

23.链表中环的入口结点

题目描述:
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null
解题思路:\color{#008000}{解题思路:}通过Map结构保存链表中每个节点出现的次数,第一次出现两次的节点就是我们所要找的环的入口结点,如果没有找到,返回null

/*****
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*****/
import java.util.*;
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead){
        
        Map<ListNode,Integer> map=new HashMap<>();
        while(pHead!=null){
            map.put(pHead,map.getOrDefault(pHead,0)+1);
            	if(map.get(pHead)==2){
                return pHead;
            	}
            pHead=pHead.next;
        }
        return null;
    }
}

23.反转链表

题目描述: 定义一个函数,输入一个链表的头节点,反转该链表后,输出新链表的头节点。
解题思路1\color{#008000}{解题思路1:}通过栈的特性去模拟反转的过程

import java.util.*;
public class Solution {
public ListNode ReverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        Stack<ListNode> stack = new Stack<>();
        while (head != null) {
            stack.push(head);
            head = head.next;
        }

        ListNode removeNode = stack.pop(); // 创建新的链表,需要创建一个新的引用
        ListNode ans = removeNode;
        removeNode.next = null; /// 初始化
        while (!stack.isEmpty()) {
            ListNode x = stack.pop(); /// 取出栈顶节点元素,然后初始化节点元素的next值
            x.next = null;
            
            // 可以用链表的尾接法去理解
            removeNode.next = x;	//将新节点连接到链表的尾部
            removeNode = x;	 //把新节点更新为链表的尾节点
        }
        return ans;
    }
}

解题思路2\color{#008000}{解题思路2:}

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre=null;	//
        ListNode cur=head;  //初始设定
        
        while(cur!=null){	//终止条件 
            //头插法三步走
            //第一步,暂存
            ListNode temp=cur.next;

            //第二步,指向
            cur.next=pre;
            
            //第三步,归位(先归pre,再cur)
            pre=cur;
            cur=temp;

        }return pre;
    }
}

52.两个链表的第一个公共结点

**题目描述:**输入两个链表,找出它们的第一个公共结点 解题思路1\color{#008000}{解题思路1:} 通过栈去模拟从链表的尾部往前遍历两个链表的重合的部分,找到最左侧重合点即可

public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       Stack<ListNode> stack1 = new Stack<>();
       Stack<ListNode> stack2 = new Stack<>();

       while (pHead1 != null) {
           stack1.add(pHead1);
           pHead1 = pHead1.next;
       }
       while (pHead2 != null) {
           stack2.add(pHead2);
           pHead2 = pHead2.next;
       }
       ListNode ans = null;
       while (!stack1.isEmpty() && !stack2.isEmpty()) {
           if(stack1.peek().val == stack2.peek().val) {	//二者顶部元素值相同,处于相同节点
               ans = stack1.peek();
               stack1.pop();
               stack2.pop();
           } else {
               break;
           }
       }
       return ans;
   }
}

栈中的方法peek()pop()\color{red}{栈中的方法peek()和pop()}

stack.peek() 返回栈顶元素,但不在原先堆栈中删除它 
stack.pop() 返回栈顶元素,并在原先堆栈中删除它

解题思路2\color{#008000}{解题思路2:} 先去判断两个链表的长度,移动其中一个链表的头节点,使其两个链表的长度一样,最后从两个链表的头部开始遍历,找到第一个重合点即可。

public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        int len1 = 0;
        int len2 = 0;
        ListNode removeNode1 = pHead1;//统计长度一定要新建变量,
        ListNode removeNode2 = pHead2;
        while (removeNode1 != null) {
            len1++;
            removeNode1 = removeNode1.next;
        }
        while (removeNode2 != null) {
            len2++;
            removeNode2 = removeNode2.next;
        }

        // 下面的两个判断就是是的两个链表的length相同
        if (len1 > len2) {
            for (int i = 1; i <= len1 - len2; i++) {
                pHead1 = pHead1.next;
            }
        } else if (len2 > len1) {
            for (int i = 1; i <= len2 - len1; i++) {
                pHead2 = pHead2.next;
            }
        }
        ListNode ans = null;
        while (pHead1 != null) {
            if (pHead1.val ==pHead2.val) {
                ans = pHead1;
                break;
            }
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        return ans;
    }
 }

18.(一)删除链表中结点

**题目描述:**给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点,返回删除后的链表的头节点。
解题思路:\color{#008000}{解题思路:}
删除节点的操作:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val){
        ListNode dummy=new ListNode(0);	//定义一个虚拟的头节点
        ListNode cur=dummy;  //cur从虚拟头节点开始遍历,保证了真正的头节点head也可以被删除
        cur.next=head;
        
        while(cur!=null&&cur.next!=null){
            if(cur.next.val==val){
                cur.next=cur.next.next;
            }else{
                cur=cur.next;
            }
        }return dummy.next;
    }
}

(二)删除链表中重复的结点

题目描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 解题思路:\color{#008000}{解题思路:}
(排序链表说明重复元素是相邻的)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode cur=head;  //头节点
        while(cur!=null&&cur.next!=null){	//向后遍历,遍历的终止条件:cur在倒数第二个节点即可
        									(链表中那个节点指向null即为尾节点)
            if(cur.val==cur.next.val){	//相邻节点值相同
                cur.next=cur.next.next;	//则删除下一节点
            }else{
                cur=cur.next;	//否则cur向下移,继续遍历
            }
        }return head;
    }
}

(三)删除链表中重复的结点

题目描述:给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字
解题思路:\color{#008000}{解题思路:}
(排序链表说明重复元素是相邻的)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummy=new ListNode(0); //定义虚拟头节点
        dummy.next=head;

        ListNode cur=head;	//
        ListNode pre=dummy;	//定义两个指针
        while(cur!=null){
            while(cur.next!=null&&cur.val==cur.next.val){	//遇到相邻重复节点
                cur=cur.next;
            }
            cur=cur.next;
            if(pre.next.next==cur){	 //没有遇到重复节点
                pre=pre.next;	//pre指针向前移动1位
            }else{
                pre.next=cur;	//遇到重复节点,pre指针移动到cur指针位置
            }
        }return dummy.next;
    }
}

25、合并两个排序的链表

**题目描述:**输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

解题思路:\color{#008000}{解题思路:}
两个链表基于大小比较同时遍历

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummp =new ListNode(0);  //建立虚拟头节点,
        ListNode cur=dummp;
		
        //基于比较,遍历两个链表
        while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                cur.next=l1;	//将cur指针指向更小的节点
                cur=cur.next;	//继续向下遍历,cur起到串联的作用
                l1=l1.next;		//l1是比较的基准,移动到下一个基准
            }else{
                cur.next=l2;
                cur=cur.next;
                l2=l2.next;
            }

        }if(l1!=null){
            cur.next=l1;
        }else{
            cur.next=l2;
        }
        return dummp.next;
    }
}

35、复杂链表的复制

**题目描述:**请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
解题思路:\color{#008000}{解题思路:}
复制节点值: 复制next、random指向关系:

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        HashMap<Node,Node> map=new HashMap<>(); //定义HashMap来存储师徒节点
        Node cur=head;	//定义cur为遍历节点

        //第一次遍历,复制节点值,存value值
        while(cur!=null){
            map.put(cur,new Node(cur.val));  //key为师傅节点,value为徒弟节点
            cur=cur.next;   //向下遍历
        }
        
        //第二次遍历,复制next、random的指向关系
        cur=head;   //遍历前让cur回到head指针
        while(cur!=null){
            map.get(cur).next=map.get(cur.next);
            map.get(cur).random=map.get(cur.random);
            cur=cur.next;
        }
        return map.get(head);   //返回对应的徒弟的头节点
    }
}