例题:反转链表
反转链表原理很简单,就是将原本指向下一个结点的指针,指向上一个结点。但是,需要存储前面和后面的结点,要不就找不到了。
所以使用pre cur next
三个结点,存储着上一个结点、当前结点、下一个结点,然后一起向后遍历。在这期间,最需要注意的就是顺序以及边界。
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) return null;
ListNode pre = null;
ListNode cur = head;
ListNode next = head.next;
while(next!=null){
cur.next = pre;
pre = cur;
cur = next;
next = next.next;
}
cur.next = pre;
return cur;
}
}
1.反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明: 1 ≤ m ≤ n ≤ 链表长度。
示例: 输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
草草在纸上画了一下,基本上弄清楚大概的思路了。还是和例题一样,用那三个指针。
这次,我们使用一个计数器,来判断循环的次数,当它到达m和n的时候,分别作出对应操作:
- 到达m时,定义两个指针,
first = pre; second = cur;
- 到达n时,将
first.next = cur; second.next = next;
,最后返回head即可。但是,这里要注意两个点
1.first为空则不执行first.next = cur
2.如果从头结点便开始翻转,那么返回的头结点就是cur
从头到尾只进行了一趟扫描,所以时间复杂度为O(n)。
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if(head==null) return null;
ListNode pre = null;
ListNode cur = head;
ListNode next = head.next;
int num = 1; //用来计数此时遍历到的是第几个结点
while(num<m){
pre = cur;
cur = next;
next = next.next;
num++;
}
ListNode first = pre; //记录之后要用到的结点
ListNode second = cur;
while(num<n){ //翻转指定的链表
cur.next = pre;
pre = cur;
cur = next;
next = next.next;
num++;
}
cur.next = pre;
if(first!=null) first.next = cur;
second.next = next;
return m==1?cur:head; //m等于1,说明头结点变了,要以cur作为头结点
}
}
2.删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
乍一看思路很明显,因为是排序的,所以只需要比较下一个结点是否和当前结点值相等,相等next便指向下下个结点,然后再判断,知道与下一个结点值不同,再继续向下遍历。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null) return null;
ListNode cur = head;
while(cur!=null && cur.next!=null){
while(cur.next!=null && cur.next.val == cur.val){
cur.next = cur.next.next;
}
cur = cur.next;
}
return head;
}
}
But,要注意边界条件。毕竟在向后跳的时候,cur.next
和cur
都有可能为null,所以一定要判断好。
3.分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
这个题乍一看像是一遍快排,但实际上又不是,因为这是个链表,而且还需要保留相对位置。那么其实我们只需要新建两个链表,一个用来存储比x小的,一个用来存储比x大的,最后将它们拼接不就好了。
思路确定,剩下的就是细节了。首先需要新建两个结点作为链表虚拟头结点,然后再用两个指针分别指向两个链表最末端的元素。
**最需要注意的!!!**在最后一定要把rpoint
的next
设为null
,要不然会形成环。
时间复杂度:O(n)。
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode left = new ListNode(0);
ListNode lpoint = left;
ListNode right = new ListNode(0);
ListNode rpoint = right;
ListNode cur = head;
while(cur!=null){
if(cur.val<x){
lpoint.next = cur;
lpoint = lpoint.next;
}else{
rpoint.next = cur;
rpoint = rpoint.next;
}
cur = cur.next;
}
rpoint.next = null;
lpoint.next = right.next;
return left.next;
}
}
4.奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
其实思路和上一题还是挺相似的,都是分为两个新链表再合并,不过要注意:
- 不能使用两次便利,一次遍历奇数,一次遍历偶数。因为第一次做的修改会对第二次造成影响。所以,必须在同一个循环中,两个新链表交替前进。最后再合并。
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head==null || head.next==null) return head;
ListNode opoint = head; //奇数指针
ListNode even = head.next; //偶数头结点
ListNode epoint = even; //偶数指针
while(opoint.next!=null && epoint.next!=null){
opoint.next = epoint.next;
opoint = opoint.next;
epoint.next = opoint.next;
epoint = epoint.next;
}
opoint.next = even;
return head;
}
5.两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
思路很简单,因为是逆序,所以设置一个进位c,用来表示从上一位进来的值。然后将当前的两个结点值和进位相加,得到的值进位或者不进位。需要注意的点有:
- 当前结点为空时,如果另一个结点不为空或者
c!=0
时,那么相加时本结点值为0. - 最后终止条件为两链表都到末尾,且
c==0
.
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int c = 0; //表示从上一位进的值
ListNode newList = new ListNode(0);
ListNode np = newList;
while(l1!=null || l2!=null || c!=0){
int val1 = l1==null?0:l1.val;
int val2 = l2==null?0:l2.val;
int val = val1+val2+c;
if(val>=10){
c = 1;
val -= 10;
}else{
c = 0;
}
ListNode temp = new ListNode(val);
np.next = temp;
np = np.next;
if(l1!=null) l1 = l1.next;
if(l2!=null) l2 = l2.next;
}
return newList.next;
}
}
6.两数相加 II
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。 进阶: 如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
这个题难得的一次AC无任何错误,其实这个题归根结底和上一个差不多,只是顺序倒过来了而已。所以其实只要再倒回来就行了,但是题目要求不能修改链表。那么,对于倒转有一个强大的数据结构——栈。利用栈便可以轻松实现反转操作,且不修改链表。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<ListNode> s1 = new Stack<ListNode>();
Stack<ListNode> s2 = new Stack<ListNode>();
while(l1!=null){
s1.push(l1);
l1 = l1.next;
}
while(l2!=null){
s2.push(l2);
l2 = l2.next;
}
ListNode newList = null;
int c = 0;
while(!s1.empty() || !s2.empty() || c!=0){
int val1 = s1.empty()?0:s1.pop().val;
int val2 = s2.empty()?0:s2.pop().val;
int val = val1+val2+c;
if(val>=10){
c = 1;
val -= 10;
}else{
c = 0;
}
ListNode temp = new ListNode(val);
temp.next = newList;
newList = temp;
}
return newList;
}
}