leetcode-链表

leetcode-链表



前言

leetcode中的链表专题中的部分题目,这里记录了自己的解题思路。


一、160-相交链表

思路1:哈希表法,将headA中的所有节点都放入哈希表中,然后遍历headB。如果map[q]=1,就代表在哈希表存在该节点,就返回该节点。否则就返回nullptr。
C++代码:

 struct ListNode {
      int val;
      ListNode *next;
      ListNode(int x) : val(x), next(NULL) {}
  };
class Solution {
public:
	ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
		unordered_map<ListNode*,int>map;
		ListNode*p = headA;
		while (p) {
			map[p]++;//将headA中的元素放入哈希表中
			p = p->next;
		}
		ListNode*q = headB;
		while (q) {//遍历headB
			if (map[q] == 1) {
				return q;
			}
			q = q->next;
		}
		return nullptr;
	}
};

思路2:双指针法,遍历headA与headB指向的链表。当p节点为空时就指向headB,当q节点为空时就指向headA,这样遍历一次p与q一定会相等。相等有两种情况,要么没有交点,都为nullptr;要么在交点处相等。

 class Solution {
 public:
	 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
		 ListNode*p = headA;
		 ListNode*q = headB;
		 while (p != q) {
			 p = p == nullptr ? headB:p->next;
			 q = q == nullptr ? headA : q->next;
		 }
		 return p;
	 }
 };

二、206-反转链表

思路1:遍历。通过遍历反转链表,就需要知道每个遍历节点的前一个节点和后一个节点。

C++代码如下:

class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		ListNode*pre = nullptr;//前向节点
		ListNode*next = nullptr;//后向节点
		ListNode*cur = head;//当前节点
		while (cur) {
			next = cur->next;//记录当前节点的后一个节点
			cur->next = pre;//反向
			pre = cur;//更新前向节点
			cur = next;//更新当前节点
		}
		return pre;//最后的pre指向的就是反向链表
	}
};

思路2:递归。递归需要弄清楚三个点:1、递归体(每一次递归需要具体执行的内容)2、递归结束条件3、递归参数。对于该题,递归体是执行到当前节点,当前节点就反向,结束条件是当前节点或者下一个节点为空,递归参数就是当前节点的下一个节点。

C++代码:

class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		//当前节点或者下一节点为空时,结束循环
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		//当前节点不需要关注后面节点的情况,所以循环参数是head->next,
		//因为是反向,所以返回的是新的头节点
		ListNode *newHead = reverseList(head->next);
		head->next->next = head;//下一个节点的next指向当前接待你,这样就完成了当前节点的反向
		head->next = nullptr;//当前节点的next要指向nullptr,避免出现环
		return newHead;
	}
};

三、21-合并两个有序链表

思路1:递归。利用哑指针dummy,返回dummy->next。

C++代码:

class Solution {
public:
	ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
		ListNode*dummy = new ListNode(-1);
		ListNode*p = list1;
		ListNode*q = list2;
		ListNode*temp = dummy;
		while (p&&q) {//循环结束条件是有一个链表已经被遍历完
			if (p->val <= q->val) {
				temp->next = p;
				p = p->next;
			}
			else {
				temp->next = q;
				q = q->next;
			}

			temp = temp->next;
		}
		temp->next = p == nullptr ? q : p;//返回不为空的链表
		return dummy->next;
	}
};

思路2:递归。

C++代码:

//递归
class Solution {
public:
	ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
		if (list1 == nullptr) {//递归结束条件
			return list2;
		}
		if (list2 == nullptr) {//递归结束条件
			return list1;
		}
		if (list1->val < list2->val) {//递归体
			ListNode*list = list1;//选择值较小的作为当前节点
			list->next = mergeTwoLists(list1->next, list2);//再比较下一个节点
			return list;//返回当前节点
		}
		else {
			ListNode*list = list2;
			list->next = mergeTwoLists(list1, list2->next);
			return list;
		}
	}
};

四、83-删除排序链表中的重复元素

思路:遍历

C++代码:

//遍历
class Solution {
public:
	ListNode* deleteDuplicates(ListNode* head) {
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		ListNode*p = head;
		while (p&&p->next) {
			//这里一定要将p->next!=nullptr放在前面
			while (p->next!=nullptr&&p->val == p->next->val) {
				
				p->next = p->next->next;
			}
			p = p->next;
			
		}
		return head;
	}
};

