关于链表的一些算法

链表的基本性质:

链表和数组一样都是一种线性结构
①数组是一段连续的存储空间;
②链表空间不一定保证连续,为临时分配的。

链表的分类:
1.按连接方向分类:
①单链表
②双链表
2.按照有环无环分类
①普通链表
②循环链表:最后一个节点next指针指向第一个节点

链表问题代码实现的关键点:
1.链表调整函数的返回值类型,根据要求往往是节点类型;
2.处理链表过程中,先采用画图的方式理清逻辑;
3.链表问题对于边界条件讨论要求严格。

关于链表插入删除的注意事项:
1.特殊处理链表为空,或者链表长度为1的情况;
2.注意插入操作的调整过程:
同时找到插入位置之前的节点和之后的节点,然后把前一个节点的next的指针指向新插入的节点,最后把新插入节点的next指针指向后一个节点;
3.注意删除操作的调整过程:
找到删除节点的前一个节点,然后指向删除节点的后一个节点。
注意点:头尾节点及空节点需要特殊考虑,双链表的插入、删除和单链表类似,但是需要额外考虑previous指针的指向。

单链表的翻转操作:
1.当链表为空或者长度为1时,特殊处理;
2.对于一般情况:头节点head,当前结点now如:head->now->----*–>null
①将now结点的next指针指向head;
②将now设置为翻转部分的新头部;
③步骤①之前提前先用一个变量记录下now节点下一个节点是什么,然后继续步骤①步骤②。

大量链表问题可以使用额外数据结构来简化调整过程。
但链表问题最优解往往是不使用额外数据结构的方法。

例1:

给定一个整数num,如何在节点值有序的环形链表中插入一个节点值为num的节点,并且保证环形链表依然有序。

解:
将num定义为新节点node,如果链表为空,nodenext指向自己形成环形链表,返回node即可;
如果链表不为空,令变量p为头节点,变量c为第二个节点,然后p和c同步移动下去;如果此时p.val<=node.val并且c.val>=node.val,则将node插入到p和c之间,返回head即可;
如果p和c转了一圈都没有发现应该插入的位置,此时node应该插入到头节点的前面(最大或者最小)。如果是最小,应该返回node,node为新头部,这样才是有序的。

代码:

	public ListNode insertNum(ListNode head, int num) {
		ListNode node = new ListNode(num);
		if (head == null) {
			node.next = node;
			return node;
		}
		ListNode pre = head;
		ListNode cur = head.next;
		while (cur != head) {
			if (pre.val <= num && num <= cur.val) {
				break;
			}
			pre = cur;
			cur = cur.next;
		}
		pre.next = node;
		node.next = cur;
		return head.val < num ? head : node;
	}

例2:

给定一个单链表的节点node,但不给定整个链表的头节点。如何在链表删除node?要求时间复杂度为O(1)。

解:
将node的值变为node下一个节点的值,然后删除node下一个节点。

代码:

	public boolean removeNode(ListNode node) {
		if (node == null) {
			return false;
		}
		ListNode next = node.next;
		if (next == null) {
			return false;
		}
		node.val = next.val;
		node.next = next.next;
		return true;
	}

例3:

给定一个链表的头节点head,再给定一个数num,请把链表调整成值小于num的节点都放在链表的左边,值等于num的节点放在链表的中间,值大于num的节点,放在链表的右边。

解:
简单做法:
1.将链表的所有节点放入到数组中,然后将数组进行快排划分的调整过程。
2.然后将数组中的节点重新串联。
最优解为:
将链表分为三个小链表,分别为值小于、大于、等于num的三个链表,然后重新连接起来。

代码:

	public ListNode listDivide(ListNode head, int pivot) {
		ListNode sh = null;// small head
		ListNode st = null;
		ListNode bh = null;// big head
		ListNode bt = null;
		ListNode next = null;

		while (head != null) {
			next = head.next;//存储 next node
			head.next = null;
			if (head.val <= pivot) {
				if (sh == null) {
					sh = head;
					st = head;
				} else {
					st.next = head;
					st = head;
				}
			} else {
				if (bh == null) {
					bh = head;
					bt = head;
				} else {
					bt.next = head;
					bt = head;
				}
			}
			head = next;
		}
		if (st != null) {
			st.next = bh;
		}
		return sh != null ? sh : bh;
	}

例4:

给定两个有序链表的头节点head1和head2,打印两个链表的公共部分。

解:
如果两个链表有任何一种为空,直接返回即可;
否则,从两个链表的头节点开始比较,较小的节点往下移动,相等时,两个链表的节点都下移,当有任何一个链表为空,停止。

代码:

	public int[] findCOmmonParts(ListNode head1, ListNode head2) {
		LinkedList<Integer> list = new LinkedList<Integer>();
		while (head1 != null && head2 != null) {
			if (head1.val < head2.val) {
				head1 = head1.next;
			} else if (head1.val > head2.val) {
				head2 = head2.next;
			} else {
				list.add(head1.val);
				head1 = head1.next;
				head2 = head2.next;
			}
		}
		int[] res = new int[list.size()];
		int index = 0;
		while (!list.isEmpty()) {
			res[index++] = list.pollFirst();
		}
		return res;
	}

