剑指offer :链表类题目汇总

         涉及到链表的增删问题,需要考虑增删的位置(前中尾)、增删后保证链表不断裂。下面对剑指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;
    }
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值