五、19-删除链表的倒数第N个节点

思路1:双指针法。定义一个快指针,和一个慢指针(哑指针,预防删除第一个节点的情况)。先让快指针移动n-1步,让后让快指针和慢指针一起移动直到快指针移动到表尾,这样慢指针指向的就是要删除节点的前一个节点。

C++代码:

class Solution {
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		ListNode*fast = head;
		ListNode*slow = new ListNode(-1, head);//慢指针
		ListNode*p = slow;
		for (int i = 1; i < n; ++i) {//让快指针先移动n-1次
			fast = fast->next;
		}
		while (fast->next) {//快指针移动到链表尾,慢指针就是倒数第n个节点
			fast = fast->next;
			p = p->next;
		}
		p->next = p->next->next;
		ListNode*ans = slow->next;
		delete slow;
		return ans;
	}
};

思路2:栈。先定义一个哑指针,下一个节点指向头节点(预防删除头节点的情况)。然后将哑节点放入栈中,在出栈n次,目前的栈顶元素就是要删除节点的前一个节点。

C++代码:

class Solution {
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		stack<ListNode*>stk;
		ListNode*dummy = new ListNode(-1, head);
		ListNode*p = dummy;
		while (p) {//入栈
			stk.push(p);
			p = p->next;
		}
		for (int i = 0; i < n; ++i) {
			stk.pop();
		}
		//注意栈中的每一个节点都保持与原链表的关系,主要依据是对应地址上的内容是否放生改变
		ListNode*pre = stk.top();//获取要删除节点的前一个节点
		pre->next = pre->next->next;
		ListNode*ans = dummy->next;
		delete dummy;
		return ans;
	}
};

六、24-交换链表中的相邻节点

思路1:递归。递归结束条件是当head或者head->next=nullptr;递归体是改变两个节点的先后顺序。递归参数是原本的第二个节点的下一个节点,也是newHead的下一个节点。

C++代码:

class Solution {
public:
	ListNode* swapPairs(ListNode* head) {
		//递归结束条件
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		//递归体
		ListNode*newHead = head->next;
		head->next = swapPairs(newHead->next);
		newHead->next = head;
		return newHead;
	}
};

思路2:遍历

C++代码:

class Solution {
public:
	ListNode* swapPairs(ListNode* head) {
		ListNode*dummy = new ListNode(-1, head);//定义哑指针
		ListNode*temp = dummy;
		while (temp->next&&temp->next->next) {
			//每一次处理两个节点
			ListNode*p = temp->next;
			ListNode*q = temp->next->next;
			temp->next = q;//取出第一个节点(原本是第二个节点)
			p->next = q->next;//(第一个节点的后面指向改为原本第二个节点后面的指向)
			q->next = p;//(原本的第二个节点,现在的第一个几点的后面一个节点就是原本的第一个节点)
			temp = p;//将temp移动到第二个节点
		}
		return dummy->next;
	}
};

七、445-两数相加Ⅱ

思路1:反转链表加求出链表长度。

C++代码:

 //三次反转链表
class Solution {
public:
	ListNode*reverseList(ListNode*head) {
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		ListNode*newHead = reverseList(head->next);
		head->next->next = head;
		head->next = nullptr;
		return newHead;
	}
	int getLength(ListNode*head) {
		ListNode*l = head;
		int len = 0;
		while (l) {
			len++;
			l = l->next;
		}
		return len;
	}
	ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
		l1 = reverseList(l1);
		l2 = reverseList(l2);
		int len1 = getLength(l1);
		int len2 = getLength(l2);
		ListNode*list;
		if (len1 > len2) {
			list = l1;
		}
		else {
			list = l2;
		}
		ListNode*temp = list;
		while (l1&&l2) {
			temp->val = l1->val + l2->val;
			temp = temp->next;
			l1 = l1->next;
			l2 = l2->next;
		}
		ListNode*temp1 = list;
		int flag = 0;
		while (temp1) {
			if (flag == 1) {
				temp1->val++;
				flag = 0;
			}
			if (temp1->val >= 10) {
				flag = 1;
				temp1->val %= 10;
			}
			temp1 = temp1->next;
		}
		if (flag == 1) {
			temp = list;
			while (temp->next) {
				temp = temp->next;
			}
			temp->next = new ListNode(1, nullptr);
		}
		return reverseList(list);

	}
};

