LeetCode日记——【数据结构】链表专题

  题1:找出两个链表的交点(Intersection of Two Linked Lists)

LeetCode题号:160

难度:easy

链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/

题目描述:

编写一个程序,找到两个单链表相交的起始节点。

如下面的两个链表:

 在节点 c1 开始相交。

注意:

如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) {
 7  *         val = x;
 8  *         next = null;
 9  *     }
10  * }
11  */
12 public class Solution {
13     public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
14         if(headA==null||headB==null) return null;
15         ListNode a = headA;
16         ListNode b = headB;
17         while(a!=b){
18             a=a==null?headB:a.next;
19             b=b==null?headA:b.next;
20         }
21         return a;  
22     }
23 }

分析:

1.我们可以发现,无论从哪条路径出发,走完自己整条路,再从对方的头节点开始走到相交节点所走过的节点数是一样的。所以我们的思路就是,两条路同时开始走,走完自己的走对方的,当两条路走到同一个节点,那么这个节点就是相交节点了。

2.程序中,a和b表示当前节点,用a与ab来遍历我们两条链表。最后return的值,a或b均可(因为最后他俩一样了才会退出循环)。如果两条链表没有交点,那么a与b最后都变成null,也会退出循环并返回null。

 

  题2:链表反转(Reverse Linked List)

LeetCode题号:206

难度:easy

链接:https://leetcode-cn.com/problems/reverse-linked-list/description/

题目描述:

反转一个单链表。

示例:

输入:1——>2——>3——>4——>5——>null

输出:5——>4——>3——>2——>1——>null

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode reverseList(ListNode head) {
11         ListNode curr = head;
12         ListNode pre = null;
13         while(curr!=null){
14             ListNode next=curr.next;
15             curr.next = pre;
16             pre = curr;
17             curr = next;
18         }
19         return pre;
20     }
21 }

分析:

这道题我看了一个执行过程的动图之后才懂。用文字描述如下:

1.一开始pre指向null,curr为head即1,然后我们开始循环。

2.循环体做的事(核心为构造新指针):将next节点置为curr节点的下一个节点,将curr本来指向next的指针指向pre,pre节点往右移动一格,curr节点往右移动一格。

3.最后一步:curr为5,5本来指向null的指针指向4,pre变为5,curr变为null。跳出循环。此时pre为我们新的head(5),所以返回pre。

 

  题3:归并两个有序的链表(Merge Two Sorted Lists)

LeetCode题号:21

难度:easy

链接:https://leetcode-cn.com/problems/merge-two-sorted-lists/description/

题目描述:

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例:

输入:1——>2——>4,1——>3——>4

输出:1——>1——>2——>3——>4——>4

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
11         ListNode head = new ListNode(-1);
12         ListNode curr = head;
13         while(l1!=null && l2!=null){
14             if(l1.val<=l2.val){
15                 curr.next=l1;
16                 l1=l1.next;
17             }else{
18                 curr.next=l2;
19                 l2=l2.next;
20             }
21             curr=curr.next;
22         }
23     curr.next=l1==null?l2:l1;
24     return head.next;
25     }
26 }

分析:

首先我们定义一个固定的head节点,方便我们最后返回head.next即真正的头节点。

定义一个curr节点作为我们遍历时的当前节点。

在循环体中,我们分别用l1和l2来指示两条链表的当前位置,依次将较小的数接在curr节点之后,接完后l1或l2后移一位。

且无论接的是l1还是l2,每接一个数,curr节点都要后移一位。

最后,可能存在其中一个链表还有最后一个数没有接上而另外一个链表已经为null就跳出循环的情况,因此我们通过判断l1或l2是否为null把最后一个节点接上,并返回头节点即head.next。

 

  题4:从有序链表中删除重复节点(Remove Duplicates from Sorted List)

LeetCode题号:83

难度:easy

链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/description/

题目描述:

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:

输入: 1——>1——>2
输出: 1——>2
示例 2:

