【算法系列 一】 Linked List

纵饮孤独 提交于 2019-12-07 13:08:03

1. 给定两个链表,分别表示两个非负整数。它们的数字逆序存储在链表中,且每个结点只存储一个数字,计算两个数的和,并且返回该链表(Leetcode 2)。

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int val = (l1.val + l2.val) % 10;
		int flag = (l1.val + l2.val) / 10;
		ListNode l3 = new ListNode(val);
		ListNode res = l3;
		while (l1.next != null && l2.next != null)
		{
			l1 = l1.next;
			l2 = l2.next;
			val = (l1.val + l2.val + flag) % 10;
			flag = (l1.val + l2.val + flag) / 10;
			ListNode l4 = new ListNode(val);
			res.next = l4;
			res = l4;
		}
		while (l1.next != null)
		{
			l1 = l1.next;
			val = (l1.val + flag) % 10;
			flag = (l1.val + flag) / 10;
			ListNode l4 = new ListNode(val);
			res.next = l4;
			res = l4;
		}
		while (l2.next != null)
		{
			l2 = l2.next;
			val = (l2.val + flag) % 10;
			flag = (l2.val + flag) / 10;
			ListNode l4 = new ListNode(val);
			res.next = l4;
			res = l4;
		}
		if (flag > 0)
		{
			ListNode l4 = new ListNode(flag);
			res.next = l4;
		}
		return l3;
    }
}

这个题目很简单,就此不多做讨论,注意一点就是最后两个数加完,如果有进位,还需要new个结点出来。

2. 给定一个单链表,翻转该链表(Leetcode 206)

由于单链表翻转时会丢失节点,所以需要把节点保存起来。

翻转单链表需要存储3个节点,分别是当前节点,当前节点的前面节点,当前节点的后继节点。

注意null时的判断,最后返还时需要把原始节点的尾节点返回。

public class Solution
{
	public ListNode reverseList(ListNode head)
	{
		ListNode reversedHead = null;

		ListNode currentNode = head;
		ListNode prevNode = null;
		ListNode nextNode = null;
		while (currentNode != null)
		{
			nextNode = currentNode.next;
			if (nextNode == null)
			{
				reversedHead = currentNode;
			}
			currentNode.next = prevNode;
			prevNode = currentNode;
			currentNode = nextNode;
		}
		return reversedHead;
	}
}

class ListNode
{
	int val;
	ListNode next;

	ListNode(int x)
	{
		val = x;
	}
}

2.1 给定一个链表,翻转该链表从m到n的位置,要求直接翻转而非申请新空间,只能遍历一遍。(Leetcode 92)

For example:
Given 1->2->3->4->5->NULL, m = 2 and n = 4,

return 1->4->3->2->5->NULL.

与翻转单链表不同的是,将1->2->3->4->5->NULL转成1->2<-3<-4<-5->NULL是无法从尾节点遍历所有节点的。需要使用另外一种思想。

整体思想就是头插法,比如1->2->3->4把2到4翻转,将结点3插到1和2之间,然后把4再插到1后面。实现翻转。

思想很简单,主要是中间结点不断再变化位置,所以注意保留很多个位置。

需要4个变量:待翻转节点的前一个节点(类似头节点),待插入节点,待插入节点的下一个节点,插完后的链表的最后一个节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        ListNode res = new ListNode(0);//必须要有空的头结点,因为如果类似1-2,要变成2-1,没办法把2插到1的前面去
		res.next = head;
		ListNode p = res;//待翻转的结点的前一个结点,每次都是插在p的后面
		for (int i = 0; i < m - 1; i++)
		{
			p = p.next;
		}
		ListNode l = p.next.next;//待插入结点
		ListNode q = null;//下一个待插结点
		ListNode d = p.next;//插完后的链表的最后一个结点
		for (int i = 0; i < n - m; i++)
		{
			q = l.next;
			d.next = q;
			l.next = p.next;
			p.next = l;
			l = q;
		}
		return res.next;
    }
}

3. 给定一个链表和一个值x,将链表划分成两部分,使得划分后小于x的结点在前,大于等于x的结点在后,在这两部分中要保持原链表中的出现顺序(Leetcode 86)。

For example,
Given 1->4->3->2->5->2 and x = 3,
return 1->2->2->4->3->5.

