整理不易,希望对你有所帮助和启发,路过点个赞或者评论一下吧~
160,相交链表,easy
206,反转链表,easy
21,合并两个有序链表,easy
83,删除排序链表中的重复元素,easy
83-Ⅱ.删除排序链表中的重复元素,middle
19,删除链表的倒数第N个节点,middle
24,两两交换链表中的节点,middle
445,两数相加Ⅱ,middle
234,回文链表,easy
725,分隔链表,middle
328,奇偶链表,middle
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
方法:双指针。
思路:定义节点pA指向headA,节点pB指向headB。
代码:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//双指针,pA与pB如果能相遇,说明两个链表长度相同且有交点;第一遍不能相遇,pA转移到pB,pB转移到pA
//再次遍历,如果能相遇说明有交点
ListNode pA = headA;
ListNode pB = headB;
//判别两个链表是否为空
if (pA == null || pB == null) {
return null;
}
//长度不同会有先走完链表的
while(pA != null && pB != null){
pA = pA.next;
pB = pB.next;
}
//A先走完,转向headB pB同理
if(pA == null){
pA = headB;
}
else {
pB = headA;
}
//再次同时走,A继续向前,B转为headA
while(pA != null && pB != null){
pA = pA.next;
pB = pB.next;
}
if(pA == null){
pA = headB;
}
else {
pB = headA;
}
//pA在B链表,pB在A链表,同时向前找交点
while(pA != pB){
pA = pA.next;
pB = pB.next;
}
return pA;
}
}
简化:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA;
ListNode pB = headB;
while(pA != pB){
pA = pA == null ? headB:pA.next;
pB = pB == null ? headA:pB.next;
}
return pA;
}
}
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
class Solution {
public ListNode reverseList(ListNode head) {
//方法一:递归
//终止条件:链表为空或递归到链表的尾节点时
if(head == null || head.next == null) return head;
ListNode cur = reverseList(head.next);
head.next.next = head;
head.next = null;
return cur;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
ListNode newHead = null; //null
while(head != null){
ListNode temp = head.next;//保存后面用的新链表 2—>3->4->null
head.next = newHead; //null<-1
newHead = head; //更新结果链表 1为newHead
head = temp; //更新原链表 2—>3->4->null中的2为head
}
return newHead;
}
}
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//方法一、迭代。新建链表
if(l1 == null) return l2;
if(l2 == null) return l1;
ListNode newHead = new ListNode(0);
ListNode temp = newHead;
//l1与l2都不为空,较小的加入新链表
while(l1 != null && l2 != null){
if(l1.val < l2.val){
temp.next = l1;
l1 = l1.next;
}
else if(l1.val >= l2.val){
temp.next = l2;
l2 = l2.next;
}
temp = temp.next;
if(l1 == null){
temp.next = l2;
}
if(l2 == null){
temp.next = l1;
}
}
return newHead.next;
}
}
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
ListNode newHead = new ListNode(0);
if(l1.val < l2.val){
newHead = l1;
newHead.next = mergeTwoLists(l1.next,l2);
}
else if(l1.val >= l2.val){
newHead = l2;
newHead.next = mergeTwoLists(l1,l2.next);
}
return newHead;
}
}
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode temp = head;
while(temp != null && temp.next != null){
if(temp.val == temp.next.val){
temp.next = temp.next.next;
}
else{
temp = temp.next;
}
}
return head;
}
}
方法二:双指针。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null) return null;
ListNode right = head.next;
ListNode left = head;
while(right != null){
if(right.val != left.val){
left.next = right;
left = left.next;
}
else{
right = right.next;
}
}
//断开与后面重复元素的连接
left.next = null;
return head;
}
}
方法三:递归。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//递归出口 空链表和链表只有头节点
if(head == null || head.next == null) return head;
ListNode newHead = deleteDuplicates(head.next);
head.next = newHead;
return head.val == newHead.val ? newHead : head;
}
}
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 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) {
//递归
if(head == null || head.next == null) return head;
//如果head与后面的节点重复,找到第一个不重复的节点,进行递归。
if(head.val == head.next.val){
while(head != null && head.next != null && head.val == head.next.val){
head = head.next;
}
return deleteDuplicates(head.next);
}
else{
ListNode newHead = deleteDuplicates(head.next);
head.next = newHead;
return head;
}
}
}
方法二:双指针-快慢指针。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//双指针,建立虚拟头节点
if(head == null || head.next == null) return head;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode left = dummy;
ListNode right = head;
while(right != null && right.next != null){
if(left.next.val != right.next.val){
left = left.next;
right = right.next;
}
else{
while(right != null && right.next != null && left.next.val == right.next.val){
right = right.next;//right右移直到right.next不是重复数字
}
left.next = right.next;//left下一位是去掉重复数字之后的数字
right = right.next;//继续右移
}
}
return dummy.next;
}
}
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
class Solution {
//先得到长度,再单指针跳过倒数第n个元素
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head == null || head.next == null) return null;
ListNode temp = head;
int length = getLength(head);
if(length == n) return head.next;
for(int i = 0; i < length - n - 1; i++){
temp = temp.next;
}
temp.next = temp.next.next;
return head;
}
public int getLength(ListNode head){
int length = 1;
while(head.next != null){
length++;
head = head.next;
}
return length;
}
}
方法二:双指针-快慢指针。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast = head;
ListNode slow = head;
//快指针先走n步
for(int i = 0; i < n; i++){
fast = fast.next;
}
//如果此时fast为空,说明删除的是头节点
if(fast == null){
return head.next;
}
//fast走到头,此时slow走到要删除节点的前一个
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
}
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) return head;
//两个相邻节点为一组,使后一个节点的指针指向前一个,组间指向也需要调整
//先保存节点2
ListNode temp = head.next;
//头节点下一个节点为递归的结果(1->4->3)
head.next = swapPairs(head.next.next);
//2->1->4->3
temp.next = head;
return temp;
}
}
方法二:迭代。
class Solution {
public ListNode swapPairs(ListNode head) {
//迭代
if(head == null || head.next == null) return head;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode left = dummy;
ListNode right = dummy;
ListNode temp = dummy;
while(right != null && right.next != null && right.next.next != null){
//分别向前走一步、两步
left = left.next; // 1
right = right.next.next; //2
//使下次循环的1指向4
temp.next = right;
left.next = right.next;
right.next = left;
//2->1->3->4
//再使temp、right都指向1.下次迭代时left为3,right为4
temp = left;
right = left;
}
return dummy.next;
}
}
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//法一:栈+取和的个位
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
while(l1 != null){
stack1.push(l1.val);
l1 = l1.next;
}
while(l2 != null){
stack2.push(l2.val);
l2 = l2.next;
}
int carry = 0;//相加的进位
ListNode head = null;
//有非空栈或carry > 0就进行相加操作
while(!stack1.isEmpty() || !stack2.isEmpty() || carry > 0){
int sum = carry;//两数的和
sum += stack1.isEmpty() ? 0 : stack1.pop();
sum += stack2.isEmpty() ? 0 : stack2.pop();
ListNode node = new ListNode(sum % 10);
node.next = head;
head = node;
carry = sum / 10;
}
return head;
}
}
注意:while条件里添加carry > 0是为了
输入:(5) + (5)
输出: 1->0
也可以单独判断carry,如果stack1、stack2都为空了但还有进位,则创建新节点加入结果。
while(!stack1.isEmpty() || !stack2.isEmpty() ){
int sum = carry;//两数的和
sum += stack1.isEmpty() ? 0 : stack1.pop();
sum += stack2.isEmpty() ? 0 : stack2.pop();
ListNode node = new ListNode(sum % 10);
node.next = head;
head = node;
carry = sum / 10;
}
if(carry > 0){
ListNode node1 = new ListNode(carry);
node1.next = head;
head = node1;
}
return head;
}
方法二:反转链表。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//法二:反转链表 3->4->2->7 4->6->5 7 -> 0 -> 8 ->7反转输出结果
if(l1 == null || l2 == null) return l1 == null ? l2 : l1;
ListNode newl1 = reverse(l1);
ListNode newl2 = reverse(l2);
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
int carry = 0;//进位
while(newl1 != null || newl2 != null || carry > 0){
int sum = carry;
sum += newl1 == null ? 0 : newl1.val;
sum += newl2 == null ? 0 : newl2.val;
ListNode node = new ListNode(sum % 10);
cur.next = node;
cur = node;
carry = sum / 10;
if(newl1 != null) newl1 = newl1.next;
if(newl2 != null) newl2 = newl2.next;
}
ListNode res = reverse(dummy.next);
return res;
}
public ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
}
扩展:另一道类似题
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if (l1 == null || l2 == null) return l1 == null ? l2 : l1;
int carry = 0;//进位
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (l1 != null || l2 != null || carry > 0) {
int sum = carry;
sum += l1 == null ? 0 : l1.val;
sum += l2 == null ? 0 : l2.val;
ListNode node = new ListNode(sum % 10);
cur.next = node;
cur = node;
carry = sum / 10;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
return dummy.next;
}
}
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null) return true;
Stack<Integer> stack = new Stack<>();
ListNode temp = head;
//将节点值放入栈
int length = 0;
while(temp != null){
stack.push(temp.val);
temp = temp.next;
length++;
}
length /= 2;
while(length-- > 0){
if(stack.pop() != head.val){
return false;
}
head = head.next;
}
return true;
}
}
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> list = new ArrayList<>();
while(head != null){
list.add(head.val);
head = head.next;
}
int left = 0;
int right = list.size() - 1;
while(left < right){
if(!list.get(left).equals(list.get(right)))
return false;
else{
left++;
right--;
}
}
return true;
}
}
错误点:if(list.get(left) != list.get(right))
错误
方法三:快慢指针。
class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null) return true;
ListNode middle = getMiddle(head);//链表中点,如果长度是偶数,返回第 l/2 - 1 个节点
ListNode newHead = reverse(middle.next);//反转后半部分链表
//比较前半部分和后半部分链表的节点值
while(newHead != null){
if(head.val != newHead.val){
return false;
}
else{
head = head.next;
newHead = newHead.next;
}
}
return true;
}
public ListNode getMiddle(ListNode head){
ListNode left = head;
ListNode right = head;
while(right.next != null && right.next.next != null){
left = left.next;
right = right.next.next;
}
return left;
}
//返回链表的中间节点
public ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode temp = reverse(head.next);
head.next.next = head;
head.next = null;
return temp;
}
}
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
示例 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.前面部分的长度大于等于后面部分的长度。
方法一:拆分链表。
思路:分类讨论。
代码:
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
if(root == null) return new ListNode[k];
ListNode[] res = new ListNode[k];
int length = getLength(root);
if(length <= k){
for(int i = 0; i < length; ){
ListNode temp = root.next;
root.next = null;
res[i++] = root;
root = temp;
}
for(int i = length; i < k; i++){
res[i] = null;
}
}
else if(length > k){
//length 比 k 大时 每部分存放 l/k 个节点 , 前 l%k 部分存放 l/k+1 个节点
int n = length / k;
int m = length % k;
int[] counts = new int[k];//记录每部分存储元素个数,[4][3][3]
for(int i = 0; i < k; i++){
counts[i] = m-- > 0 ? n + 1: n;
}
ListNode cur = root;
//将节点存储到数组中 counts[0]=4
for(int i = 0; i < k; i++){
res[i] = cur;
//跳到每部分最末节点 1->2->3->4
for(int j = 0; j < counts[i] - 1; j++){
cur = cur.next;
}
//断开每部分之间的连接
ListNode temp = cur.next;
cur.next = null;
cur = temp;
}
}
return res;
}
public int getLength(ListNode node){
int length = 0;
while(node != null){
length++;
node = node.next;
}
return length;
}
}
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 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
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
class Solution {
public ListNode oddEvenList(ListNode head) {
//双指针
if(head == null) return head;
ListNode odd = head;
ListNode evenHead = head.next;
ListNode even = evenHead;
while(even != null && even.next != null){
odd.next = odd.next.next;
odd = odd.next;
even.next = even.next.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
}