160. 相交链表
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 1:
输入: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 个节点。
注意:
- 如果两个链表没有交点,返回 null.
- 在返回结果后,两个链表仍须保持原有的结构。
- 可假定整个链表结构中没有循环。
- 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
题解
方法一:
用两个指针分别获取两个链表得长度,得到长度差n
。
长链表的指针从头结点跑n
步,短链表的指针指向短链表头指针,之后两个指针同步向后跑,直到两个指针相交。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//1. 计算两个链表的长度之差
ListNode p1 = headA;
ListNode p2 = headB;
int n = 0;
while (p1!=null){
p1 = p1.next;
n++;
}
while (p2!=null){
p2 = p2.next;
n--;
}
//p1指向长链,p2指向短链
if (n>0){
p1 = headA;
p2 = headB;
}else {
p1 = headB;
p2 = headA;
}
n = Math.abs(n);
//长链先走n步
for (int i = 0; i<n; i++){
p1 = p1.next;
}
//p1,p2一起走
while(p1!=p2){
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
}
方法二:
更简洁的方法先使得长链表指针到达离末端的距离与短链表长度相同。
p1, p2分别指向链表headA和headB,若p1先跑完,则p1指向headB, 若p2跑完,则将p2指向headA,直到p1与p2相同。
例如对于链表:A={1,3,5,7,9,11}
和 B={2,4,9,11}
,相交于结点 9
, 两个链表长度相差为2. 那么链表B少跑两个结点。
指针p2在链表B先跑完,将p2重置向A, p1,p2继续同步向后跑,指针p1在A中会跑完多的两个结点,同时p2在B中也会同步跑2个结点,此时p2指向的位置正好是比链表A比链表B长出的那一部分。最后p1指向headB,p1和p2此时离末尾的距离相同,只需要同步向后走直到遇到交点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1 = headA;
ListNode p2 = headB;
while (p1!=p2){
p1 = p1==null? headB: p1.next;
p2 = p2==null? headA: p2.next;
}
return p1;
}
}
时间复杂度O(m+n)
空间复杂度O(1)
206. 反转链表(简单)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/probl...
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
解答:
1. 迭代版
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode curr = head;
ListNode prev = null;
ListNode temp = null;
while (curr!=null){
temp = curr.next;
curr.next = prev;
prev = curr;
curr = temp;
}
return prev;
}
}
时间复杂度O(n)
空间复杂度O(1)
2. 递归版
class Solution {
public ListNode reverseList(ListNode head) {
if (head==null || head.next==null){
return head;
}
ListNode res = reverseList(head.next); //反转链表head.next
//最后将head反转
head.next.next = head;
head.next = null;
return res;
}
}
时间复杂度O(n)
空间复杂度O(n)
- 合并两个有序链表
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
题解:类似于归并排序的归并部分
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(-1);
ListNode p = head;
while (l1!=null && l2!=null){
if (l1.val<=l2.val){
p.next = l1;
p = p.next;
l1 = l1.next;
}
else{
p.next = l2;
p = p.next;
l2 = l2.next;
}
}
//有且仅有一个列表没有遍历完
p.next = l1 == null ? l2 : l1;
return head.next;
}
}
时间复杂度:O(m+n)
空间复杂度:O(1)
83. 删除排序链表中的重复元素 (简单)
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
题解:
1. 双指针
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null){
return head;
}
ListNode pre = head;
ListNode curr = pre.next;
while (curr != null){
if (curr.val != pre.val){
pre.next = curr;
pre = pre.next;
}
curr = curr.next;
}
pre.next = null;
return head;
}
}
2. 单指针*
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode curr = head;
while (curr !=null && curr.next != null){
if (curr.val == curr.next.val){
curr.next = curr.next.next; //若当前结点与下一个节点值相等,跳过中间相同的结点
}else{
curr = curr.next; //否则不跳过
}
}
return head;
}
}
时间复杂度O(n)
空间复杂度O(1)
19. 删除链表的倒数第N个节点 (中等)
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
题解:一趟扫描法
使用两个指针,指针p1先走n步,另一个指针p2才从头节点开始同步向后走,此时两个指针间隔n个指针,当p1走到末尾时,p2恰好在倒数第n+1个结点上,删除掉倒数第n个结点即可。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
ListNode curr = head;
while(curr!=null){
curr = curr.next;
if (n != 0){ //curr先走n步
n--;
}else{ //curr走了n步之后,pre开始走
pre = pre.next;
}
}
pre.next = pre.next.next;
return dummy.next;
}
}
时间复杂度O(L),L表示链表长度
空间复杂度O(1)
82. 删除排序链表中的重复元素 II
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:
输入: 1->1->1->2->3
输出: 2->3
题解
解法一:尾插不重复的节点
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode();
ListNode tail = dummy;
// 找不重复的节点,将不重复的节点尾插入tail后面
// 用双指针L, R记录重复区间[i,j), 若[i,j)长度为1,则i为不重复节点,尾插!
// 若若[i,j)长度不为1, 则i是重复节点,不用尾插
for (ListNode L = head, R = head; R != null; L = R){
while (R!= null && L.val == R.val){
R = R.next;
}
if (L.next == R){ // L的下一个节点就是R,那么L不是重复元素,尾插
tail.next = L;
tail = L;
tail.next = null;
}
}
return dummy.next;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
解法二:删除重复节点
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy, cur = head;
while (cur != null && cur.next != null) {
if (cur.val != cur.next.val) {
if (pre.next != cur) pre.next = cur.next;
else pre = pre.next;
}
cur = cur.next;
}
if (pre.next != cur) pre.next = cur.next;
return dummy.next;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
24. 两两交换链表中的节点 (中等)
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
题解:
1. 迭代
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
ListNode curr = dummy.next;
while (curr != null && curr.next != null){
ListNode temp = curr.next;
curr.next = curr.next.next;
temp.next = curr;
pre.next = temp;
pre = curr;
curr = curr.next;
}
return dummy.next;
}
}
时间复杂度O(n)
空间复杂度O(1)
2. 递归
//递归
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null){
return head;
}
ListNode first = head;
ListNode second = head.next;
first.next = swapPairs(second.next); //第一个结点的下一个结点为第二个结点后面结点交换后的检点
second.next = first; //第二个结点指向第一个结点
return second; //现在第二个结点为头结点,因此返回第二个结点
}
}
时间复杂度: O(n)
空间复杂度:O(n)
445. 两数相加 II * (中等)
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
题解:
1. 借用栈
用两个栈分别存放两个链表的数值,每次计算对应数位和时,弹出栈顶元素后累加。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
//l1入栈
ListNode p = l1;
while (p != null){
stack1.push(p.val);
p = p.next;
}
//l2入栈
p = l2;
while (p != null){
stack2.push(p.val);
p = p.next;
}
//栈顶元素相加
int carry = 0; // 进位
while (!stack1.isEmpty() || !stack2.isEmpty() || carry!=0){
//两数相加,再加上进数
int sum = 0;
if (!stack1.isEmpty()){
sum += stack1.pop();
}
if (!stack2.isEmpty()){
sum += stack2.pop();
}
sum += carry;
//求余数
//插入新节点
int remainder = sum % 10;
ListNode s = new ListNode(remainder);
s.next = dummy.next;
dummy.next = s;
carry = sum/10; //下一轮进位
}
return dummy.next;
}
}
时间复杂度O(n+m)
空间复杂度O(n+m)
234. 回文链表*(中等)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/probl...
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
解答:
1. 借用栈:
考虑到栈结构出栈时是逆序的,因此借助栈。
扫描一遍链表,并将数值存放到栈中;
再扫描一遍链表,每扫描一个结点查看是否与栈顶元素相等,并弹出一个元素。若每次都相等,则是回文链表。
空间复杂度O(n)
2. 栈 + 快慢指针:
链表后半段入栈,并将链表前半段与栈比较。
(找中点的方式,快指针一次走两步,慢指针一次走一步)
空间复杂度O(n/2)
3. 快慢指针 + 反转链表:
链表后半段逆序,比较两个链表是否相等。
链表后半段再次还原回来。
注意的地方:
首先不管链表结点个数是奇数个还是偶数个,后部分头结点都是p1后一个结点,因此p1确定了,后半部分就确定了。
其次当链表结点为奇数个时,那么前半部分链表长度比后半部分多1,但在比对时,不用考虑前半部分最后一个结点,只要后半部分链表指针到了结尾,那么就算比对成功。
时间复杂度O(n+n/2) = O(n);
只用到了常数个指针,因此空间复杂度为O(1).
JAVA代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
//定义一个快指针和一个慢指针
ListNode p1 = head;
ListNode p2 = head;
if (head == null || head.next == null) return true;
//找到中点,p1指向中点, p2指向中点的后面一个结点
while(p2.next!=null && p2.next.next!=null){
p1 = p1.next;
p2 = p2.next.next;
}
p2 = p1.next;
p1.next = null;
//反转链表后半部分,rev为反转后的链表
ListNode p3 = null;
while (p2!=null){
ListNode temp = p2.next;
p2.next = p3;
p3 = p2;
p2 = temp;
}
ListNode rev = p3;
//比较两个链表
p1 = head;
while(p3!=null){
if ( p1.val == p3.val){
p1 = p1.next;
p3 = p3.next;
}
else break;
}
if (p3 == null){
return true;
}else{
return false;
}
}
}
时间复杂度O(n)
空间复杂度O(1)
725. 分隔链表* (中等)
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
示例 1:
输入:
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, 它代表了最后一个部分为空链表。
示例 2:
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
提示:
root 的长度范围: [0, 1000].
输入的每个节点的大小范围:[0, 999].
k 的取值范围: [1, 50].
题解:
- 构造新的链表
先求出链表长度n
, 则数组中每条链表的长度至少为n/k
, 若n%k = rem>0
, 则前rem个数组长度再加1
,为len+1
. 构建新链表,将相应长度的节点添加到链表中即可。
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
ListNode[] res = new ListNode[k];
int n = 0;
ListNode curr = root;
while(curr!=null){
curr = curr.next;
n++;
}
int len = n / k;
int rem = n % k; //前rem个链表的长度为len+1, 其余链表长度为len
//数组中添加链表
curr = root;
for (int i = 0; i < k; i++){
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
for (int j = 0; j < len + (rem>0?1: 0); j++){ //每个链表中添加节点
if (curr!=null){
p = p.next = new ListNode(curr.val);
curr = curr.next;
}
else p.next = null; //若原链表分隔完,将空链表放入未加入链表的数组中
}
res[i] = dummy.next;
rem--; //构造好一条链表,rem减1
}
return res;
}
}
时间复杂度O(n+k),若k比较大,最多要新加k-1个空节点
空间复杂度O(max(n+k))
2. 在原链表上分隔
curr指向当前节点,走了对应长度后断开。
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
ListNode[] res = new ListNode[k];
ListNode curr = root;
int n = 0;
while(curr!=null){
curr = curr.next;
n++;
}
int len = n/k;
int rem = n%k;
curr = root;
for (int i=0; i<k; i++){
res[i] = curr;
for(int j =0; j< len + (rem>0?1: 0)-1; j++){
if (curr!=null){
curr = curr.next;
}
}
//断开
if (curr!=null){
ListNode temp = curr.next;
curr.next = null;
curr = temp;
}
rem--;
}
return res;
}
}
时间复杂度 O(n+k), 若k比较大,最多要新加k-1个空节点
空间复杂度O(k)
328. 奇偶链表 (中等)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/probl...
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 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
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
题解
将奇节点放在一个链表里,偶节点放在另一个链表里。然后把偶链表接在奇链表的尾部。
public class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null) return null;
ListNode odd = head;
ListNode even_head = head.next;
ListNode even = even_head;
while (even != null && even.next != null) { //注意边界条件
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = even_head;
return head;
}
}
时间复杂度O(n)
空间复杂度O(1)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。