涉及到链表的增删问题,需要考虑增删的位置(前中尾)、增删后保证链表不断裂。下面对剑指offer中出现的链表类题目进行总结:
6.从尾到头打印链表:
输入一个链表,从尾到头打印链表每个节点的值。
解题思路:最直接的方法是改变链表的方向,从尾到头输出,这种方法需要“后进先出”的栈结构;
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Stack<Integer> stack = new Stack<Integer>();
while(listNode!=null){
stack.push(listNode.val);
listNode=listNode.next;
}
ArrayList<Integer> arrayList = new ArrayList<Integer>();
while(!stack.isEmpty()){
arrayList.add(stack.pop());
}
return arrayList;
}
}
或者使用递归结构,每访问到一个节点的时候先递归输出他后面的节点,再输出该节点本身。
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
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);
}
return arrayList;
}
}
在java语言中,ArrayList和LinkedList都支持栈操作,栈的实现方式一般有两种,一种是使用顺序存储的方式,即使用数组---ArrayList来实现,第二种是使用链式存储实现---LinkedList来实现.
22.链表中倒数第K个结点:
输入一个链表,输出该链表中倒数第k个结点。
解题思路:利用两个指针遍历的思路来解决,第一个指针从链表的头指针开始遍历向前走k-1步,第二个指针保持不动,从第K 步开始,第二个指正也从链表的头指针开始遍历,当第一个指针走到链表的尾结点时,第二个结点正好指向倒数第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) {
if(head==null || k==0)
return null;
ListNode listNode = head;
for(int i = 1 ;i<k;i++){
if(head.next!=null){
head = head.next;
}else
return null;
}
while(head.next!=null){
head = head.next;
listNode = listNode.next;
}
return listNode;
}
}
注意:保证代码的鲁棒性。充分考虑可能出现的情况,空指针,节点数少于K,输入的参数为0等。
==>举一反三:用一个指针遍历链表不能解决问题是,尝试用两个指针来遍历链表。例如,求链表的中间结点(一个指针一次走一步,另一个指针一次走两步。)
23.链表中环的入口结点:
一个链表中包含环,请找出该链表的环的入口结点。
解题思路:使用两个指针。将问题分解为三部分:找到环中任意一点。先定义两个指针,从链表的头结点出发,一个指针一次走一步,一个指针一次走两步,当快指针走到链表末尾都没有追上慢指针,则链表中不包含环,否则返回该链表结点;然后得到环中的节点数。上一步得到的结点一定在环中,从该结点出发,下一次回到该结点所走过的结点数目即为环中结点数;最后找到环的入口处。使用两个指针,一个指针先走节点数步,第二个指针在开始走,当两个结点相遇时,即为环的入口处。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode meetingNode(ListNode pHead){
if(pHead==null)
return null;
ListNode node1 = pHead.next;//慢
if(node1==null)
return null;
ListNode node2 = node1.next;//快
while(node1!=null && node2!=null){
if(node1.val==node2.val)
return node1;
node1= node1.next;
node2= node2.next;
if(node2.next!=null)
node2= node2.next;
}
return null;
}
public ListNode EntryNodeOfLoop(ListNode pHead){
//先确定链表中有环,并找出相遇的点,相遇的点一定在环中
ListNode meetingNode = meetingNode(pHead);
if(meetingNode==null)
return null;
//得到环中的节点数
ListNode node = meetingNode.next;
int num =1;
while(node.val != meetingNode.val){
num++;
node = node.next;
}
//找到环的入口处
ListNode p1 = pHead ;
for(int i = 0;i<num;i++)
pHead = pHead.next;
while(pHead.val!=p1.val){
pHead = pHead.next;
p1=p1.next;
}
return p1;
}
}
24.反转链表
输入一个链表,反转链表后,输出链表的所有元素。
解题思路:调整链表的方向,需要保存当前节点的前、后两个结点的信息,防止出现链表断裂的现象,同时考虑如果输入的头结点是 NULL,或者整个链表只有一个结点的情况。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null)
return null;
if(head.next==null)
return head;
ListNode reverse = null;
ListNode behind = null;
while(head!=null){
behind=head.next;//保存head的下一个节点的信息
head.next=reverse;//让head从指向behind变成指向reverse了
reverse=head;
head=behind;
}
return reverse;
}
}
18.删除链表中的重复结点:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
解题思路:新建一个头节点,以防第一个节点被删除。同时保证删除后链表不断裂。(注意考虑重复结点所在的位置,以及删除重复结点后的结果(1,1,1,1,1)(1,1,2)(1,1,2,2,3,3)())
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead){
ListNode index = new ListNode(1); //新建一个节点,防止头结点要被删除
index.next = pHead;
ListNode temp=pHead;
ListNode result = index;
while(temp!=null){
if(temp.next!=null && temp.val==temp.next.val){//重复
while(temp.next!= null && temp.val==temp.next.val){
// 跳过值与当前结点相同的全部结点,找到第一个与当前结点不同的结点
temp = temp.next;
}
temp=temp.next;
index.next = temp;//删除
}else{
index = index.next;
temp = temp.next;
}
}
return result.next;
}
}
52. 两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
解题思路: 如果存在共同节点的话,那么从该节点,两个链表之后的元素都是相同的。可以先遍历两个链表,获得链表的长度,第二次遍历时,较长的链表上先走若干步,接着同时遍历,直到找到第一个相同的结点。(链表题注意如果需要遍历两次,需要在遍历前先新建一个链表,便于第二次从头遍历)
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null || pHead2 ==null)
return null;
int length1 =0,length2 =0;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
//链表的长度
while(p1!=null){
length1++;
p1 = p1.next;
}
while(p2!=null){
length2++;
p2 = p2.next;
}
int dif =0;
//寻找长链表和短链表
if(length1>=length2){
dif = length1-length2;
for(int i=0 ;i<dif;i++)
pHead1 = pHead1.next;
}else{
dif = length2-length1;
for(int j=0 ;j<dif;j++)
pHead2 = pHead2.next;
}
while(pHead1!=null && pHead2!=null && pHead1.val != pHead2.val){
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
if(pHead1==null || pHead2==null)
return null;
else
return pHead1;
}
}
举一反三:两个链表的拓扑形状和树的形状相似,两个链表的第一个公共结点是二叉树中两个叶结点的最低公共祖先。
25.合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题思路:合并两个链表依次从头结点开始依次遍历,逐个添加到已经合并的链表之后,每次合并的步骤相同,可以采用递归的方式。同时考虑程序的鲁棒性,当头结点为空时的情况。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null)
return list2;
else if(list2 == null)
return list1;
if(list1.val < list2.val){
list1.next = Merge(list1.next,list2);
return list1;
}else{
list2.next = Merge(list1,list2.next);
return list2;
}
}
}
35.复杂链表的复制:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解题思路:复杂问题简单化,分为三步解决。第一步,需要在原始链表的每个结点N后创建对应的复制结点N';第二步,设置复制出来的结点的random指针;第三步,将长链表拆分为两部分,偶数位置的结点连接起来即为复杂链表的复制链表。
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
//复制原始链表的结点放到每个原始结点后
public void cloneNode(RandomListNode pHead){
RandomListNode node = pHead;
while( node !=null ){
RandomListNode temp = new RandomListNode(0);
temp.label = node.label;
temp.next = node.next;
temp.random = null;
node.next = temp;
node = temp.next;
}
}
//设置复制出来的结点的random指针
public void randomNode(RandomListNode pHead){
RandomListNode node = pHead;
while(node!=null){
RandomListNode temp = node.next;//复制的点
if(node.random!=null)
temp.random = node.random.next;
node = temp.next;
}
}
//把链表的偶数位置结点复制出来
public RandomListNode copyNode(RandomListNode pHead){
RandomListNode result = new RandomListNode(0);
RandomListNode resultHead = new RandomListNode(0);
if(pHead==null)
return null;
if(pHead !=null){//设置复制链表的头部
result = resultHead = pHead.next;
pHead.next = resultHead.next;
pHead = resultHead.next;
}
while(pHead !=null){
resultHead.next = pHead.next;
resultHead = resultHead.next;
pHead.next = resultHead.next;
pHead = resultHead.next;
}
return result;
}
public RandomListNode Clone(RandomListNode pHead){
cloneNode(pHead);
randomNode(pHead);
return copyNode(pHead);
}
}
36.二叉搜索树与双向链表:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
解题思路:使用递归,分别去将当前节点的左右子树变成双向链表,然后获取左边链表的最后一个元素,当前元素的左指针指向它,它的右指针指向当前元素;右边链表的第一个元素,它的左指针指向当前元素,当前元素的右指针指向它;然后从当前元素开始,不断从左边找,找到第一个元素,返回此元素的指针。
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeNode convert(TreeNode pRootOfTree , TreeNode last) {
if(pRootOfTree==null)
return last;
TreeNode cur = pRootOfTree;
if(cur.left!=null)
last = convert(cur.left,last);
cur.left = last;
if(last!=null)
last.right = cur;
last = cur;
if(cur.right!=null)
last =convert(cur.right,last);
return last;
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return pRootOfTree;
TreeNode last = null;
last = convert(pRootOfTree,last);
while(last.left!=null)
last = last.left;
return last;
}
}