【数据结构与算法】链表经典题
- 1. 两数相加
- 2. 两数相加2
- 3. 回文链表
- 4. 分隔链表
- 5. 奇偶链表
- 6. 相交链表
- 7. 翻转链表
- 8. 合并两个有序链表
- 9. 删除链表的倒数第N个节点
- 10. 环形链表
- 11. 两两交换链表中的结点
- 12. 删除排序链表中的重复元素
两数相加
LeetCode:两数相加
题目描述:
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
思想:
链表遍历,关键的地方在于补0,因为两个链表长短可能不一致,不补0的话很麻烦
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p = l1,q=l2;
int twoSum=0,nextAdd=0;
while(p!=null||q!=null){
if(p.next==null&&q.next!=null)
p.next = new ListNode(0);
if(q.next==null&&p.next!=null)
q.next = new ListNode(0);
twoSum = p.val + q.val;
p.val = (twoSum + nextAdd)%10;
nextAdd = (twoSum + nextAdd)/10;
if(p.next==null&&q.next==null&&nextAdd>0){
p.next = new ListNode(nextAdd);
break;
}
p=p.next;
q=q.next;
}
return l1;
}
}
2021-3-30更新
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode L = l1;
int carry = 0;
while(l1!=null){
int temp = l1.val + carry + (l2==null?0:l2.val);
carry = temp / 10;
l1.val = temp % 10;
if(l1.next==null){
if(carry>0) l1.next = new ListNode(0);
else{
l1.next = l2==null?null:l2.next;
break;
}
}
l1 = l1.next;
if(l2!=null) l2 = l2.next;
}
return L;
}
}
两数相加2
LeetCode:两数相加 II
题目描述:
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
思想:
与上一题不一样,链表顺序是反过来的,使用栈进行处理。
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> stackL1 = buildStack(l1);
Stack<Integer> stackL2 = buildStack(l2);
ListNode L=null,p=null;
int x,y,item,carry=0;
while(!(stackL1.empty()&&stackL2.empty()) || carry>0){
x = stackL1.empty()?0:stackL1.pop();
y = stackL2.empty()?0:stackL2.pop();
item = (x+y+carry)%10;
carry = (x + y + carry)/10;
L = new ListNode(item);
L.next = p;
p = L;
}
return L;
}
private Stack<Integer> buildStack(ListNode L){
Stack<Integer> stack = new Stack<>();
while(L!=null){
stack.push(L.val);
L=L.next;
}
return stack;
}
}
回文链表
LeetCode:回文链表
题目描述:
判断一个链表是否为回文链表。
示例:
输入: 1->2->2->1
输出: true
思想:
先用快慢指针取得中间位置的指针,将后半段链表进行翻转,再遍历比较前半段和后半段是否一致。时间复杂度O(3/2n),空间复杂度O(1)
优化:在快慢指针的循环中,同时翻转前半段链表,随后从中间位置往两头遍历,时间复杂度O(n)
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
if(head==null||head.next==null)
return true;
ListNode mid=head,end=head;
while(end!=null&&end.next!=null){
mid = mid.next;
end = end.next.next;
}
ListNode p=mid,q=mid.next,t;
while(q!=null){
t=q.next;
q.next=p;
p=q;q=t;
}
q=head;
while(q!=mid){
if(q.val!=p.val)
return false;
q=q.next;
p=p.next;
}
return true;
}
}
优化版,但是代码着实有点复杂,没有上一种方法直白
class Solution {
public boolean isPalindrome(ListNode head) {
if(head==null||head.next==null)
return true;
ListNode mid=head,end=head.next,q=head.next,t;
while(end!=null&&end.next!=null){
end = end.next.next;
t = q.next;
q.next = mid;
mid = q;q = t;
}
ListNode p=(end==null)?mid.next:mid;
while(q!=null){
if(q.val!=p.val)
return false;
q=q.next;
p=p.next;
}
return true;
}
}
分隔链表
LeetCode:分隔链表
题目描述:
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
示例:
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
思想:
没什么思想,就是先遍历一遍获得链表长度,再找分隔点进行分割。主要是代码逻辑有点复杂,需要多加练习。
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
int len=0;
ListNode p=root,pre=null;
while(p!=null){
++len;
p=p.next;
}
int base=len/k,add=len%k,i=0,count=0;
p=root;
ListNode[] res = new ListNode[k];
while(p!=null){
if(count ==0){
if(pre!=null)
pre.next = null;
res[i++]=p;
count = base + (add-->0?1:0);
}
--count;
pre=p;
p=p.next;
}
return res;
}
}
奇偶链表
LeetCode:奇偶链表
题目描述:
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
思想:
第一次写的方法是在遍历时使用一个flag注明奇偶,这样太蠢了。使用双指针比较直观,如下方代码
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head==null||head.next==null)
return head;
ListNode m=head.next,n=m;
ListNode p=head;
while(n!=null&&n.next!=null){
p.next = p.next.next;
n.next = n.next.next;
p=p.next;
n=n.next;
}
p.next=m;
return head;
}
}
相交链表
LeetCode:相交链表
题目描述:
编写一个程序,找到两个单链表相交的起始节点。
示例:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
思想:
这题代码简洁,非常巧妙。A走完A的路再走B的路,B走完B的路再去走A的路,最后一段路一定是重合的,即最后一定会在交点相遇。
代码:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p=headA,q=headB;
while(p!=q){
p=(p==null)?headB:p.next;
q=(q==null)?headA:q.next;
}
return p;
}
}
翻转链表
LeetCode:翻转链表
题目描述:
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
思想:
前插法很熟悉了,主要是递归。注意每次递归返回末尾结点,即新链表的头结点,不然得不到结果;函数体内记录当前结点和下一个结点,这递归相当于从倒数第二个结点开始向左翻转,每次返回后,左移一个结点继续下一次翻转。
代码
//前插
private ListNode preInsert(ListNode p){
ListNode L =new ListNode(0),temp;
L.next=null;
while(p!=null){
temp = p.next;
p.next=L.next;
L.next = p;
p=temp;
}
return L.next;
}
//递归
private ListNode recursive(ListNode p){
if(p==null||p.next==null){
return p;
}
ListNode q =p.next;
ListNode head = recursive(q);
q.next=p;
p.next=null;
return head;
}
public ListNode reverseList(ListNode head) {
return recursive(head);
//return preInsert(head);
}
2021-1-24原地翻转
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode left=null,right=head;
ListNode temp;
while(right!=null){
temp = right.next;
right.next=left;
left = right;
right = temp;
}
return left;
}
}
合并两个有序链表
LeetCode:合并两个有序链表
题目描述:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思想:
这题用递归来做,思路比较清晰简洁。
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null) return l2;
if(l2==null) return l1;
if(l1.val<l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
删除链表的倒数第k个节点
LeetCode:删除链表的倒数第N个节点
题目描述:
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
思想:
本题可以用一趟遍历实现。快慢指针构成一个窗口,再滑动窗口。具体:先循环n次将high指针移动到第n位置,与头结点位置的slow指针同时移动,这样当high指向null时,slow指针指向了目标位置。
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast = head,slow=head;
while(n-->0){
fast = fast.next;
}
if(fast==null) return head.next;
while(fast.next!=null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
}
环形链表
LeetCode:环形链表
题目描述:
给定一个链表,判断链表中是否有环。
示例:
输入:head = [3,2,0,-4]
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
思想:
快慢指针,一次跨两个节点,一次跨一个节点,两个指针重合就说明是环形链表。
代码:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null) return false;
ListNode slow = head;
ListNode fast = head.next;
while(fast!=slow){
if(fast==null||fast.next==null) return false;
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
两两交换链表中的结点
LeetCode:两两交换链表中的结点
题目描述:
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
思想:
没啥思想,一个递归,一个循环,两种方法,注意熟练掌握。
代码
- 递归
private ListNode swapPairsImpl(ListNode L){
if(L.next==null) return null;
if(L.next.next == null) return L.next;
ListNode p = L.next,q=p.next;
p.next = swapPairsImpl(q);
L.next = q;
q.next = p;
return q;
}
public ListNode swapPairs(ListNode head) {
ListNode L =new ListNode(-1);
L.next = head;
return swapPairsImpl(L);
}
- 循环
public ListNode swapPairs(ListNode head) {
ListNode L =new ListNode(-1);
L.next = head;
ListNode m, p=L,q;
while(p.next!=null&&p.next.next!=null){
m=p;p = m.next;q=p.next;
p.next = q.next;
m.next = q;
q.next = p;
}
return L.next;
}
删除排序链表中的重复元素
LeetCode:删除排序链表中的重复元素
题目描述:
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例:
输入: 1->1->2->3->3
输出: 1->2->3
思想:
递归方法很巧妙,只需要三行代码。关键点在于,通过return来返回相同结点最右边的结点,达到删除目的。
代码
- 普通循环(自己想到的)
public ListNode deleteDuplicates(ListNode head) {
ListNode p =head;
while(p!=null&&p.next!=null){
if(p.next.val == p.val)
p.next = p.next.next;
else
p=p.next;
}
return head;
}
- 递归
public ListNode deleteDuplicates(ListNode head) {
if(head==null||head.next==null) return head;
head.next = deleteDuplicates(head.next);
return head.val==head.next.val?head.next:head;
}