输入: 1——>1——>2——>3——>3
输出: 1——>2——>3

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode deleteDuplicates(ListNode head) {
11         ListNode curr = head;
12         while(curr!=null && curr.next != null){
13             if(curr.val==curr.next.val){
14                 curr.next = curr.next.next;
15             }else{
16                 curr=curr.next;
17             }
18         }
19         return head;
20     }
21 }

分析:

由于这个链表是有序的,因此我们可以这样写:

定义一个当前节点curr为head。

在当前节点curr与当前节点的下一个节点curr.next均不为空的条件下开启循环。循环体里分为两种情况:

1.我们发现当前节点curr的值与它的下一个节点的值相同,那么我们将curr的指针直接指向它的下下个节点,即删除了curr.next。注意,这里不需要将curr后移,否则出现连续3个及以上相同的值时无法删除。

2.值不相同,则将curr节点后移一位即可。

最后返回头节点。

 

  题5:删除链表的倒数第 n 个节点(Remove Nth Node From End of List)

LeetCode题号:19

难度:Medium

链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/

题目描述:

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode removeNthFromEnd(ListNode head, int n) {
11         ListNode dummy = new ListNode(0);
12         dummy.next=head;
13         ListNode curr = head;
14         int length  = 0;
15         int removeLength = 0;
16         while(curr!=null){
17             length++;
18             curr=curr.next;
19         }
20         removeLength=length-n;
21         curr = dummy;
22         while (removeLength > 0) {
23         removeLength--;
24         curr = curr.next;
25     }
26     curr.next = curr.next.next;
27     return dummy.next;
28     }
29 }

分析:

在第一次遍历中,我们得到链表的总长度,从而计算出我们需要删除节点的正向序号。

第二次遍历时,我们通过改变指针指向删除指定节点。

 

  题6:删除链表的倒数第 n 个节点(Swap Nodes in Pairs)

LeetCode题号:24

难度:Medium

链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs/description/

题目描述:

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

 示例:给定 1->2->3->4, 你应该返回 2->1->4->3.

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode swapPairs(ListNode head) {
11         ListNode pre = new ListNode(0);
12         pre.next=head;
13         ListNode curr = pre;
14         while(curr.next!=null&&curr.next.next!=null){
15             ListNode start = curr.next;
16             ListNode end = curr.next.next;
17             curr.next=end;
18             start.next=end.next;
19             end.next=start;
20             curr=start;
21         }
22         return pre.next;
23     }
24 }

分析:

特别注意:代码18,19行不可互换。因为执行19行之后,end的next节点就已经改变了。

 

  题7:链表求和(Add Two Numbers II

LeetCode题号:445

难度:Medium

链接:https://leetcode-cn.com/problems/add-two-numbers-ii/description/

题目描述:

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例:

输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
11         Stack<Integer> s1 = new Stack<>();
12         Stack<Integer> s2 = new Stack<>();
13 
14         while(l1 != null) {
15             s1.push(l1.val);
16             l1 = l1.next;
17         }
18         while(l2 != null) {
19             s2.push(l2.val);
20             l2 = l2.next;
21         }
22 
23         ListNode res = null;
24         int carry = 0;
25         while(!s1.isEmpty() || !s2.isEmpty() || carry > 0) {
26             int x = s1.isEmpty() ? 0 : s1.pop();
27             int y = s2.isEmpty() ? 0 : s2.pop();
28             int sum = x + y + carry;
29             ListNode curr = new ListNode(sum % 10);
30             carry = sum / 10;
31             curr.next = res;
32             res = curr;
33         }
34         return res;
35     }
36 }

分析:

这道题我做了好久,崩溃。

主要思路就是先把两条链表中的数据压入栈中,然后再一个个取出来相加,注意用整除来获取进位数,用取余来获取该位的值。

相加的时候要注意链表的方向,生成的链表的头部是最后生成的节点。所以我们每生成一个新的curr节点,都使它指向上一个节点res,然后再把res移到当前的curr。

 

  题8:回文链表(Palindrome Linked List)

LeetCode题号:234

难度:Easy

链接:https://leetcode-cn.com/problems/palindrome-linked-list/description/

题目描述:

请判断一个链表是否为回文链表。

示例 1:

输入:1——>2

输出:false

示例2:

输入:1——>2——>2——>1

