(二) 链表 - 经典问题汇总

(一)找到链表的倒数第n个节点

    1.这个问题不难,我们可以两次遍历链表,第一遍记录链表的长度L,第二遍从头遍历链表,找到L-n+1的位置的节点,然后输出,这样时间复杂度为o(n),空间复杂度为o(1)

    2.上面的方法看起来不错,但是我们还可以有更好的方法,我们仅仅使用一次遍历就可以,我们使用两个指针slowNode和fastNode来一起遍历,两个指针都指向链表头结点,仅当fastNode移动了n个节点的时候,slowNode才开始移动,然后两个指针同时移动直到fastNode到达链表末尾,这个时候slowNode所指向的节点就是倒数第n个节点,时间复杂度O(n),空间复杂度o(1)

public ListNode NthNodeFromEnd(ListNode headNode, int k){
        ListNode slowNode = headNode;
        ListNode fastNode = headNode;
        for(int j = 1; j<k; j++){
            //错误处理,当参数大于链表元素个数时
            if(fastNode == null||fastNode.getNext() == null){
                System.out.println("参数不正确");
                return null;
            }
            fastNode = fastNode.getNext();
        }
        while(fastNode.getNext()!=null){
            fastNode = fastNode.getNext();
            slowNode = slowNode.getNext();
        }
        return slowNode;
    }

    (二)判断链表是以NULL结尾还是形成一个环

    1.一个直接的判定方法是从第一个节点开始,令其为当前节点,看看后面是否存在节点的后继节点等于当前节点,如果存在这样的节点就说明存在环,如果不存在则对链表中的其他节点重复上述操作。但是这样不仅复杂,如果处理不好还容易出现死循环。

    2.一个简单有效的方法是由floyd提出来的,,该方法被称为floyd环判定算法,该方法使用链表中移动速度不同的两个指针,一旦他们进入环就会相遇,即表示存在环,这个判定方法的正确性在于,快速移动指针和慢速移动指针将会指向同一位置的唯一可能情况,就是整个或部分链表是一个环。时间复杂度O(n),空间复杂度O(1)

public Boolean DoesLinkedListContainsLoop(ListNode headNode){
		ListNode fastNode = headNode;
		ListNode slowNode = headNode;
		while(fastNode.getNext()!=null&&fastNode.getNext().getNext()!=null){
			slowNode = slowNode.getNext();
			fastNode = fastNode.getNext().getNext();
			if(slowNode == fastNode){
				return true;
			}
		}
		return false;
	}

(三)判定链表当中是否存在环,如果存在找到环的起始节点

    这是问题二的扩展,首先判断是否存在环,如果存在环,则在fastNode与slowNode相遇之后,初始化slowNode使其等于头结点,然后slowNode与fastNode从各自位置沿着链表移动,每次移动一个节点,他们再次相遇的位置就是环开始的位置,我们也可以用这种方法来删除环。

    假设起点到环的起点距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针移动的总距离为i,i = m + a * n + k,因为快指针移动速度为慢指针的两倍,那么快指针的移动距离为2i,2i = m + b * n + k。其中,a和b分别为慢指针和快指针在第一次相遇时转过的圈数。我们让两者相减(快减慢),那么有i = (b - a) * n。即i是圈长度的倍数。利用这个结论我们就可以理解Floyd解法为什么能确定环的起点。将一个指针移到链表起点,另一个指针不变,即距离链表起点为i处,两者同时移动,每次移动一步。当第一个指针前进了m,即到达环起点时,另一个指针距离链表起点为i + m。考虑到i为圈长度的倍数,可以理解为指针从链表起点出发,走到环起点,然后绕环转了几圈,所以第二个指针也必然在环的起点。即两者相遇点就是环的起点。

public ListNode FindBiginOfLoop(ListNode headNode){
		Boolean loopExists = false;
		ListNode fastNode = headNode;
		ListNode slowNode = headNode;
		while(fastNode.getNext()!=null&&fastNode.getNext().getNext()!=null){
			slowNode = slowNode.getNext();
			fastNode = fastNode.getNext().getNext();
			if(slowNode == fastNode){
				loopExists = true;
				break;
			}
		}
		if(loopExists){
			slowNode = headNode;
			while(true){
				if(slowNode == fastNode){
					return slowNode;
				}
				slowNode = slowNode.getNext();
				fastNode = fastNode.getNext();
			}
		}else{
			System.out.println("不存在环");
			return null;
		}
	}