八、234-回文链表

思路1:将链表中的数据放入数组中。通过数组可以很方便的访问对应下标中的数据,同时数组的大小可以直接获取。只是这种方法的空间开销比较大。
C++代码:

class Solution {
public:
	bool isPalindrome(ListNode* head) {
		vector<int>vec;
		ListNode*p = head;
		while (p) {
			vec.push_back(p->val);
			p = p->next;
		}
		for (int i = 0, j = vec.size() - 1; i < j; ++i, --j) {
			if (vec[i] != vec[j]) {
				return false;
			}
		}
		return true;
	}
};

思路2:快慢指针。快指针每次走两步,慢指针每次走一步。这样就很容易获取链表的前半部分和后半部分。然后反转后半部分,将反转的后半部分与前面的进行比较。
C++代码:

class Solution {
public:
	ListNode*reverseList(ListNode*head) {
		if (head == nullptr || head->next == nullptr) {
			return head;
		}

		ListNode*newHead = reverseList(head->next);
		head->next->next = head;
		head->next = nullptr;
		return newHead;
	}
	ListNode*getFirstHalf(ListNode*head) {
		//定义快慢指针
		ListNode*fast = head;//快指针
		ListNode*slow = head;//慢指针

		while (fast->next&&fast->next->next) {
			fast = fast->next->next;
			slow = slow->next;
		}
		return slow;
	}

	bool isPalindrome(ListNode* head) {
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		
		ListNode*firstHalf = getFirstHalf(head);
		ListNode*secondeHalf = reverseList(firstHalf->next);

		//比较
		bool flag = true;
		ListNode*p = head;
		while (secondeHalf&&flag) {
			if (p->val != secondeHalf->val) {
				flag = false;
			}
			secondeHalf = secondeHalf->next;
			p = p->next;
		}
		firstHalf->next = reverseList(firstHalf->next);
		return flag;
	}
};

九、725-分割链表

思路:求出链表的长度,平均间隔,余数。
C++代码:

class Solution {
public:

	int getLength(ListNode*head) {
		ListNode*p = head;
		int len = 0;
		while (p) {
			len++;
			p = p->next;
		}
		return len;
	}
	vector<ListNode*> splitListToParts(ListNode* head, int k) {
		int len = getLength(head);//获取链表长度
		int gap = len / k;//平局间隔
		int remainder = len % k;//余数

		ListNode*p = head;
		vector<ListNode*>ret(k,nullptr);
		for (int i = 0; i < k&&p!=nullptr; ++i) {
			ret[i] = p;
			int l = gap + (remainder > i ? 1 : 0);//余数是否用完
			for (int j = 1; j < l; ++j) {
				p = p->next;
			}
			ListNode*next = p->next;//记录下下一个节点
			p->next = nullptr;
			p = next;
		}
		return ret;
	}
};

十、328-奇偶链表

思路:定义奇偶链表,然后将二者连接起来。
C++代码:

class Solution {
public:
	ListNode* oddEvenList(ListNode* head) {
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		ListNode*p = head;//定义奇指针链表
		ListNode*q = head->next;//定义偶指针链表

		ListNode*l1 = p;
		ListNode*l2 = q;

		while (l2&&l2->next) {
			l1->next = l2->next;//奇指针的下一个节点指向偶指针的下一个节点
			l2->next = l2->next->next;//偶指针的下一个节点指向下下一个节点
			l1 = l1->next;//移动奇节点
			l2 = l2->next;//移动偶节点
		}
		l1->next = q;//奇节点链表的尾部指向偶节点的头部,即连接奇偶链表
		return p;
	}
};

总结

链表的特性是不能直接通过下标访问某个节点,所有遍历在链表中十分常见。与遍历十分相关的就是递归,发现一个问题可以用递归解决时,考虑递归的三要素。如果考虑链表倒序的问题,可以利用双指针,或者栈的思想。

引用

leetcode刷题指南

leetcode官网

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值