剑指offer链表的题

前情提要

刚刷完牛客网的剑指offer,写个博客对自己的一个总结。第一次刷,很多题只会暴力破解,我把做每道题的个人思路和他人思路用java进行了编写,如果在这篇文章找不到可以试试看看别的区域,因为很多的树的题都是用递归进行做的,我把其归在了树里面。希望自己的博客能对大家有帮助,准备下一个开leetcode hot100题的坑了。

删除链表中重复的结点

题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
他人思路:
自己的思路把问题想得复杂化了,自己想着用MapSet进行判断,看是否出现过,但是没有考虑到这个链表是有序的,可以直接对相邻的元素进行比较

  1. 首先添加一个头节点,以方便碰到第一个,第二个节点就相同的情况
    2.设置 pre ,last 指针, pre指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索
/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        ListNode head = new ListNode(0);
        head.next = pHead;
        ListNode pre = head;
        ListNode current = head.next;
        while(current!=null){
            if(current.next!=null && current.val == current.next.val){
                while(current.next!=null && current.val == current.next.val){
                    current = current.next;
                }
                pre.next = current.next;
                current = current.next;
            }else{
                pre = current;
                current = current.next;
            }
        }
        return head.next;
    }
}

链表中倒数第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){
            return null;
        }
        int size = 0;
        ListNode current = head;
        while(current!=null){
            size++;
            current = current.next;
        }
        if(size<k){
            return null;
        }
        int i = 1;
        k = size-k+1;
        current = head;
        while(i<=size){
            if(i==k){
                return current;
            }
            i++;
            current = current.next;
        }
        return current;
    }
}

时间复杂度:O(2n) = O(n)
空间复杂度:O(1)
总结:
循环了两次,运行时间较多

他人思路:

