目录
1.反转链表
2. 链表内指定区间反转
3. 链表中的节点每k个一组翻转
4. 合并两个排序的链表
5.判断链表中是否有环
6.链表中环的入口
7. 链表中倒数最后K个节点
8. 删除链表的倒数第N个节点
9. 两个链表的第一个公共节点
10. 链表相加
11. 判断一个链表是否为回文结构
12. 链表的奇偶重排
13. 删除有序链表中重复的元素-1
14. 删除有序链表中重复的元素-2
说明:题目来源于牛客网
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
public ListNode ReverseList(ListNode head) {
if(head == null) {
return null;
}
ListNode prev = null;
ListNode cur = head;
while( cur!= null){
ListNode curNext= cur.next;
cur.next = prev;
prev = cur;
cur = curNext;
}
return prev;
}
时间复杂度:O(n), 遍历一次链表
空间复杂度:O(1)
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4
返回 1→4→3→2→5→NULL.。
思路
public ListNode reverseBetween (ListNode head, int m, int n) {
// write code here
if(head == null) {
return null;
}
//设置一个新的链表头
ListNode newHead = new ListNode(-1);
newHead.next = head;
//前序节点
ListNode node = newHead;
//当前节点
ListNode cur = head;
//放入M前面的节点
for (int i = 0; i < m-1; i++) {
node = cur;
cur = cur.next;
}
//反转m 到n
for(int i = m; i < n; i++) {
//标记好节点
ListNode tmp = cur.next;
cur.next = tmp.next;
tmp.next = node.next;
node.next = tmp;
}
return newHead.next;
}
复杂度:
将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表。如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样。你不能更改节点中的值,只能更改节点本身。
思路:
public ListNode reverseKGroup (ListNode head, int k) {
// write code here
//连接K前面的节点
ListNode tail = head;
for(int i = 0; i < k; i++) {
if (tail == null) return head;
tail = tail.next;
}
//到这一步,tail就走到了下一组的头结点
//到达K,翻转
ListNode pre = null;//遍历的前序节点
ListNode cur = head;//原本的头不动,用一个新的来记录
while(cur != tail) {
ListNode curNext = cur.next;
cur.next = pre;
pre = cur;
cur = curNext;
}
//到这里就翻转完一组K
//递归下一组
//原本的头节点因为不动,还在原来的位置,现在成了尾结点
//尾结点连接下一组的头节点
head.next = reverseKGroup(tail,k);//此处的tail就是每一组的头结点
return pre;
}
复杂度分析:
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
思路:
public ListNode Merge(ListNode list1,ListNode list2) {
if (list1 == null && list2 == null) {
return null;
}
//建一个新的链表,将合并后的放入其中
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
//放入一个节点
tmp.next = list1;
list1 = list1.next;
tmp = tmp.next;
}else {
tmp.next = list2;
list2 = list2.next;
tmp = tmp.next;
}
}
//到这走完其中一个链表了
if(list1 != null) {
tmp.next = list1;//因为是有序了,所以连接好前面一个节点就写
}
if (list2 != null) {
tmp.next = list2;
}
return newHead.next;
}
}
时间复杂度:O(m+n)
空间复杂度:O(m+n),每一次递归,递归栈都会保存一个变量,最差情况会保存(m+n)个变量
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
思路
public boolean hasCycle(ListNode head) {
if(head == null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
return true;
}
}
return false;
}
给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
思路;
public ListNode hasCycle(ListNode head) {
if(head == null) return null;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
return slow;
}
}
return null;
}
public ListNode EntryNodeOfLoop(ListNode pHead) {
ListNode slow = hasCycle(pHead);
//没有环
if(slow == null) return null;
//快指针回到表头
ListNode fast = pHead;
//再次相遇就是入口
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。
思路:
public ListNode FindKthToTail (ListNode pHead, int k) {
ListNode fast = pHead;
ListNode slow = pHead;
//快指针先行k步
for (int i = 0; i < k; i++) {
if (fast != null) {
fast = fast.next;
} else {
//达不到k步说明链表过短,没有倒数k
return null;
}
}
//快慢指针同步,快指针先到底,慢指针指向倒数第k个
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针
思路:
public ListNode removeNthFromEnd (ListNode head, int n) {
// write code here
ListNode fast = head;
ListNode slow = head;
ListNode pre = new ListNode(-1);
pre.next = head;
ListNode cur = pre;
while(n != 0) {
fast = fast.next;
n--;
}
while(fast != null) {
fast = fast.next;
cur = slow;
slow = slow.next;
}
cur.next = slow.next;
return pre.next;
}
思路:
1. 利用双指针法,先算出两个链表的长度
2.然后走两个链表的长度差值
在一起走,相遇就是一第一个公共节点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null && pHead2 == null) {
return null;
}
ListNode pl = pHead1;
ListNode ps = pHead2;
int len1 = 0;
int len2 = 0;
//1.遍历两个链表,求长度
while(pl != null) {
len1++;
pl = pl.next;
}
//走完再回到头节点
pl = pHead1;
while (ps != null) {
len2++;
ps = ps.next;
}
ps = pHead2;
//差值
int len = len1 - len2;
//pl始终是记录最长链表的
//len小于0 ,说明pHead2长
if (len < 0) {
pl = pHead2;
ps = pHead1;
len = len2 - len1;
}
//标记好最长的之后,长的开始走差值
while (len > 0) {
pl = pl.next;
len--;
}
//到这里,剩下的节点数相同,
//同时走,相遇就是第一个公共节点
while (ps != pl) {
ps = ps.next;
pl = pl.next;
}
return ps;
}
假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
思路:
public ListNode addInList (ListNode head1, ListNode head2) {
// write code here
//任意链表为空,返回另一个
if (head1 == null) return head2;
if (head2 == null) return head1;
//反转两个链表
head1 = ReverseList(head1);
head2 = ReverseList(head2);
//添加表头
ListNode res = new ListNode(-1);
ListNode head = res;
//进位符号
int carry = 0;
//只要某个链表还有或者进位还有
while (head1 != null || head2 != null || carry != 0) {
//链表不为空则取其值
int val1 = head1 == null ? 0 : head1.val;
int val2 = head2 == null ? 0 : head2.val;
//相加
int temp = val1 + val2 + carry;
//获取进位
carry = temp / 10;
temp %= 10;
//添加元素
head.next = new ListNode(temp);
head = head.next;
//移动下一个
if (head1 != null) head1 = head1.next;
if (head2 != null) head2 = head2.next;
}
//结果反转回来
return ReverseList(res.next);
}
public ListNode ReverseList(ListNode pHead) {
if (pHead == null) return null;
ListNode cur = pHead;
ListNode pre = null;
while (cur != null) {
ListNode curNext = cur.next;
cur.next = pre;
pre = cur;
cur = curNext;
}
return pre;
}
思路:
1.找中间节点
2.反转后半链表
3.两边往中间走,判断
public boolean isPail (ListNode head) {
// 1.找中间节点
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//2.反转后半链表
ListNode pre = null;
while(slow != null) {
ListNode node = slow.next;
slow.next = pre;
pre = slow;
slow = node;
}
//两边往中间走,判断
ListNode cur = head;
while(pre != null) {
if(cur.val != pre.val) {
return false;
}
cur = cur.next;
pre = pre.next;
}
return true;
}
给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。
注意是节点的编号而非节点的数值。
思路:
public ListNode oddEvenList (ListNode head) {
// write code here
//如果链表为空,不用重排
if (head == null) return head;
// odd 开头指向第一个节点
ListNode odd = head;
//even 指向开头第二个
ListNode even = head.next;
//指向even开头
ListNode evenHead = even;
while (even != null && even.next != null) {
//odd连接even的后一个,即奇数位
odd.next = even.next;
//odd进入后一个奇数位
odd = odd.next;
//even连接后一个奇数的后一位,即偶数位
even.next = odd.next;
even = even.next;
}
//even整体接在odd后面
odd.next = evenHead;
return head;
}
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
思路:
public ListNode deleteDuplicates (ListNode head) {
// write code here
if (head == null) return null;
ListNode cur = head;
while (cur != null && cur.next != null) {
if (cur.val == cur.next.val) {
cur.next = cur.next.next;
}else {
cur = cur.next;
}
}
return head;
}
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
思路:
public ListNode deleteDuplicates (ListNode head) {
// write code here
if(head == null) return null;
ListNode pre = new ListNode(-1);
pre.next = head;
ListNode cur = pre;
while(cur.next != null && cur.next.next != null) {
if(cur.next.val == cur.next.next.val) {
int temp = cur.next.val;
while(cur.next != null && cur.next.val == temp) {
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return pre.next;
}