双链表类及环形链表类题目的常规思路—Leetcode-thinking_record02

目录

 

求两个链表的交点(Easy)

基本代码结构

Solve1:使用set求交集

大体思路

细节设计

代码实现

Solve2:空间复杂度O(1)

大体思路

细节设计

代码实现

双链表类题目思路总结

链表求环(Medium)

基本代码结构

Solve1:使用set求环起始节点

大体思路

细节设计

代码实现

Solve2:快慢指针赛跑

大体思路

细节设计

 代码实现

链表环类题目思路总结


求两个链表的交点(Easy)

LeetCode 160.Intersection of Two LinkedLists

已知链表A的头节点指针headA, 链表B的头节点指针headB,两个链表相交,求两个链表交点对应的节点。

 题目要求:

  • 如果两个链表没有交点,则返回NULL
  • 在求交点的过程中,不可以破坏链表的结构或者修改链表的数据域
  • 可以确保传入的链表A与链表B没有任何环
  • *实现算法尽可能使时间O(n),空间复杂度O(1)

基本代码结构

Solve1:使用set求交集

大体思路

用set集合来求交点。原理就是通过set集合来判断出两个链表之间有没有相同(重复)的元素。通过将A链表中的值,逐个插入到一个set集合(test_set)中去,然后,再遍历B数组,判断B数组中的每个节点是否在set集合(test_set)中出现过,如果出现过,就把这个重复的元素给打印出来,那么这个第一个出现的元素肯定就是交点了!

细节设计

这里需要强调的是,我们插入的是节点对应的指针(地址),而绝非之前所一般使用的值域!示意图如下,发现两个链表第一个地址相同的节点,就是我们要找的两个链表的交点:

按照这个思路,可以编写代码如下:

代码实现

LeetCode 160.Intersection of Two LinkedLists(solve1).cpp

#include <stdio.h>

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

#include <set>

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
   		std::set<ListNode*> node_set;
		while(headA){
			node_set.insert(headA);
			headA = headA->next;
        }
        while(headB){
        	if (node_set.find(headB) != node_set.end()){
	        	return headB;
	        }
	        headB = headB->next;
        }
        return NULL;
    }
};

int main(){
	ListNode a1(1);
	ListNode a2(2);
	ListNode b1(3);
	ListNode b2(4);
	ListNode b3(5);
	ListNode c1(6);
	ListNode c2(7);
	ListNode c3(8);
	a1.next = &a2;
	a2.next = &c1;
	c1.next = &c2;
	c2.next = &c3;
	b1.next = &b2;
	b2.next = &b3;
	b3.next = &c1;
	
	Solution solve;
	ListNode *result = solve.getIntersectionNode(&a1, &b1);
	printf("%d\n", result->val);
	return 0;
}

Solve2:空间复杂度O(1)

solve1的解法可以完成求两个链表交点对应的节点的要求,但在空间复杂度上无法实现精简,这就需要一个更精简的思路来应对。

大体思路

从两个链表对齐的位置节点同时开始往后遍历,什么时候两个链表的节点的值一样了,就证明这个节点是两个链表的交点了。

细节设计

 再看步骤3:

代码实现

#include <stdio.h>

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

int get_list_length(ListNode *head){
	int len = 0;
	while(head){
		len++;
		head = head->next;
	}
	return len;
}

ListNode *forward_long_list(int long_len, 
				int short_len, ListNode *head){
	int delta = long_len - short_len;
	while(head && delta){
		head = head->next;
		delta--;
	}
	return head;
}

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
   		int list_A_len = get_list_length(headA);
   		int list_B_len = get_list_length(headB);   		
   		if (list_A_len > list_B_len){
   			headA = forward_long_list(list_A_len, list_B_len, headA);
	   	}
	   	else{
	   		headB = forward_long_list(list_B_len, list_A_len, headB);
	   	}   		
        while(headA && headB){
        	if (headA == headB){
	        	return headA;
	        }
	        headA = headA->next;
	        headB = headB->next;
        }
        return NULL;
    }
};

int main(){
	ListNode a1(1);
	ListNode a2(2);
	ListNode b1(3);
	ListNode b2(4);
	ListNode b3(5);
	ListNode c1(6);
	ListNode c2(7);
	ListNode c3(8);
	a1.next = &a2;
	a2.next = &c1;
	c1.next = &c2;
	c2.next = &c3;
	b1.next = &b2;
	b2.next = &b3;
	b3.next = &c1;	
	Solution solve;
	ListNode *result = solve.getIntersectionNode(&a1, &b1);
	printf("%d\n", result->val);
	return 0;
}