输出:true

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public boolean isPalindrome(ListNode head) {
11         List <Integer> list = new ArrayList<>();
12         ListNode curr = head;
13         //将链表中的值存入数组中
14         while(curr!=null){  
15             list.add(curr.val);
16             curr=curr.next;
17         }
18         int start = 0; //第一个数的索引
19         int end = list.size()-1; //最后一个数的索引
20         while(start<end){
21             if(!list.get(start).equals(list.get(end))){
22                 return false;
23             }
24                 start++;
25                 end--;
26         }
27         return true;
28     }
29 }

分析:

回文链表概念:我们将它反转之后还是与原链表一样,我们就称这种链表结构为回文结构。

首先我们通过一次遍历将链表中的数按顺序存入容器中。

然后我们用双指针来分别从头和尾将数取出,并进行比较。如果数字不一样,马上返回false。如果当前两数一样,则分别移动两个指针,继续比较。当指针均移动到中间位置时数字都一样,则返回true。

 

  题9:分隔链表(Split Linked List in Parts)

LeetCode题号:725

难度:Medium

链接:https://leetcode-cn.com/problems/split-linked-list-in-parts/description/

题目描述:

给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。

这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。

返回一个符合上述规则的链表的列表。

举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode[] splitListToParts(ListNode root, int k) {
11         ListNode curr = root;
12         int length = 0;
13         while (curr != null) {
14             curr = curr.next;
15             length++;
16         }
17         int width = length / k, rem = length % k;
18         ListNode[] ans = new ListNode[k];
19         curr = root;
20         for (int i = 0; i < k; ++i) {
21             ListNode head = new ListNode(0), write = head;
22             for (int j = 0; j < width + (i < rem ? 1 : 0); ++j) {
23                 write = write.next = new ListNode(curr.val);
24                 if (curr != null) curr = curr.next;
25             }
26             ans[i] = head.next;
27         }
28         return ans;
29     }
30 }

分析:

我们分隔的方式为:总个数整除块数为每块基准个数,剩余的节点,从第一块开始每块放一个,放完为止。

如:总数22,分成5块,22/5=4,则我们得到22=4+4+4+4+4+2。最后两块我们前两块中每块放一个。这样最后我们就分为:22=5+5+4+4+4。

于是,在程序中体现为:总共分为k块,前rem块每块有width+1个节点,其余每块width个节点,即width+(i<rem?1:0)

在i循环中,我们创建每块的头节点,在j循环中,往里面放数。最后返回ans数组。

 

  题10:链表元素按奇偶聚集(Odd Even Linked List)

LeetCode题号:328

难度:Medium

链接:https://leetcode-cn.com/problems/split-linked-list-in-parts/description/

题目描述:

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:

输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL

代码:

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode oddEvenList(ListNode head) {
11         if(head==null) return null;
12         ListNode oddcurr = head;//用来遍历奇数节点
13         ListNode evencurr = head.next;//用来遍历偶数节点
14         ListNode evenhead = evencurr;//保存偶数节点的头部
15         while(evencurr!=null && evencurr.next!=null){
16             oddcurr.next=evencurr.next;
17             oddcurr=oddcurr.next;
18             evencurr.next=oddcurr.next;
19             evencurr=evencurr.next;
20         }
21         oddcurr.next=evenhead;//将奇数链的尾部与偶数链的头部连起来
22         return head;
23     }
24 }

分析:

总体思路为:把原链表拆为奇数,偶数两条链表,然后把偶数链表接在奇数链表尾部。

我们定义oddcurr来遍历奇数节点,定义evencurr来遍历偶数节点。另外,我们提前保存好偶数链表头evnhead。

在循环中,我们将当前奇数点指向偶数点的下一个点(即下一个奇数点),然后奇数点移动到刚刚连好的下一个奇数点。将偶数点指向奇数点的下一个点(即下一个偶数点),然后偶数点移动到下一个偶数点。

最后将偶数链的头部接到奇数链子的尾部,返回奇数链的头部head即可。

 完结撒花~

posted @ 2020-05-15 10:12  菅兮徽音  阅读(197)  评论(0编辑  收藏  举报