例5:

给定一个单链表的头节点head,实现一个调整单链表的函数,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整。
例如链表:
1->2->3->4->5->6->7->8
调整后为:
3->2->1->6->5->4->7->8

解:
方法一:时间复杂度为O(n),额外空间复杂度为O(k)。
利用栈,元素依次进栈,凑齐k个元素,依次出栈,第一组要特殊处理,需要返回的是节点3而不是几点1。
方法二:时间复杂度为O(n),额外空间复杂度为O(1)。
省去栈,依然是每收集k个元素就做逆序调整,记录下每一组的第一个节点,然后往下,当遍历到k个节点时逆序,然后将上一组调整好的尾结点与这一组的头节点相连。

代码:

	public ListNode inverse(ListNode head, int K) {
		if (K < 2) {
			return head;
		}
		ListNode cur = head;
		ListNode start = null;
		ListNode pre = null;
		ListNode next = null;
		int count = 1;
		while (cur != null) {
			next = cur.next;
			if (count == K) {
				start = pre == null ? head : pre.next;
				head = pre == null ? cur : head;
				resign(pre, start, cur, next);
				pre = start;
				count = 0;
			}
			count++;
			cur = next;
		}
		return head;
	}

	private void resign(ListNode left, ListNode start, ListNode end, ListNode right) {
		// TODO 自动生成的方法存根
		ListNode pre = start;
		ListNode cur = start.next;
		ListNode next = null;
		while (cur != right) {
			next = cur.next;
			cur.next = pre;
			pre = cur;
			cur = next;
		}
		if (left != null) {
			left.next = end;
		}
		start.next = right;
	}

例6:

给定一个单链表的头节点head,再给定一个值val,把所有等于val的节点删除。

解:
将整个过程看成构造链表的过程,假设之前构造的链表头节点是head,尾节点是tail,如果当前节点now值为val就直接抛弃,否则就把该节点接到之前链表的末尾,此时需要改变末尾的next的指针,指向null节点,并且将null节点作为新的尾结点,特殊情况起初head和tail都为null,所以第一个接上的节点既是head也是tail,并且接入第一个的时候,tail是没有next指针的。

代码:

	public ListNode clear(ListNode head, int num) {
		while (head != null) {
			if (head.val != num) {
				break;
			}
			head = head.next;
		}
		ListNode pre = head;
		ListNode cur = head;
		while (cur != null) {
			if (cur.val == num) {
				pre.next = cur.next;
			} else {
				pre = cur;
			}
			cur = cur.next;
		}
		return head;
	}

例7:

判断链表是否为回文结构
例如:
1->2->3->2->1,是回文结构,返回true,
1->2->3->1, 不是回文结构,返回false。

解:
方法一:时间复杂度O(n),使用n个额外空间;
将链表元素依次放入栈中,然后依次弹出,比对弹出元素是否与链表对应位置的元素值一样,如果每一步都相等说明是回文结构。

方法二:时间复杂度O(n),使用N/2个额外空间;
依然申请一个栈,定义两个指针(快慢指针),快指针一次走两步,慢指针一次走一步,慢指针遍历时将遍历过的节点压入栈,当快指针走完的时候,慢指针到了中间的位置,此时栈中的元素其实是链表左部分的逆序,如果链表长度为奇数,就不把中间节点压入栈中,接下来慢指针继续便利,栈开始弹出元素,对比栈弹出元素是否与遍历元素相等,如果每一步都相等则就是回文结构,否则就不是。

方法三:时间复杂度O(n),额外空间复杂度为O(1)。
前面过程与方法二一样找到中间节点,然后把链表右半边逆序调整,接下类从链表的两头开始依次对比节点值是否一样,如果到中间位置都一样,说明链表是回文结构。返回之前,将右半部分调整回来。

代码:

	public boolean isPalindrome(ListNode head) {
		if (head == null || head.next == null) {
			return true;
		}
		ListNode n1 = head;
		ListNode n2 = head;
		while (n2.next != null && n2.next.next != null) {
			n1 = n1.next;//n1->mid
			n2 = n2.next.next;//n2->end
		}
		n2 = n1.next;//n2->右部分
		n1.next = null;
		ListNode n3 = null;
		while (n2 != null) {
			n3 = n2.next;
			n2.next = n1;
			n1 = n2;
			n2 = n3;
		}
		n3 = n1;
		n2 = head;
		boolean res = true;
		while (n1 != null && n2 != null) {
			if (n1.val != n2.val) {
				res = false;
				break;
			}
			n1 = n1.next;
			n2 = n2.next;
		}
		n1 = n3.next;
		n3.next = null;
		while (n1 != null) {
			n2 = n1.next;
			n1.next = n3;
			n3 = n1;
			n1 = n2;
		}
		return res;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值