1、反转链表
题目描述:
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围: 0\leq n\leq10000≤n≤1000
要求:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n) 。
如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:
示例:
思路:
-
用两个指针指示前置节点与当前节点
-
初始化两个节点
-
不断移动两个指针的同时改变当前节点的方向,直到到达最后一个节点
代码:
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode ReverseList(ListNode head) { //记录前置节点和当前节点位置 ListNode pre = null; ListNode cur = head; //不断移动cur节点,向后遍历,同时更改nex //当cur为空时,遍历完成 while(cur != null){ //将cur下一个节点存起来 ListNode temp = cur.next; //更改方向 cur.next = pre; //pre cur都后移 pre = cur; cur = temp; } return pre; } }
2、链表内指定区间反转
题目描述:
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。 例如: 给出的链表为 1\to 2 \to 3 \to 4 \to 5 \to NULL1→2→3→4→5→NUL**L, m=2,n=4m=2,n=4, 返回 1\to 4\to 3\to 2\to 5\to NULL1→4→3→2→5→NUL**L.
数据范围: 链表长度 0 < size \le 10000<siz**e≤1000,0 < m \le n \le size0<m≤n≤siz**e,链表中每个节点的值满足 |val| \le 1000∣val∣≤1000
要求:时间复杂度 O(n)O(n) ,空间复杂度 O(n)O(n)
进阶:时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)
示例:
思路:
-
new一个空节点指示链表,因为只翻转一段区间,得找到链表头。
-
设置两个节点一个指向当前节点,一个指向当前节点的前置节点。
-
初始化这两个节点,让它们指向第m个节点以及第m个节点的前一个节点,
-
然后向后翻转n-m次就完成了m到之间的节点的翻转。
import java.util.*; /* * public class ListNode { * int val; * ListNode next = null; * } */ public class Solution { /** * * @param head ListNode类 * @param m int整型 * @param n int整型 * @return ListNode类 */ public ListNode reverseBetween (ListNode head, int m, int n) { // write code here //严谨性判断 if(head==null||head.next==null){ return head; } if((n-m)<1){ return head; } //初始化一个空节点 ListNode tmpHead = new ListNode(0); tmpHead.next = head; //记录当前节点位置和前置节点位置 ListNode cur = head; ListNode pre = tmpHead; for(int i=1; i<m; i++){ cur = cur.next; pre = pre.next; } for(int i=0; i<n-m; i++){ //将cur下一个节点存起来 ListNode temp = cur.next; //cur后移 cur.next = cur.next.next; //翻转 temp.next = pre.next; //pre后移 pre.next = temp; } return tmpHead.next; } }
3、链表中的节点每个k个一组翻转
题目描述:
将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表 如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样 你不能更改节点中的值,只能更改节点本身。
数据范围: \ 0 \le n \le 2000 0≤n≤2000 , 1 \le k \le 20001≤k≤2000 ,链表中每个元素都满足 0 \le val \le 10000≤val≤1000 要求空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
例如:
给定的链表是 1\to2\to3\to4\to51→2→3→4→5
对于 k = 2k=2 , 你应该返回 2\to 1\to 4\to 3\to 52→1→4→3→5
对于 k = 3k=3 , 你应该返回 3\to2 \to1 \to 4\to 53→2→1→4→5
示例:
思路:
-
new一个空节点指示链表
-
设置三个节点cur,pre,temp
-
计算链表长度
-
遍历链表,k个k个交换,每一组交换完后要更新pre和cur
代码:
import java.util.*; /* * public class ListNode { * int val; * ListNode next = null; * } */ public class Solution { /** * * @param head ListNode类 * @param k int整型 * @return ListNode类 */ public ListNode reverseKGroup (ListNode head, int k) { // write code here //严谨性判断 if(head == null || head.next == null || k < 2) return head; //初始化一个空节点 ListNode tmpHead = new ListNode(0); tmpHead.next = head; //记录前置节点位置,当前节点位置,和一个temp ListNode pre = tmpHead, cur = head, temp; int len = 0; //求链表长度 while (head != null) { len ++ ; head = head.next; } //外层循环决定有多少组 //内层循环翻转 for (int i = 0; i < len / k; i ++ ) { for (int j = 1; j < k; j ++ ) { //下一个节点 temp = cur.next; //cur后移 cur.next = temp.next; //翻转 temp.next = pre.next; //pre后移 pre.next = temp; } //交换完之后,更新cur,pre pre = cur; cur = cur.next; } return tmpHead.next; } }
4、合并两个排序的链表
题目描述:
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
数据范围: 0 \le n \le 10000≤n≤1000,-1000 \le 节点值 \le 1000−1000≤节点值≤1000 要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6},转换过程如下图所示:
或输入{-1,2,4},{1,3,4}时,合并后的链表为{-1,1,2,3,4,4},所以对应的输出为{-1,1,2,3,4,4},转换过程如下图所示:
示例:
思路:
-
设置一个ListNode记录结果
-
设置一个指针pre指示待插入节点的前一个节点(即当前节点)
-
这是两个指针pre1,pre2指示list1,list2
-
选择两个链表中较小值作为pre,然后后移
代码:
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode Merge(ListNode list1,ListNode list2) { if(list1==null) return list2; if(list2==null) return list1; //head记录结果,初始化为list1,list2较小值 ListNode head = list1.val < list2.val ? list1 : list2; //pre指示下一个节点的前一个节点(即当前节点) ListNode pre = head; //cur1指示下一个节点(来自于list1或list2) ListNode cur1 = head.next; //cur2指示下一个节点(来自另一个链表) ListNode cur2 = (head == list1) ? list2 : list1; while (cur1 != null && cur2 != null) { if (cur1.val < cur2.val) { pre.next = cur1; cur1 = cur1.next; } else { pre.next = cur2; cur2 = cur2.next; } pre = pre.next; } pre.next = cur1 == null ? cur2 : cur1; return head; } }
5、合并k个已排序的链表
题目描述:
合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。
数据范围:节点总数满足 0 ≤n≤10^5, 链表个数满足 1≤k≤10^5 ,每个链表的长度满足 1≤len≤200 ,每个节点的值满足 |val| <= 1000∣val∣<=1000
要求:时间复杂度 O(nlogk)O(nlogk)
示例:
思路:
(一)利用第四题的结果,遍历数组两两比较(复杂度为n^2,不符合题目要求)
(二)把每个链表的值用数组存起来,然后sort之后重新构建链表
代码:
//方法一 public ListNode mergeKLists(ArrayList<ListNode> lists) { if(lists.size() == 0) return null; ListNode head = lists.get(0); for (int i = 1; i < lists.size(); i ++ ) head = merge2Lists(head, lists.get(i)); return head; } public static ListNode merge2Lists(ListNode head1, ListNode head2) { ListNode dummy = new ListNode(0), p = dummy; while (head1 != null && head2 != null) { if(head1.val < head2.val) { p.next = head1; head1 = head1.next; } else { p.next = head2; head2 = head2.next; } p = p.next; } p.next = (head1 == null) ? head2 : head1; return dummy.next; } //方法二 public ListNode mergeKLists(ArrayList<ListNode> lists) { ListNode dummyHead = new ListNode(-1); ListNode cur = dummyHead; int num = 0; // 时间复杂度n 得到节点个数 for(ListNode node : lists){ while(node!= null){ node = node.next; num++; } } //初始化一个大小为num的数组来存所有的节点值 int[] arr = new int[num]; int i = 0; for(ListNode node : lists){ while(node!= null){ arr[i++] = node.val; node = node.next; } } // 复杂度nlog(n) 对数组排序 Arrays.sort(arr); //构造新链表并返回 for(int j = 0; j < num; j++){ ListNode node = new ListNode(arr[j]); cur.next = node; cur = cur.next; } return dummyHead.next; }
6、判断链表中是否有环
题目描述:
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
数据范围:链表长度 0 \le n \le 100000≤n≤10000,链表中任意节点的值满足 |val| <= 100000∣val∣<=100000
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
输入分为两部分,第一部分为链表,第二部分代表是否有环,然后将组成的head头结点传入到函数里面。-1代表无环,其它的数字代表有环,这些参数解释仅仅是为了方便读者自测调试。实际在编程时读入的是链表的头节点。
例如输入{3,2,0,-4},1时,对应的链表结构如下图所示:
可以看出环的入口结点为从头结点开始的第1个结点(注:头结点为第0个结点),所以输出true。
示例:
思路:
思路一:双指针(快慢指针)
-
快指针走两步
-
慢指针走一步
-
若相遇说明有环(因为快指针走两步,如果有环的话,快指针会追上慢指针,两个指针会相遇)
-
否则快指针走到链表尾部,说明无环
思路二:哈希表(HashSet)
-
遍历链表
-
一旦遇到了之前的节点,就可以判断链表中存在环
-
否则将该节点记录下来
-
遍历到表尾说明无环
思路三:逐个删除
-
判断head.next==head
-
若等于说明有环
-
否则说明到该节点都是无环的,就删除该节点
-
递归下一个节点直至该节点为空或没有下一个节点
代码:
思路一代码:
public class Solution { 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; } //fast走到末尾,说明无环 return false; } }
思路二代码:
public class Solution { public boolean hasCycle(ListNode head) { //思路:哈希表 //遍历链表,将每个节点记录下来,一旦遇到之前遍历的节点,就可以判断链表中存在环 // 使用set来记录出现的结点 HashSet<ListNode> set = new HashSet<>(); while(head != null){ // 当set中包含结点,说明第一次出现重复的结点,即环的入口结点 if(set.contains(head)){ return true; } // set中加入未重复的结点 set.add(head); head = head.next; } return false; } }
思路三代码:
public class Solution { public boolean hasCycle(ListNode head) { //思路:删除法 //所谓删除,就是让它的next指向它自己,若有环,一定存在它的next就是它本身 //严谨性判断 if(head==null || head.next == null) return false; //如果出现head.next = head表示有环 if(head.next == head) return true; ListNode nextNode = head.next; //删除节点(它的下一个指针没指向自己,说明目前没换,那就让next指向自己,把这个没换的节点删掉) head.next = head; //递归查看下一个节点 return hasCycle(nextNode); } }
7、链表中环的入口节点
题目描述:
给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
数据范围: n\le10000n≤10000,1<=结点值<=100001<=结点值<=10000
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:
可以看到环的入口结点的结点值为3,所以返回结点值为3的结点。
输入描述:
输入分为2段,第一段是入环前的链表部分,第二段是链表环的部分,后台会根据第二段是否为空将这两段组装成一个无环或者有环单链表
返回值描述:
返回链表的环的入口结点即可,我们后台程序会打印这个结点对应的结点值;若没有,则返回对应编程语言的空结点即可。
示例:
思路:
思路一:快慢指针
-
先判断是否有环
-
无环返回null
-
否则,让快指针回到头,然后与慢指针一起每次向后移动一次
-
再次相遇点即为所求
思路二:hash法
-
用HashSet记录出现的节点
-
遍历链表,判断当前节点是否包含在当前集合中
-
若在,则返回该节点
-
否则,将节点加入集合中
代码:
思路一代码:
public class Solution { //判断链表是否为空 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(slow == fast) return slow; } //无环返回null 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; } }
思路二代码:
public ListNode EntryNodeOfLoop(ListNode pHead) { // 使用set来记录出现的结点 HashSet<ListNode> set = new HashSet<>(); while(pHead != null){ // 当set中包含结点,说明第一次出现重复的结点,即环的入口结点 if(set.contains(pHead)){ return pHead; } // set中加入未重复的结点 set.add(pHead); pHead = pHead.next; } return null; }
8、链表中倒数最后k个节点
题目描述:
输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。
数据范围:0 \leq n \leq 10^50≤n≤105,0 \leq a_i \leq 10^90≤a**i≤109,0 \leq k \leq 10^90≤k≤109
要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
例如输入{1,2,3,4,5},2时,对应的链表结构如下图所示:
其中蓝色部分为该链表的最后2个结点,所以返回倒数第2个结点(也即结点值为4的结点)即可,系统会打印后面所有的节点来比较。
示例:
思路:
思路一:快慢指针
-
快指针先走k步
-
若k步之前fast已经到链尾了,返回null
-
否则快慢指针一起往后走
-
若fast走到链表尾部,返回slow
思路二:先找长度,再返回n-k个节点
-
遍历链表求长度n
-
若n<k,返回null
-
否则返回第n-k个节点
思路三:利用栈
-
遍历链表,将元素压栈
-
若栈的元素小于k,返回null
-
否则让前k个元素出栈构造链表返回
代码:
思路一代码
public ListNode FindKthToTail (ListNode pHead, int k) { // write code here //快慢指针 ListNode fast = pHead; ListNode slow = pHead; //快指针先走k步 for(int i = 0; i < k; i++){ //判断k是不是在链表长度范围内 if(fast != null) fast = fast.next; //达不到k步说明链表过短,没有倒数k else return slow = null; } //双指针一起走 while(fast != null){ fast = fast.next; slow = slow.next; } return slow; }
思路二代码
public ListNode FindKthToTail (ListNode pHead, int k) { // write code here //先找长度n,如果k小于长度,返回第n-k个节点 int n = 0; ListNode p = pHead; //遍历链表,统计链表长度 while(p != null){ n++; p = p.next; } //长度过小,返回空链表 if(n < k) return null; p = pHead; //遍历n-k次,返回第n-k个节点 for(int i=0;i<n-k;i++) p = p.next; return p; }
思路三代码
public ListNode FindKthToTail (ListNode pHead, int k) { // write code here //将链表元素压栈,最后返回栈顶前k个元素构造链表 if(pHead == null || k == 0) return null; Stack<ListNode> stack = new Stack<>(); //链表节点压栈 while(pHead != null){ stack.push(pHead); pHead = pHead.next; } //判断栈元素是否小于k if(stack.size() < k) return null; //前k个元素出栈构造链表 ListNode firstNode = stack.pop(); while(--k>0){ //将出栈元素重新连接成链表 ListNode temp = stack.pop(); temp.next = firstNode; firstNode = temp; } return firstNode; }
9、删除链表中的倒数第n个节点
题目描述:
给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针 例如,
给出的链表为: 1\to 2\to 3\to 4\to 51→2→3→4→5, n= 2n=2. 删除了链表的倒数第 nn 个节点之后,链表变为1\to 2\to 3\to 51→2→3→5.
数据范围: 链表长度 0\le n \le 10000≤n≤1000,链表中任意节点的值满足 0 \le val \le 1000≤val≤100
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n) 备注:
题目保证 nn 一定是有效的
示例:
思路:
思路一:快慢指针
-
设置前置节点方便处理头节点,设置快慢指针找到n的位置,设置慢指针的前置节点方便删除第n个节点
-
快慢指针都指向原始头节点,fast先走n步,之后与slow一起走,当fast走到链表尾,slow所指位置即为n个节点
-
删除第n个节点
-
返回链表
思路二:长度统计法
-
计算链表长度length
-
找到length-n的位置
-
删除该位置上的节点
-
返回链表
代码:
思路一代码
public ListNode removeNthFromEnd (ListNode head, int n) { // write code here //双指针 //添加表头,避免head为空的情况 ListNode res = new ListNode(-1); res.next = head; //快慢指针 ListNode fast = head; ListNode slow = head; //慢指针的前序节点 删除时,让它的next指向slow的next ListNode pre = res; //快指针先走n步 while(n!=0){ fast = fast.next; n--; } //快慢指针同步,快指针走到尾,慢指针所指即为要删除的节点n while(fast!=null){ fast = fast.next; pre = slow; slow = slow.next; } //删除该位置节点 pre.next = slow.next; //返回链表 return res.next; }
思路二代码
public ListNode removeNthFromEnd (ListNode head, int n) { // write code here //长度统计法 //记录链表长度 int length = 0; //添加表头 ListNode res = new ListNode(-1); res.next = head; //当前节点 ListNode cur = head; //前序节点 ListNode pre = res; //找到链表长度 while(cur!=null){ length++; cur = cur.next; } //回到头部 cur = head; //从头开始遍历,找到倒数第n个位置 for(int i=0;i<length-n;i++){ pre = cur; cur = cur.next; } //删除倒数第n个节点 pre.next = cur.next; //返回去掉头节点 return res.next; }
10、两个链表的第一个公共结点
题目描述:
输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
数据范围: n \le 1000n≤1000 要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
例如,输入{1,2,3},{4,5},{6,7}时,两个无环的单向链表的结构如下图所示:
可以看到它们的第一个公共结点的结点值为6,所以返回结点值为6的结点。
输入描述:
输入分为是3段,第一段是第一个链表的非公共部分,第二段是第二个链表的非公共部分,第三段是第一个链表和第二个链表的公共部分。 后台会将这3个参数组装为两个链表,并将这两个链表对应的头节点传入到函数FindFirstCommonNode里面,用户得到的输入只有pHead1和pHead2。
返回值描述:
返回传入的pHead1和pHead2的第一个公共结点,后台会打印以该节点为头节点的链表。
示例:
思路:
思路一:双指针
-
先计算两链表长度
-
让较长的链表先走长度差步
-
然后两链表一起向后走
-
有公共节点时停下
思路二:双指针连接法
-
p1接上p2,p2接上p1,这样两链表就一样长了。(注意,这里的连接并不需要真的连接链表,只是再第一次遍历,链表走到尾时让它指向另一个链表头)
-
同时移动两链表
-
第一次相同时,返回
思路三:集合法
-
将链表1中的节点存入集合
-
遍历链表2,返回第一个包含在集合中的节点
思路四:栈
-
将两链表压栈
-
比较栈顶元素,相等则出栈,并记录栈顶元素
-
直到不相等时,返回上一个栈顶元素
代码:
思路一代码
public class Solution { //计算链表长度的函数 public int ListLength(ListNode pHead){ int n = 0; while(pHead != null){ n++; pHead = pHead.next; } return n; } public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { //双指针 //先遍历两个链表,求两个链表长度 //较长的链表先从头走两链表长度差步 //两链表在同步向后遍历,遇到的第一个相同节点即为第一个公共节点 int p1 = ListLength(pHead1); int p2 = ListLength(pHead2); //当链表1更长时,链表1指针先走p1-p2步 if(p1>p2){ int n = p1-p2; for(int i = 0; i < n; i++) pHead1 = pHead1.next; //两链表同时移动,直到遇到公共节点 while((pHead1 != null) && (pHead2 != null) && (pHead1 != pHead2)){ pHead1 = pHead1.next; pHead2 = pHead2.next; } } else{ int n = p2 - p1; for(int i = 0; i < n; i++) pHead2 = pHead2.next; while((pHead1 != null) && (pHead2 != null) && (pHead1 != pHead2)){ pHead1 = pHead1.next; pHead2 = pHead2.next; } } return pHead1; } }
思路二代码
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { //双指针连接法 if(pHead1==null || pHead2==null) return null; ListNode p1 = pHead1; ListNode p2 = pHead2; //p1街上p2,p2接上p1,相当于遍历两次两链表的值,这里的连接不需要真的将链表连起来,至是当它们第一遍走到头后去指向另一个链表头即可 while(p1 != p2){ p1 = p1 == null ? pHead2 : p1.next; p2 = p2 == null ? pHead1 : p2.next; } return p1; }
思路三代码
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { //集合 //将第一个链表的节点存到集合中,然后遍历第二个链表,返回第一个包含再集合中的节点 //创建集合 Set set = new HashSet<ListNode>(); //先把链表1的节点全部存到集合set中 while(pHead1 != null){ set.add(pHead1); pHead1 = pHead1.next; } //然后访问链表2的节点,判断集合中是否包含链表2的节点,如果包含就返回 while(pHead2 != null){ if(set.contains(pHead2)) return pHead2; pHead2 = pHead2.next; } //如果集合set不包含链表2的任何一个节点,说明没有交点,直接返回null return null; }
思路四代码
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { //栈 //将两个链表的节点压栈 //比较栈顶元素,同时记录每个栈的上一个栈顶元素 //当两个栈栈顶元素不相等时,返回上一个栈顶元素 Deque<ListNode> d1 = new ArrayDeque<>(); Deque<ListNode> d2 = new ArrayDeque<>(); //两链表节点压栈 while(pHead1!=null){ d1.add(pHead1); pHead1 = pHead1.next; } while(pHead2!=null){ d2.add(pHead2); pHead2 = pHead2.next; } ListNode ans = null; while(!d1.isEmpty() && !d2.isEmpty() && d1.peekLast() == d2.peekLast()){ ans = d1.pollLast(); d2.pollLast(); } return ans; }
11、链表相加(二)
题目描述:
假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
数据范围:0 \le n,m \le 10000000≤n,m≤1000000,链表任意值 0 \le val \le 90≤val≤9 要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。
示例:
思路:
思路一:反转链表
-
将两个链表翻转
-
将链表元素相加
-
将最后结果翻转回来
思路二:栈
-
将两链表元素值压栈
-
取两站顶元素出栈相加
-
构造结果链表
代码:
思路一代码
//反转链表 public ListNode ReverseList(ListNode pHead){ if(pHead == null) return null; //指示当前节点 ListNode cur = pHead; //指示前置节点 ListNode pre = null; while(cur != null){ //断开链表,要记录后续一个 ListNode temp = cur.next; //当前节点的下一个指向前一个节点 cur.next = pre; //前一个节点更新为当前节点 pre = cur; //当前节点更新为暂存起来的temp cur = temp; } return pre; } 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 addInList (ListNode head1, ListNode head2) { // write code here //栈 //用两个栈存两个链表元素 Stack<Integer> s1 = new Stack(); Stack<Integer> s2 = new Stack(); //将链表元素值入栈 while(head1!=null){ s1.push(head1.val); head1 = head1.next; } while(head2!=null){ s2.push(head2.val); head2 = head2.next; } //记录返回链表 ListNode res = null; //进位符 int cnt = 0; //链表相加 while(!s1.empty() || !s2.empty()){ //操作数,从两栈顶取 int x1 = s1.isEmpty() ? 0 : s1.pop(); int x2 = s2.isEmpty() ? 0 : s2.pop(); int sum = x1 + x2 + cnt; cnt = sum / 10; //进行当前节点的插入 ListNode tempNode = new ListNode(sum%10); tempNode.next = res; res = tempNode; } //两个栈的元素都取完了,但产生了进位 if(cnt > 0){ ListNode tempNode = new ListNode(cnt); tempNode.next = res; res = tempNode; } return res; }
12、单链表的排序
题目描述:
给定一个节点数为n的无序单链表,对其按升序排序。
数据范围:0 < n \le 1000000<n≤100000
要求:时间复杂度 O(nlogn)O(nlogn)
示例:
思路:
思路一:归并排序
-
先判断链表为空或只有一个元素,这种情况直接返回
-
准备三个指针,快指针right每次走两步,慢指针mid每次走一步,前序指针left每次跟在mid前一个位置。三个指针遍历链表,当快指针到达链表尾部的时候,从left位置将链表断开,刚好分成两个子问题开始递归。
-
从left位置将链表断开,刚好分成两个子问题开始递归
-
将子问题得到的链表合并
思路二:转化为数组排序
-
将链表元素存入数组
-
对数组排序
-
排序后的元素赋给链表(构造链表)
代码:
思路一代码:
//合并两段有序链表 ListNode merge(ListNode pHead1, ListNode pHead2){ //若一个已经为空,直接返回另一个 if(pHead1 == null) return pHead2; if(pHead2 == null) return pHead1; //加一个表头 ListNode head = new ListNode(-1); ListNode cur = head; //两个链表都要不为空 while(pHead1 != null && pHead2 != null){ //取较小值的节点 if(pHead1.val <= pHead2.val){ cur.next = pHead1; pHead1 = pHead1.next; } else{ cur.next = pHead2; pHead2 = pHead2.next; } //指针后移 cur = cur.next; } //哪个链表还有值,直接连在后面 if(pHead1 != null) cur.next = pHead1; else cur.next = pHead2; return head.next; } public ListNode sortInList (ListNode head) { // write code here //归并排序 if(head == null || head.next == null) return head; ListNode left = head; ListNode mid = head.next; ListNode right = head.next.next; //右边的指针到达末尾时,中间的指针指向该段链表的中间 while(right != null && right.next != null) { //right每次走两步 left = left.next; mid = mid.next; right = right.next.next; } //从left断开,将链表一分为二 left.next = null; //分成两段排序,合并排好序的两段 return merge(sortInList(head),sortInList(mid)); }
思路二代码
public ListNode sortInList (ListNode head) { // write code here //转化为数组排序 ArrayList<Integer> nums = new ArrayList(); ListNode p = head; //遍历链表,将节点值加入数组 while(p != null){ nums.add(p.val); p = p.next; } p = head; //对数组元素排序 Collections.sort(nums); //遍历数组 for(int i=0; i<nums.size(); i++){ //将数组元素依次加入链表 p.val = nums.get(i); p = p.next; } return head; }
13、判断一个链表是否为回文结构
题目描述:
给定一个链表,请判断该链表是否为回文结构。
回文是指该字符串正序逆序完全一致。
数据范围: 链表节点数 0 \le n \le 10^50≤n≤105,链表中每个节点的值满足 |val| \le 10^7∣val∣≤107
示例:
思路:
思路一:数组赋值反转法
-
遍历一次链表,将元素取出放入辅助数组
-
准备另一个辅助数组,录入第一个数组的全部元素,再将其反转
-
依次遍历原数组与反转之后的数组,若元素都相等则是回文串,只要遇到一个不同的就不是回文结构
思路二:双指针
-
遍历数组一次,取出所有链表元素
-
双指针一个从左到右遍历,一个从右向左遍历,依次比较元素是否相同。
-
如果不一样则返回false。否则遍历到两数组相遇。
思路三:长度法找中点
-
遍历链表,统计链表的长度
-
将长度除2,遍历找到链表中点
-
从中点位置开始,对链表后半段进行反转
-
双指针(对撞指针)比较对应元素是否相同
思路四:双指针找中点
-
快慢指针找到链表中点
-
从中点位置,开始往后将后半段链表反转
-
左右双指针依次比较遇到的值
思路五:栈
-
将链表元素压栈
-
顺序比较链表元素与栈顶元素
-
有不同返回false。若到链尾都没有不同元素相同,返回true。
代码:
思路一代码
public boolean isPail (ListNode head) { // write code here ArrayList<Integer> nums = new ArrayList(); //将链表元素取出一次放入数组 while(head!=null){ nums.add(head.val); head = head.next; } //temp装反转之后的元素 ArrayList<Integer> temp = new ArrayList(); temp = (ArrayList<Integer>) nums.clone(); //准备一个数组承接翻转之后的数组 Collections.reverse(temp); for(int i=0; i<nums.size(); i++){ int x = nums.get(i); int y = temp.get(i); //正向遍历与反向遍历相同 if(x != y) return false; } return true; }
思路二代码
public boolean isPail (ListNode head) { // write code here ArrayList<Integer> nums = new ArrayList(); //将链表元素取出一次放入数组 while(head!=null){ nums.add(head.val); head = head.next; } //双指针指向首尾 int left = 0; int right = nums.size() - 1; //对撞指针判断是否是回文串 while(left <= right){ int x = nums.get(left); int y = nums.get(rught); //如果不一致就是部位回文 if(x != y) return false; left++; right--; } return true; }
思路三代码
//反转链表指针 ListNode reverse(ListNode head){ //前序节点 ListNode prev = null; while(head != null){ //断开后序 ListNode next = head.next; //指向前序 head.next = prev; prev = head; head = next; } return prev; } public boolean isPail (ListNode head) { // write code here ListNode p = head; int n = 0; //找到链表长度 while(p!=null){ n++; p = p.next; } //中点 n = n/2; p = head; //找到中点 while(n > 0){ p = p.next; n--; } //中点处反转 p = reverse(p); ListNode q = head; while(p != null){ //比较判断节点值是否相等 if(p.val != q.val) return false; p = p.next; q = q.next; } return true; }
思路四代码
//反转链表指针 ListNode reverse(ListNode head){ //前序节点 ListNode prev = null; while(head != null){ //断开后序 ListNode next = head.next; //指向前序 head.next = prev; prev = head; head = next; } return prev; } public boolean isPail (ListNode head) { // write code here //空链表直接为回文 if(head == null) return true; //准备快慢双指针 ListNode slow = head; ListNode fast = head; //双指针找中点 while(fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next; } //中点处反转 slow = reverse(slow); fast = head; while(slow != null){ //比较判断节点值是否相等 if(slow.val != fast.val) return false; fast = fast.next; slow = slow.next; } return true; }
思路五代码
public boolean isPail (ListNode head) { // write code here //栈 ListNode p = head; Stack<Integer> s = new Stack(); //辅助记录元素 while(p != null){ s.push(p.val); p = p.next; } p = head; //正序遍历链表,从栈中弹出的内容逆序的 while(!s.isEmpty()){ //比较是否相同 if(p.val!=s.pop()) return false; p = p.next; } return true; }
14、链表的奇偶重排
题目描述:
给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。
注意是节点的编号而非节点的数值。
数据范围:节点数量满足 0 \le n \le 10^50≤n≤105,节点中的值都满足 0 \le val \le 10000≤val≤1000
要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
示例:
思路:
-
odd指示第一个节点,even指示第二个节点
-
只要even不为null,even.next指向odd的下一个节点,odd.next指向even的下一个节点
-
最后把even接到odd后面,返回head
代码:
public ListNode oddEvenList (ListNode head) { // write code here //双指针 if(head == null || head.next == null) return head; //even开头指向第二个节点,可能为空 ListNode even = head.next; //odd开头指向第一个节点 ListNode odd = head; //指向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 = even.next; } //even整体接在odd后面 odd.next = evenhead; return head; }
15、删除有序链表中的重复元素-I
题目描述:
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次 例如: 给出的链表为1\to1\to21→1→2,返回1 \to 21→2. 给出的链表为1\to1\to 2 \to 3 \to 31→1→2→3→3,返回1\to 2 \to 31→2→3.
数据范围:链表长度满足 0 \le n \le 1000≤n≤100,链表中任意节点的值满足 |val| \le 100∣val∣≤100
进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
示例:
思路:
-
因为链表有序,只需比较相同节点值是否相同
-
若相同,就删除该节点
-
直到遍历完链表
代码:
public ListNode deleteDuplicates (ListNode head) { // write code here if(head==null || head.next==null) return head; ListNode pre = head; while(pre.next!=null){ if(pre.val==pre.next.val) pre.next = pre.next.next; else pre=pre.next; } return head; }
16、删除有序链表中的重复元素-II
题目描述:
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。 例如: 给出的链表为1 \to 2\to 3\to 3\to 4\to 4\to51→2→3→3→4→4→5, 返回1\to 2\to51→2→5. 给出的链表为1\to1 \to 1\to 2 \to 31→1→1→2→3, 返回2\to 32→3.
数据范围:链表长度 0 \le n \le 100000≤n≤10000,链表中的值满足 |val| \le 1000∣val∣≤1000
要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
示例:
思路:
与上一题不同的是,重复的元素要全部删掉,这就需要先判断是否有重复元素,如果有就全跳过。
代码:
public ListNode deleteDuplicates (ListNode head) { // write code here //空链表 if(head == null) return null; ListNode res = new ListNode(0); //在链表前加一个表头 res.next = head; ListNode cur = res; 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 res.next; }