文章目录
- 1、002——[两数相加](https://leetcode-cn.com/problems/add-two-numbers/)
- 2、021——[合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/)
- 3、024——[两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/)
- 4、025——[ K 个一组翻转链表](https://leetcode-cn.com/problems/reverse-nodes-in-k-group/)
- 5、061——[旋转链表](https://leetcode-cn.com/problems/rotate-list/)
- 6、076——[最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/)
- 7、082——[删除排序链表中的重复元素 II](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/)
- 8、083——[ 删除排序链表中的重复元素](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/)
- 9、086——[分隔链表](https://leetcode-cn.com/problems/partition-list/)
- 10、092——[反转链表 II](https://leetcode-cn.com/problems/reverse-linked-list-ii/)
- 11、138——[复制带随机指针的链表](https://leetcode-cn.com/problems/copy-list-with-random-pointer/)
- 12、143——[重排链表](https://leetcode-cn.com/problems/reorder-list/)
- 13、147——[对链表进行插入排序](https://leetcode-cn.com/problems/insertion-sort-list/)
- 14、148——[排序链表](https://leetcode-cn.com/problems/sort-list/)
- 15、160——[相交链表](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/)
- 16、203——[移除链表元素](https://leetcode-cn.com/problems/remove-linked-list-elements/)
- 17、206——[反转链表](https://leetcode-cn.com/problems/reverse-linked-list/)
- 18、234——[回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/)
- 19、237——[删除链表中的节点](https://leetcode-cn.com/problems/delete-node-in-a-linked-list/)
- 20、328——[奇偶链表](https://leetcode-cn.com/problems/odd-even-linked-list/)
1、002——两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
/**
* 思路:从前往后遍历,求和,进位,用指针curr连接,最后记得判断temp进位是否为0,
* 不为0需要进位
*/
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = new ListNode(0);
ListNode h1 = l1, h2 = l2, curr = head;
int temp = 0, num1, num2, sum;
while (h1 != null || h2 != null) {
num1 = h1 == null ? 0 : h1.val;
num2 = h2 == null ? 0 : h2.val;
sum = num1 + num2 + temp;
temp = sum / 10;
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (h1 != null) {
h1 = h1.next;
}
if (h2 != null) {
h2 = h2.next;
}
}
if (temp != 0) {
curr.next = new ListNode(temp);
}
return head.next;
}
2、021——合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
/**
* 思路:双指针,分别指向两个链表,然后从左到右比较,如果哪个小,
* 就把那个加到新链表中,同时它的指向后移,直到其中一个遍历完毕,
* 把另一个链表剩余部分,加到新链表尾部
*/
public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(0);
ListNode h1 = l1, h2 = l2, curr = head;
while (h1 != null && h2 != null) {
if (h1.val < h2.val) {
curr.next = new ListNode(h1.val);
h1 = h1.next;
} else {
curr.next = new ListNode(h2.val);
h2 = h2.next;
}
curr = curr.next;
}
if (h1 == null) {
curr.next = h2;
}
if (h2 == null) {
curr.next = h1;
}
return head.next;
}
/**
* 思路: 递归,先确定递归终止条件,当l1为null,返回l2,当l2为null返回l1
* 每一层都返回排好序的链表头
* 如果l1小,就把l1.next指向排好序的表头
*/
public static 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;
}
}
3、024——两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
/**
* 递归法
* 1、返回值
* 2、调用单元做了什么
* 3、终止条件
*
* 在本题中
* 1、返回值:交换完成的子链表
* 2、调用单元:设需要交换的两个节点为head和next,head连接后面交换完成的子链表,next
* 连接head,完成交换
* 3、终止条件:head为null或者next为null
*/
public ListNode swapPairs1(ListNode head) {
if (null == head || null == head.next) {
return head;
}
ListNode next = head.next;
head.next = swapPairs1(next.next);
next.next = head;
return next;
}
/**
* 非递归
*/
public ListNode swapPairs2(ListNode head) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode temp = pre;
while (temp.next != null && temp.next.next != null) {
// 第一个节点
ListNode start = temp.next;
// 第二个节点
ListNode end = temp.next.next;
// 把上一组第二个节点的next指向新一组交换后的第一个节点
temp.next = end;
// 前一个的next,指向后一个的next(保证链表不死循环)
start.next = end.next;
// 交换 把后一个节点指向前一个
end.next = start;
// 更新temp值
temp = start;
}
return pre.next;
}
4、025—— K 个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
进阶:
你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
/**
* 思路:遍历k个节点,找到k个节点的首尾,然后反转,将pre节点指向反转后的头结点
* 将反转后的节点指向待反转部分的头结点,然后更新pre和end的位置
*/
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
ListNode end = dummy;
while (end.next != null) {
for (int i = 0; i < k && end != null; i++) {
end = end.next;
}
if (end == null) {
break;
}
ListNode start = pre.next;
ListNode next = end.next;
end.next = null;
pre.next = reverseList(start);
start.next = next;
pre = start;
end = pre;
}
return dummy.next;
}
/**
* 反转链表
*/
private ListNode reverseList(ListNode head) {
//单链表为空或只有一个节点,直接返回原单链表
if (head == null || head.next == null) {
return head;
}
//前一个节点指针
ListNode preNode = null;
//当前节点指针
ListNode curNode = head;
while (curNode != null) {
ListNode temp = curNode.next;
curNode.next = preNode;
preNode = curNode;
curNode = temp;
}
return preNode;
}
5、061——旋转链表
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
/**
* 思路: 遍历链表,确定链表长度len,同时找到队尾,如果k为的整数倍,直接返回无需移动
* 如果不是,找到len - 1 - (k % len)位置的前一个元素,也就是新的队尾,将队尾next置null
* 将旧队尾指向旧表头,返回新表头
*
* 为什么len - 1 - (k % len)的下标就是新的表头呢?
* 因为由题可知,旋转完的数据的新表头就是原链表倒数第k个元素,
* 也就是len - 1 - (k % len)
*/
public static ListNode rotateRight(ListNode head, int k) {
if (head == null || head.next == null || k == 0) {
return head;
}
ListNode curr = head;
ListNode tail = head;
int len = 0;
while (curr != null) {
len++;
if (curr.next == null) {
tail = curr;
}
curr = curr.next;
}
if (k % len == 0) {
return head;
}
ListNode oldHead = head;
ListNode newTail = head;
int index = len - (k % len) - 1;
int i = 0;
while (i++ < index) {
newTail = newTail.next;
}
ListNode newHead = newTail.next;
newTail.next = null;
tail.next = oldHead;
return newHead;
}
6、076——最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
public String minWindow(String s, String t) {
char[] chars = s.toCharArray(), chart = t.toCharArray();
int n = chars.length, m = chart.length;
int[] hash = new int[128];
for (char ch : chart) hash[ch]--;
String res = "";
for (int i = 0, j = 0, cnt = 0; i < n; i++) {
hash[chars[i]]++;
if (hash[chars[i]] <= 0) cnt++;
while (cnt == m && hash[chars[j]] > 0) hash[chars[j++]]--;
if (cnt == m)
if (res.equals("") || res.length() > i - j + 1)
res = s.substring(j, i + 1);
}
return res;
}
7、082——删除排序链表中的重复元素 II
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
/**
* 思路:判断当前元素和其后面的元素值是否相同,不相同向后遍历,
* 相同的话,while遍历找到与当前元素不相同的元素
*/
public ListNode deleteDuplicates(ListNode head) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode curr = pre;
while (curr.next != null && curr.next.next != null) {
if (curr.next.val == curr.next.next.val) {
// 记录第一个重复节点的位置
ListNode temp = curr.next;
// while循环把之后相同元素全移除
while (temp.next != null && temp.val == temp.next.val) {
temp = temp.next;
}
curr.next = temp.next;
} else {
curr = curr.next;
}
}
return pre.next;
}
8、083—— 删除排序链表中的重复元素
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
/**
* 思路: 比较当前元素和他下一个元素的值,如果不相等继续向后遍历
* 如果相等就把当前元素的指针指向它下一个的后一个元素,继续比较
*/
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;
}
9、086——分隔链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
/**
* 思路:构造两个头结点,一个用来存放比目标数小的节点,另一个存放大于等于
* 目标值的节点,然后再将两个连起来
*/
public static ListNode partition(ListNode head, int x) {
ListNode small = new ListNode(0);
ListNode big = new ListNode(0);
ListNode smallCurr = small;
ListNode bigCurr = big;
ListNode curr = head;
while (curr != null) {
if (curr.val < x) {
smallCurr.next = curr;
smallCurr = smallCurr.next;
} else {
bigCurr.next = curr;
bigCurr = bigCurr.next;
}
curr = curr.next;
}
smallCurr.next = big.next;
bigCurr.next = null;
return small.next;
}
10、092——反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
来源:力扣(LeetCode)
/**
* 思路:使用栈将需要旋转区间的元素存起来,然后才拿出来,后进先出,倒序
*/
public ListNode reverseBetween(ListNode head, int left, int right) {
Stack<ListNode> stack = new Stack<>();
ListNode before = new ListNode(-1);
ListNode after = new ListNode(-1);
ListNode curr = head, currBefore = before, currAfter = after;
int count = 0;
while (curr != null) {
count++;
if (count < left) {
currBefore.next = curr;
currBefore = currBefore.next;
} else if (count >= left && count <= right) {
stack.push(curr);
} else {
currAfter.next = curr;
currAfter = currAfter.next;
}
curr = curr.next;
}
while (!stack.empty()) {
currBefore.next = stack.pop();
currBefore = currBefore.next;
}
currBefore.next = after.next;
currAfter.next = null;
return before.next;
}
/**
* 双指针:找到需要旋转的区间左边元素及其前一个元素,可以将其之后的元素采用头插法,
* 插到其前一个元素的后面,正好是旋转
*/
public ListNode reverseBetween1(ListNode head, int left, int right) {
ListNode pre = new ListNode(-1);
pre.next = head;
ListNode g = pre;
ListNode p = head;
// 找到左边的前一个节点位置
for (int i = 0; i < left - 1; i++) {
g = g.next;
p = p.next;
}
// 头插法插入处于旋转区间的元素
for (int i = 0; i < right - left; i++) {
ListNode remove = p.next;
p.next = p.next.next;
remove.next = g.next;
g.next = remove;
}
return pre.next;
}
11、138——复制带随机指针的链表
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
/**
* 思路:使用map,遍历链表同时new一个相同值的对象,
* 第二次遍历复制,因为map中存在的key,value是一样的即
* map.get(node).next = map.get(node.next);
* map.get(node).random = map.get(node.random);
*/
public Node copyRandomList(Node head) {
if (null == head) {
return null;
}
Map<Node, Node> map = new HashMap<>();
Node node = head;
while (node != null) {
map.put(node, new Node(node.val));
node = node.next;
}
node = head;
while (node != null) {
map.get(node).next = map.get(node.next);
map.get(node).random = map.get(node.random);
node = node.next;
}
return map.get(head);
}
12、143——重排链表
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
//使用有序集合
public void reorderList(ListNode head) {
if (null == head) {
return;
}
// 存到有序集合中
List<ListNode> list = new ArrayList<>();
ListNode curr = head;
while (curr != null) {
list.add(curr);
curr = curr.next;
}
int l = 0, r = list.size() - 1;
while (l < r) {
list.get(l).next = list.get(r);
l++;
//偶数个节点会提前相遇
if (l == r) {
break;
}
list.get(r).next = list.get(l);
r--;
}
list.get(l).next = null;
}
/**
* 快慢指针 分割中点
*/
public void reorderList2(ListNode head) {
if (head == null || head.next == null || head.next.next == null) {
return;
}
//找中点,链表分成两个
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode newHead = slow.next;
slow.next = null;
//第二个链表倒置
newHead = reverseList(newHead);
//链表节点依次连接
while (newHead != null) {
ListNode temp = newHead.next;
newHead.next = head.next;
head.next = newHead;
head = newHead.next;
newHead = temp;
}
}
private ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode tail = head;
head = head.next;
tail.next = null;
while (head != null) {
ListNode temp = head.next;
head.next = tail;
tail = head;
head = temp;
}
return tail;
}
13、147——对链表进行插入排序
给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
/**
* 思路:按顺序遍历,如果后面元素的值大于等于前一个元素,继续向后遍历,
* 否则删除这个元素,从新从头遍历 找到他合适的插入位置,从新插入进来
*/
public static ListNode insertionSortList1(ListNode head) {
ListNode pre = new ListNode(-1);
pre.next = head;
ListNode curr = head;
ListNode preCurr,temp;
while (curr != null && curr.next != null) {
if (curr.val <= curr.next.val) {
curr = curr.next;
} else {
// 保存并删除当前元素的下一个节点
temp = curr.next;
curr.next = curr.next.next;
preCurr = pre;
while (preCurr.next != null) {
if (preCurr.next.val <= temp.val) {
preCurr = preCurr.next;
} else {
//找到插入的位置
temp.next = preCurr.next;
preCurr.next = temp;
break;
}
}
}
}
return pre.next;
}
14、148——排序链表
给你链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
public ListNode sortList(ListNode head) {
//当链表的为空或链表只有一个元素的时候,直接返回。
if (head == null || head.next == null) {
return head;
}
//定义快慢指针,来找出链表中的中间节点
ListNode fast = head;
ListNode slow = head;
//这里的判断条件是;倒数第一个和倒数第二个节点
//在为偶数个的时候,会取第一个数作为第一个中间数
//这里特别要注意;fast !=null && fast.next.next != null 者两个条件的区别
while (fast.next != null && fast.next.next != null) {
//快指针移动两步,慢指针移动一步
fast = fast.next.next;
slow = slow.next;
}
//定义一个伪节点,来指向被第一个对半拆分后的,第二个链表的头节点
ListNode temp = slow.next;
//对半拆分链表,
slow.next = null;
//左拆分,
ListNode left = sortList(head);
//右拆分
ListNode right = sortList(temp);
//定义一个返回结果的伪节点的指针
ListNode dumpy = new ListNode(0);
//定义一个用于遍历链表的伪节点
ListNode prev = dumpy;
//当左链表和右链表不为空时,几句比较两个链表节点的值
while (left != null && right != null) {
//左链表的节点的值小于右链表的节点值时,就拼接左链表的节点
//并将左链表的节点的指针后移一位
if (left.val <= right.val) {
prev.next = left;
left = left.next;
} else {
//否则就,拼接右链表的节点,移动右链表的节点的指针
prev.next = right;
right = right.next;
}
//拼接了一个节点后,就移动伪节点的指针
prev = prev.next;
}
//如果左链表有剩余元素,就拼接左链表,否则就拼接右链表
prev.next = left != null ? left : right;
//返回伪节点的指向的头节点
return dumpy.next;
}
15、160——相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
/**
* 思路:已知不存在环,可以直接遍历比较,如果其中一个遍历完则将其指向另一个链表的头结点
* 如果存在交点正好可以遍历到或者两者同时遍历完
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
ListNode A = headA;
ListNode B = headB;
while(A != B){
A = A == null ? headB : A.next;
B = B == null ? headA : B.next;
}
return A;
}
16、203——移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
/**
* 思路:遍历,找到值相同的就将前一个元素指向他的下一个节点
*/
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return null;
}
ListNode pre = new ListNode(-1);
pre.next = head;
ListNode curr = head;
ListNode currPre = pre;
while (curr != null) {
if (curr.val == val) {
currPre.next = curr.next;
} else {
currPre.next = curr;
currPre = currPre.next;
}
curr = curr.next;
}
return pre.next;
}
17、206——反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
/**
* 递归法
* 1、返回值
* 2、调用者做了什么
* 3、终止条件
* 本题
* 1、返回反转后的链表表头
* 2、调用者将他本身指向null,将他后继节点指向自己
* 3、终止条件找到链表尾部
*/
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode ans = reverseList(head.next);
head.next.next = head;
head.next = null;
return ans;
}
/**
* 双指针
*/
public ListNode reverseList(ListNode head) {
ListNode pre = head;
ListNode ans = null;
while (pre != null) {
ListNode temp = pre.next;
pre.next = ans;
ans = pre;
pre = temp;
}
return ans;
}
18、234——回文链表
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
/**
* 思路:快慢指针,找到中间节点,然后分割,将后半部分反转,再与前面比较
*/
public static boolean isPalindrome(ListNode head) {
ListNode pre = head;
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode newHead = reverseList(slow.next);
while (newHead != null) {
if (newHead.val != pre.val) {
return false;
}
newHead = newHead.next;
pre = pre.next;
}
return newHead == null;
}
/**
* 反转链表并返回反转后的新表头
*/
private static ListNode reverseList(ListNode head) {
ListNode pre = head;
ListNode ans = null;
while (pre != null) {
ListNode temp = pre.next;
pre.next = ans;
ans = pre;
pre = temp;
}
return ans;
}
19、237——删除链表中的节点
请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。
题目数据保证需要删除的节点 不是末尾节点 。
/**
* 思路:将下一个元素的节点值给到当前节点,把下一个节点删除
*/
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
20、328——奇偶链表
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
/**
* 思路:遍历,把奇数节点放一起,偶数放一起,再连接起来
*/
public static ListNode oddEvenList1(ListNode head) {
if (head == null) {
return head;
}
ListNode evenHead = head.next;
ListNode odd = head, even = evenHead;
while (even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenHead;
return head;
}