(四)判断链表当中是否存在环,如果存在返回环的长度

        嗯,还是第二个问题的延伸,如果存在环的话,记录slowNode当时指向的节点,然后继续让slowNode每次移动一步,直到再次到达记录的位置就可以计算出环的长度

public int FindLoopLength(ListNode headNode){
		Boolean loopExists = false;
		ListNode fastNode = headNode;
		ListNode slowNode = headNode;
		while(fastNode.getNext()!=null && fastNode.getNext().getNext()!=null){
			slowNode = slowNode.getNext();
			fastNode = fastNode.getNext().getNext();
			if(slowNode == fastNode){
				loopExists = true;
				break;
			}
		}
		if(loopExists){
			int count = 1;
			while(slowNode.getNext()!= fastNode){
				slowNode = slowNode.getNext();
				count++;
			}
			return count;
		}else{
			return 0;
		}
	}

(五)在有序链表中插入一个新的节点

    遍历链表,找到合适的位置插入链表节点,要注意插入到链表头结点时要做一些处理

public ListNode InsertInSortedList(ListNode headNode, ListNode NewNode){
		 ListNode currentNode = headNode;
		 ListNode tempNode = null;
		 if(headNode == null) return NewNode;
		 if(currentNode.getData() >= NewNode.getData()){
			 NewNode.setNext(currentNode);
			 return NewNode;
		 }
		 while(currentNode!=null && currentNode.getData()<NewNode.getData()){
		    tempNode = currentNode;
			currentNode = currentNode.getNext();
		 }
		 NewNode.setNext(currentNode);
		 tempNode.setNext(NewNode);
		 return headNode;
	}

(六)逆置单向链表

        遍历链表,将链表方向反转即可,但是注意,原来链表头部节点,反转后要指向null;时间复杂度为o(n),空间复杂度为O(1)

public ListNode ReverseList(ListNode headNode){
		ListNode tempNode = null;
		ListNode nextNode = null;
		while(headNode!=null){
			nextNode = headNode.getNext();
			headNode.setNext(tempNode);
			tempNode = headNode;
			headNode = nextNode;
		}
		return tempNode;
	}

(七)假设两个链表在某个节点相交后成为一个单向链表,两个链表头结点是已知的,相交节点未知,求出链表相交的节点

     1. 将两个链表分别放到栈里,然后同步出栈,最后一个相等的节点就是相交节点,这个比较简单,时间复杂度为o(max(m,n))

空间复杂度为O(m+n)

    2.   可以先分别遍历原来链表,计算链表长度,求出链表长度的差d,长链表先走d步,然后长链表和短链表一起向后遍历,出现的第一个相等的字节就是链表相交的地方,时间复杂度为O(max(m,n)),空间复杂度为O(1),比前一种做法空间复杂度低

public  ListNode FindIntersectingNode(ListNode list1, ListNode list2){
		int length1 = ListLength(list1);
		int length2 = ListLength(list2);
		ListNode Node1 = null , Node2 = null;
		int diff = 0;
		if(length1>length2){
			Node1 = list1;
			Node2 = list2;
			diff = length1-length2;
		}else{
			Node1 = list2;
			Node2 = list1;
			diff = length2 - length1;
		}
		while(diff>0){
			Node1 = Node1.getNext();
			diff--;
		}
		while(Node1!=null){
			if(Node1.getData()==Node2.getData()){
				return Node1;
			}
			Node1 = Node1.getNext();
			Node2 = Node2.getNext();
		}
		return null;
	}

(八)如何找到链表的中间节点

        1.遍历一遍链表,返回链表长度n,再遍历一遍找到n/2的位置,但是这样需要遍历两遍链表

        2.使用两个指针,一个快指针一个慢指针,快指针移动的速度是慢指针移动速度的两倍,当快指针遍历结束时慢指针的位置就是链表中间位置,当节点个数为奇数时四舍五入

public ListNode FindMiddle(ListNode headNode){
		ListNode slowNode = headNode, fastNode = headNode;
		if(ListLength(headNode)==0){
			return null;
		}
		while(fastNode.getNext()!=null&&fastNode.getNext().getNext()!=null){
			slowNode = slowNode.getNext();
			fastNode = fastNode.getNext().getNext();
		}
		return slowNode;
	}

(九)如何从表尾开始打印链表

        使用递归超级方便,空间复杂度O(n),时间复杂度为O(n)

public void PrintListFromEnd(ListNode headNode){
		if(headNode == null)
			return;
		PrintListFromEnd(headNode.getNext());
		System.out.println(headNode.getData());
	}




        


猜你喜欢

转载自blog.csdn.net/zz0129/article/details/80400426