/*
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;
        }
        //设置两个指针,p2指针先走(k-1)步,然后再一起走,
        //当p2为最后一个时,p1就为倒数第k个数
        ListNode current1 = head;
        ListNode current2 = head;
        //p2先走,走k-1步,如果k大于链表长度则返回 空,否则的话继续走
        while(k > 1){
            if(current1.next==null){
                return null;
            }else{
                current1 = current1.next;
                k--;
            }
        }
        //两个指针一起 走,一直到p2为最后一个,p1即为所求
        while(current1.next!=null){
            current1 = current1.next;
            current2 = current2.next;
        }
        return current2;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)

复杂链表的复制

题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

个人思路:

public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead==null){
            return null;
        }
        RandomListNode copyHead = new RandomListNode(pHead.label);
        RandomListNode current = pHead;
        RandomListNode copyCurrent = copyHead;
        while(current!=null){
            copyCurrent.next = new RandomListNode(current.next.label);
            copyCurrent.random = new RandomListNode(current.random.label);
            current = current.next;
            copyCurrent = copyCurrent.next;
        }
        return copyHead;
    }
}

总结:
currentNode.radom.next就是新的随机节点啊,如果是currentNode.random的话还是原来的链表里面的元素,虽然他们俩label一样…不知道我说的你听懂没
画图你就会清晰一些了,currentNode.random.next你可以拆分成currentNode.random和next,那这个currentNode.random就是原链表currentNode节点的任意指针指向的节点,然后我们在构建新链表的时候,每个新链表节点都是接在原链表节点的后面,那么新链表的对应节点就应该接在原链表的currentNode.random节点后面,也就是next指向的节点了..没法发图,你可以尝试理解一下

他人思路:
在这里插入图片描述

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
/*
*解题思路:
*1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
*2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
*3、拆分链表,将链表拆分为原链表和复制后的链表
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead == null) {
            return null;
        }
         
        RandomListNode currentNode = pHead;
        //1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
        while(currentNode != null){
            RandomListNode cloneNode = new RandomListNode(currentNode.label);
            RandomListNode nextNode = currentNode.next;
            currentNode.next = cloneNode;
            cloneNode.next = nextNode;
            currentNode = nextNode;
        }
         
        currentNode = pHead;
        //2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
        while(currentNode != null) {
            currentNode.next.random = currentNode.random==null?null:currentNode.random.next;
            currentNode = currentNode.next.next;
        }
         
        //3、拆分链表,将链表拆分为原链表和复制后的链表
        currentNode = pHead;
        RandomListNode pCloneHead = pHead.next;
        while(currentNode != null) {
            RandomListNode cloneNode = currentNode.next;
            currentNode.next = cloneNode.next;
            cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
            currentNode = currentNode.next;
        }
         
        return pCloneHead;
    }
}

从头到尾打印链表

题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

个人思路:
多借助一个list

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        
        ArrayList<Integer> list = new ArrayList<Integer>();
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        ListNode current = listNode;
        while(current!=null){
            list.add(current.val);
            current = current.next;
        }
        for(int i=list.size()-1;i>=0;i--){
            list2.add(list.get(i));
        }
        return list2;
    }
}

时间复杂度:O(2n) 运行时间:18ms
空间复杂度:O(2n) 占用内存:9412k
总结:
多用了一个list,浪费了空间

个人思路:
不借助第二个list

import java.util.ArrayList;
class ListNode {
    int val;
    ListNode next = null;

   ListNode(int val) {
       this.val = val;
    }
}

public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        
        ArrayList<Integer> list = new ArrayList<Integer>();
        ListNode current = listNode;
        while(current!=null){
            list.add(current.val);
            current = current.next;
        }
        for(int i=0;i<(list.size()+1)/2;i++){
            int temp = list.get(i);
            list.set(i,list.get((list.size()-i-1)));
            list.set(list.size()-i-1,temp);
        }
        return list;
    }
}      

时间复杂度:O(n+n/2) = O(n) 运行时间:17ms
空间复杂度:O(n) 占用内存:9560k

合并两个排序的链表

题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

个人思路:
将list1链表插入list2中,四种情况讨论

class ListNode {
    int val;
    ListNode next = null;
    ListNode(){
    	
    }
    ListNode(int val) {
        this.val = val;
    }
}
public class Solution {
	
	public static void main(String[] args) {
		Solution s = new Solution();
		ListNode node1 = new ListNode(1);
		ListNode node2 = new ListNode(2);
		ListNode node3 = new ListNode(3);
		ListNode node4 = new ListNode(4);
		ListNode node5 = new ListNode(5);
		ListNode node6 = new ListNode(6);
		ListNode node7 = new ListNode();
		node1.next = node3;
		node3.next = node5;
		node2.next = node4;
		node4.next = node6;
		ListNode head = s.Merge(node1, node2);
	}
    public ListNode Merge(ListNode list1,ListNode list2) {
    	
    	if(list1==null){
    		return list2;
    	}
    	
    	if(list2==null){
    		return list1;
    	}
    	
        ListNode current1 = list1;
        ListNode after = list1.next;
        ListNode current2 = list2;
        ListNode pre = null;
        ListNode head = list2;
        while(current1!=null){
            while(current2!=null){
                if(current1.val <= current2.val && pre==null){
                    pre = current1;
                    current1.next = current2;
                    head = pre;
                    break;
                }else if(current1.val <= current2.val && pre!=null){
                    pre.next = current1;
                    current1.next = current2;
                    break;
                }else if(current1.val > current2.val && current2.next!=null){
                    pre = current2;
                    current2 = current2.next;
                }else if(current1.val > current2.val && current2.next==null){
                    current2.next = current1;
                    break;
                }
            }
            if(after!=null){
            	current1 = after;
                after = current1.next;
            }else{
            	break;
            }       
        }
        return head;
    }
}

总结:
只通过了一部分代码{135}{246}着个例子通过了,但是{135}{}这个例子直接报死循环或者循环时间过多,不知道因为什么

他人思路:
递归版本:

/*
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;
        }
        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;
        }
    }
}

非递归版本:

/*
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;
        }
        if(list2==null){
            return list1;
        }
        ListNode current = null;
        ListNode head = null;
        while(list1!=null && list2!=null){
            if(list1.val < list2.val){
                if(head==null){
                    head = list1;
                    current = list1;
                }else{
                    current.next = list1;
                    current = list1;
                }
                list1 = list1.next;
            }else{
                if(head==null){
                    current = list2;
                    head = list2;
                }else{
                    current.next = list2;
                    current = list2;
                }
                list2 = list2.next;
            }
        }
        if(list1==null){
            current.next = list2;
        }else{
            current.next = list1;
        }
        return head;
    }
}

反转链表

题目描述
输入一个链表,反转链表后,输出新链表的表头。

他人想法:
head为当前节点,如果当前节点为空的话,那就什么也不做,直接返回null;
当前节点是head,pre为当前节点的前一节点,next为当前节点的下一节点
需要pre和next的目的是让当前节点从pre->head->next1->next2变成pre<-head
即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了
所以需要用到pre和next两个节点
做循环,如果当前节点不为空的话,始终执行此循环,此循环的目的就是让当前节点从指向next到指向pre
如此就可以做到反转链表的效果
先用next保存head的下一个节点的信息,保证单链表不会因为失去head节点的原next节点而就此断裂
如果head为null的时候,pre就为最后一个节点了,但是链表已经反转完毕,pre就是反转后链表的第一个节点
直接输出pre就是我们想要得到的反转后的链表

public class Solution {
    public ListNode ReverseList(ListNode head) {
       
        if(head==null)
            return null;
            
        ListNode pre = null;
        ListNode next = null;
        while(head!=null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
}

链表中环的入口

题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

个人思路:
用一个set存储结点,如果出现重复的则返回这个值,如果没出现则返回null

import java.util.*;
/*
 public class ListNode {
    int val;
    ListNode next = null;

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

    public ListNode EntryNodeOfLoop(ListNode pHead){
        ListNode current = pHead;
        Set<ListNode> set = new HashSet<ListNode>();
        while(current!=null){
            if(set.contains(current)){
                return current;
            }
            set.add(current);
            current = current.next;
        }
        return null;
    }
}

时间复杂度:O(n)
空间复杂度:O(n)

他人思路:
第一步,找环中相汇点。分别用p1,p2指向链表头部,p1每次走一步,p2每次走二步,直到p1p2找到在环中的相汇点。
第二步,找环的入口。接上步,当p1
p2时,p2所经过节点数为2x,p1所经过节点数为x,设环中有n个节点,p2比p1多走一圈有2x=n+x; n=x;可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2每次走一步直到p1==p2; 此时p1指向环的入口。

import java.util.*;
/*
 public class ListNode {
    int val;
    ListNode next = null;

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

    public ListNode EntryNodeOfLoop(ListNode pHead){
        if(pHead==null || pHead.next==null){
            return null;
        }
        ListNode p1 = pHead;
        ListNode p2 = pHead;
        while(p2!=null && p2.next!=null){
            p1 = p1.next;
            p2 = p2.next.next;
            if(p1==p2){
                p2 = pHead;
                while(p1!=p2){
                    p1 = p1.next;
                    p2 = p2.next;
                }
                if(p1==p2){
                    return p1;
                }
            }
        }
        return null;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)

调整数组顺序使奇数位于偶数前面

题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

个人思路:
从数组中找到奇数和偶数,存到两个单链表中,然后讲偶数的单链表加到奇数的单链表后

class ArrayNode{
    int num;
    ArrayNode next = null;
    public ArrayNode(){}
    public ArrayNode(int num){
        this.num = num;
    }
}
public class Solution {
    public void reOrderArray(int [] array) {
    	
    	  ArrayNode head1 = new ArrayNode();
          ArrayNode tail1 = new ArrayNode();
          ArrayNode head2 = new ArrayNode();
          ArrayNode tail2 = new ArrayNode();
          tail1 = head1;
          tail2 = head2;
          for(int i=0;i<array.length;i++){
              if(array[i]%2==1){
                  ArrayNode node = new ArrayNode(array[i]);
                  tail1.next = node;
                  tail1 = node;
              }
              if(array[i]%2==0){
                  ArrayNode node = new ArrayNode(array[i]);
                  tail2.next = node;
                  tail2 = node;
              }
          }         
          tail1.next = head2.next;
          ArrayNode current = head1.next;
          int i = 0;
          while(current != null){
              array[i++] = current.num;
              current = current.next;
          }
    }
}

时间复杂度:O(n)
空间复杂度:O(n)
总结:
这是用空间换取时间
这里的链表也可以用数组进行替代

圆圈中最后剩下的数

题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1

个人思路:
用循环单链表实现,每次到该删除的时候就进行删除

class Child{
    int index;
    Child next = null;
    public Child(int index){
        this.index = index;
    }
}
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n==0){
            return -1;
        }
        Child child0 = new Child(0);
        Child current = child0;
        for(int i=1;i<n;i++){
            Child child = new Child(i);
            current.next = child;
            current = child;
        }
        current.next = child0;
        int count = -1;
        while(current.next != current){
            if((count+1) == (m-1)){
                current.next = current.next.next;
                count = -1;
            }else{
                current = current.next;
                count++;
            }
         }
        return current.index;
     }
}

时间复杂度O(m*n)
时间复杂度:O(n)
总结:
总体来说比下面的运行时间要低一些,因为没有用的数都被删掉了用数组实现,会额外判断一下不存在的数字

他人思路:
用数组模拟环实现

public class Solution {
   public static int LastRemaining_Solution(int n, int m) {
        if(n==0){
            return -1;
        }
        int[] a = new int[n];
        for(int i=0;i<n;i++){
            a[i] = i;
        }
        int count = 0;//计算走的步数,用来模拟循环
        int time = -1;//计算小朋友查的数字
        int i = 0;
        
        while(n > 0){
            if(a[i] != -1 && n==1){ //找到了数组中仅剩的那个
                break;
            }
            if(a[i] != -1){
                time++;
                count++;
            }
            if(time == m-1){
                a[i] = -1;
                time = -1;
                count--;
                n--;
            }
            if(count == n){
                count = 0;
                i = -1;
            }
            i++;
        }
        return a[i];
     }
}

时间复杂度:O(m*n)
空间复杂度:O(n)
总结:
时间复杂度太高了

他人思路:
在这n个数字中,第一个被删除的数字是(m-1)%n
接下来我们把剩下的的这n-1个数字的序列k+1,…,n-1,0,…k-1作一个映射,映射的结果是形成一个从0到n-2的序列:

k+1 -> 0
k+2 -> 1

n-1 -> n-k-2
0 -> n-k-1

k-1 -> n-2

class Child{
    int index;
    Child next = null;
    public Child(int index){
        this.index = index;
    }
}
 
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n==0){
            return -1;
        }
        Child child0 = new Child(0);
        Child current = child0;
        for(int i=1;i<n;i++){
            Child child = new Child(i);
            current.next = child;
            current = child;
        }
        current.next = child0;
        int count = -1;
        
        while(current.next != current){
            int deleteIndex = (m-1)%n;
            current = deleteChild(current,deleteIndex,n);
            n--;
         }
        return current.index;
     }
     
    public Child deleteChild(Child current,int deleteIndex,int length){
        for(int i=0;i<length;i++){
            if(i == deleteIndex){
                current.next = current.next.next;
                break;
            }else{
                current = current.next;
            }
        }
        return current;
    }
}

时间复杂度O(n)
空间复杂度O(n)
总结:
因为还有删除,所以我个人认为不是O(n),但是小于O(n*m)

两个链表的第一个公共节点

题目描述
输入两个链表,找出它们的第一个公共结点。

**个人思路:**因为刚开始做,没有理解到结点是指整个ListNode都是一样得,拥有得是同一个地址,而不是简单得数字一样,所以说找到公共结点,指的是这个结点后面的结点全部相同。

他人思路:
找出2个链表的长度,然后让长的先走两个链表的长度差,然后再一起走
(因为2个链表用公共的尾部)

/*
public class ListNode {
    int val;
    ListNode next = null;
 
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
         
        int length1 = findLength(pHead1);
        int length2 = findLength(pHead2);
         
        if(length1 > length2){
            pHead1 = stepListNode(pHead1,length1-length2);
        }else{
            pHead2 = stepListNode(pHead2,length2-length1);
        }
         
        while(pHead1 != null){
            if(pHead1 == pHead2){
                return pHead1;
            }
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        return null;
    }
     
    public int findLength(ListNode pHead){
        if(pHead == null){
            return 0;
        }
        int sum = 1;
        while(pHead.next != null){
            pHead = pHead.next;
            sum++;
        }
        return sum;
    }
     
    public ListNode stepListNode(ListNode pHead,int step){
        for(int i=0;i<step;i++){
            pHead = pHead.next;
        }
        return pHead;
    }
}

时间复杂度:O(n)
空间复杂度:O(n)

他人思路:
长度相同有公共结点,第一次就遍历到;没有公共结点,走到尾部NULL相遇,返回NULL
长度不同有公共结点,第一遍差值就出来了,第二遍一起到公共结点;没有公共,一起到结尾NULL。

/*
public class ListNode {
    int val;
    ListNode next = null;
 
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
         
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
         
        while(p1 != p2){
            p1 = (p1==null ? pHead2 : p1.next);
            p2 = (p2==null ? pHead1 : p2.next);
        }
        return p1;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)
总结:
如果两个链表的长度相同并且没有相同的结点,这是死循环?这个说法是错误得;因为是相同长度,走到最后,相同得一定还是null

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值