双链表类题目思路总结

  • 1.通过set集合来解决两个链表之间,有重复性元素的问题,应知这种方法再时间复杂度和空间复杂度上不占优势。
  • 2.通过从“对齐”位置查找,排查到两个链表的相同元素的问题,在时间复杂度和空间复杂度上开销好,优于set集合的方式。

 

链表求环(Medium)

LeetCode 142. Linked List Cycle II

已知链表中可能存在环,若有返回环起始节点,否则返回NULL.

 

基本代码结构

Solve1:使用set求环起始节点

大体思路

遍历链表每个节点地址,把其插入到set集合中去,并且每次在将节点的地址插入setjihe 前,在set中判断一下,set集合是否已经包含了这个这个对应的地址。如果发现有,那这个节点就是其实的节点了!

细节设计

1.遍历链表,将链表中节点对应的指针(地址),插入set

2.在遍历时插入节点前,需要在set中查找,第一个在set中发现的节点地址,即是链表环的起点。

代码实现

LeetCode 142. Linked List Cycle II(solve1).cpp

#include <stdio.h>

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

#include <set>
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        std::set<ListNode *> node_set;
        while(head){
        	if (node_set.find(head) != node_set.end()){
	        	return head;
	        }
	        node_set.insert(head);
        	head = head->next;
        }
        return NULL;
    }
};

int main(){
	ListNode a(1);
	ListNode b(2);
	ListNode c(3);
	ListNode d(4);
	ListNode e(5);
	ListNode f(6);
	a.next = &b;
	b.next = &c;
	c.next = &d;
	d.next = &e;
	e.next = &f;
	//f.next = &c;
	Solution solve;
	ListNode *node = solve.detectCycle(&a);
	if (node){
		printf("%d\n", node->val);
	}
	else{
		printf("NULL\n");
	}
	return 0;
}

Solve2:快慢指针赛跑

大体思路

当"赛道"是一个环形的时候,一个快指针和一个慢指针朝一个方向"跑",总会有跑的快的超过跑的慢的一圈的时候,快慢指针和这个思想是一样的,设定一个快指针(每次遍历走两步)和一个慢指针(每次遍历走一步),那么因为是个链表环,就肯定有一个位置会让快指针和慢指针“相遇”。

细节设计

细节方面,只要注意一点,一个指针一次走两步,一个指针一次走一步,先找出相遇的节点,然后,从起点和相遇的节点同时开始以相同的步数前进,当相遇的时候,相遇的节点就是环的起点。

如果有环,快指针和慢指针相遇的时候,快指针所走过的路程,恰好是慢指针的两倍,根据这个条件列等式方程的话,可以解出来"a段=c段",这个奇妙的答案! 

 代码实现

LeetCode 142. Linked List Cycle II(solve2).cpp

#include <stdio.h>

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
    	ListNode *fast = head;
    	ListNode *slow = head;
    	ListNode *meet = NULL;
    	while(fast){
    		slow = slow->next;
    		fast = fast->next;
    		if (!fast){
		    	return NULL;
		    }
		    fast = fast->next;
		    if (fast == slow){
    			meet = fast;
    			break;
    		}
	    }
	    if (meet == NULL){
    		return NULL;
    	}
    	while(head && meet){
	    	if (head == meet){
	    		return head;
	    	}
	    	head = head->next;
	    	meet = meet->next;
	    }
        return NULL;
    }
};

int main(){
	ListNode a(1);
	ListNode b(2);
	ListNode c(3);
	ListNode d(4);
	ListNode e(5);
	ListNode f(6);
	ListNode g(7);
	a.next = &b;
	b.next = &c;
	c.next = &d;
	d.next = &e;
	e.next = &f;
	f.next = &g;
	g.next = &c;
	Solution solve;
	ListNode *node = solve.detectCycle(&a);
	if (node){
		printf("%d\n", node->val);
	}
	else{
		printf("NULL\n");
	}
	return 0;
}

链表环类题目思路总结

  • 1.依旧像双链表问题一样,用set来设计,找重复节点。
  • 2.用快慢指针的思路来进行设计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值