由于上一题的影响,我第一反应就是头插法,比如例子中的1->4->3->2,把2插到1的后面变成1->2->4->3,每遇到小的一个就头插一次。

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode partition(ListNode head, int x) {
        if (head == null)
		{
			return head;
		}
		ListNode res = new ListNode(0);
		res.next = head;
		ListNode p = res;
		while (p != null)
		{
			if (p.next == null || p.next.val >= x)
			{
				break;
			}
			p = p.next;
		}
		ListNode d = p.next;
		ListNode l = d;
		ListNode b = d;
		while (l != null)
		{
			l = l.next;
			if (l == null || l.val < x)
			{
				break;
			}else {
				b = b.next;
			}
		}
		ListNode q = null;
		if (l != null)
		{
			q = l.next;
		}
		while (l != null)
		{
			if(l.val >= x)
			{
				b = l;
				l = q;
				if(l != null)
				{
					q = l.next;
				}else {
					break;
				}
				continue;
			}
			p.next = l;
			l.next = d;
			b.next = q;
			l = q;
			if(l != null)
			{
				q = l.next;
			}else {
				break;
			}
			p = p.next;
		}
		return res.next;
    }
}

其中如上题设了好多指针,在此解释一下

p:头插中的头

d:大于等于x的第一个结点

b:大于等于x的最后一个结点

l:待插入的结点

q:下一个待判断的结点

然后对这个复杂的关系画了半天的图,终于AC掉了这个问题。

然后一想,分明还有更好的办法啊。定义两个指针p1,p2,遍历一遍链表,将小于x的接到p1后面,大于等于x的接到p2后面,最后将p2接到p1后面就好了啊。

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode p1 = new ListNode(0);
		ListNode p2 = new ListNode(0);
		ListNode p1tail = p1;
		ListNode p2tail = p2;
		while (head != null)
		{
			if (head.val < x)
			{
				p1tail.next = head;
				p1tail = p1tail.next;
			}
			else
			{
				p2tail.next = head;
				p2tail = p2tail.next;
			}
			head = head.next;
		}
		p1tail.next = p2.next;
		p2tail.next = null;
		return p1.next;
    }
}

这样代码简单多了,思路也更清晰了。

在上面的思路下,这道题目是不是有点像快速排序呢?有关快排或者排序的知识请参kao这里。选出一个pivot,然后将序列分成两边,一边比pivot小,一边比pivot大,然后不断递归。这正是快速排序的思想啊。

该问题其实说明:快速排序对于单链表存储结构仍然使用。

注意:不是所有排序都方便使用链表存储。如堆排序,将不断的查找数组的n/2和n的位置(父结点与子结点),用链表做就不太方便了。

4. 给定排序的链表,删除重复元素,只保留重复元素第一次出现的结点(Leetcode 83)。

For example,
Given 1->1->2, return 1->2.
Given 1->1->2->3->3, return 1->2->3.

思想很简单,由于是排好序的,碰到一个不同的值就保存起来,然后比较下一个结点的值是不是和它相同,如果相同则删掉这个结点,如果不同则改变保存起来的值,继续比较。

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null)
		{
			return head;
		}
		ListNode p = head;
		ListNode p1 = head;
		int val = head.val;
		head = head.next;
		while (head != null)
		{
			if(head.val == val)
			{
				p1.next = head.next;
				head = head.next;
			}else {
				val = head.val;
				p1 = head;
			}
		}
		return p;
    }
}

5. 给定排序的链表,若发现重复元素,则重复元素全部删除。(Leetcode 82

For example,
Given 1->2->3->3->4->4->5, return 1->2->5.
Given 1->1->1->2->3, return 2->3.

思想很简单,碰到相同的就跳过即可。

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null)
		{
			return head;
		}
		ListNode res = new ListNode(0);
		res.next = head;
		ListNode p = res;//最后一个不重复的结点
		while (head != null)
		{
			if (head.next == null || head.val != head.next.val)
			{
				p = head;
				head = head.next;
			}
			else
			{
				while (head.next != null && head.val == head.next.val)
				{
					head = head.next;
				}
				head = head.next;
				p.next = head;
			}
			
		}
		return res.next;
    }
}

系列:

【算法系列 一】 Linked List

【算法系列 二】 Stack

【算法系列 三】 Quene

【算法系列 四】 String

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!