1. 反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
2. 分析
2.1 头插法,首先肯定需要找到left的位置,但是由于这个节点需要改变,所以需要找到这个节点的前一个节点作为prev,然后这个节点就是cur,但是还需要记录后一个节点next,因为是将cur和next进行调换,所以需要保存next节点,然后将cur指向next的下一个节点,此时next节点需要指向prev的下一个节点位置,然后调整prev的下一个节点为next。
核心图
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode sentinel = new ListNode(-1);
sentinel.next = head;
ListNode prev = sentinel;
// 移动到left前一个节点
for(int i=1;i<left;i++){
prev=prev.next;
}
// 记录left节点
ListNode cur = prev.next;
// 存储left节点的后一个节点
ListNode next;
for(int i=0;i<right-left;i++){
// 存储left节点的后一个节点
next = cur.next;
// 将left节点指针指向后一个节点的后一个节点
cur.next = next.next;
// 将后一个节点的指针指向left前一个节点的下一个节点
next.next = prev.next;
// 然后将prev的指针指向next节点
prev.next = next;
}
return sentinel.next;
}
运行截图:
时间复杂度:O(right) 第一次循环O(left)第二次循环O(right-left)
空间复杂度:O(1)
总结:头插法涉及到了4个节点的这种指向,需要好好画图才能了解,不然很头晕,一开始一直疑惑 next.next = prev.next;作用,画完图了才知道这个只是为了后面的断开节点做准备,对于链表的这个删除插入节点还是不熟悉。
2.2 穿针引线法,这个名字也挺绕的,总体思路简单,就是将链表分成三个部分,然后将中间链表反转,然后其余两个链表分别接入新链表。
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode sentinel = new ListNode(-1);
sentinel.next = head;
ListNode prev = sentinel;
for(int i=0;i<left-1;i++){
// 找到left左边的节点
prev =prev.next;
}
ListNode rightNode = prev;
for(int i=0;i<right-left+1;i++){
// 找到right节点
rightNode=rightNode.next;
}
// right右边节点
ListNode rightNodeNext = rightNode.next;
// left节点
ListNode leftNode = prev.next;
// 分割原链表
rightNode.next = null;
prev.next = null;
// 反转链表
reverseList(leftNode);
// 当前第一个链表指向右节点
prev.next = rightNode;
// 第二个链表的右节点指向当前第三个链表
leftNode.next = rightNodeNext;
return sentinel.next;
}
// 反转链表
public ListNode reverseList(ListNode head){
ListNode cur = head;
ListNode prev = null;
while(cur!=null){
ListNode nextNode = cur.next;
cur.next = prev;
prev=cur;
cur = nextNode;
}
return prev;
}
运行截图
时间复杂度: O(right-left)
空间复杂度:O(1)
总结:尤其需要注意反转链表前需要断开之前的链表
2. 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
2.1 分析
2.1.1 这一题的思路主要是记录好第一个和第二个节点,然后让哨兵节点先指向第二个节点,第一个节点指向第二个节点的后一个节点,第二个节点然后指向第一个节点,那么此时交换已经完成,继续移动cur
public ListNode swapPairs(ListNode head) {
if(head==null || head.next == null) return head;
ListNode sentinel = new ListNode(-1);
sentinel.next = head;
ListNode cur = sentinel;
while(cur.next!=null && cur.next.next!=null){
// 第一个节点
ListNode next = cur.next;
// 第二个节点
ListNode nextNext = cur.next.next;
// 当前节点指向第二个节点
cur.next = nextNext;
// 第一个节点指向第二个节点的下一个节点
next.next = nextNext.next;
// 第二个节点指向第一个节点
nextNext.next = next;
// cur变成第二个节点
cur=next;
}
return sentinel.next;
}
运行截图:
时间复杂度:O(n)
空间复杂度:O(1)
3. 单链表+1
用一个非空单链表来表示一个非负整数,然后将这个整数加一。你可以假设这个整数除了 0 本身,没有任何前导的 0。这个整数的各个数位按照 高位在链表头部、低位在链表尾部 的顺序排列。
单链表加1
3.1 分析
3.1.1 可以将链表元素写入栈里面,然后栈依次弹出,先判断时候之前没有进位,然后计算出元素和,继续判断是否有进位,然后依次将元素添加至新链表里面。总体思路和整型数据这种加法一致。
public static ListNode plusOne(ListNode head) {
Stack<Integer> stack = new Stack<>();
while (head != null) {
stack.push(head.value);
head = head.next;
}
// 进位
int carry = 0;
// 1
int adder = 1;
ListNode sentinel = new ListNode(0);
while (!stack.isEmpty() || carry > 0 || adder != 0) {
// 取出第一个弹出的元素
int num = stack.isEmpty() ? 0 : stack.pop();
// 计算总和
int sum = num + adder + carry;
// 先判断是否有进位
carry = sum >= 10 ? 1 : 0;
// 判断值是否大于0
sum = sum >= 10 ? sum - 10 : sum;
// 创建新链表
ListNode cur = new ListNode(sum);
cur.next = sentinel.next;
sentinel.next = cur;
// 加数设置为0
adder = 0;
}
return sentinel.next;
}
时间复杂度:O(n)将链表元素压入栈。
空间复杂度:O(n) 新建了一个链表。
总结:思路基本上都能想到,但是中间进位一开始放在判断求和是否大于10之后,就导致了进位永远都是0,应当是先进位,然后再处理和。
3.1.2 先将链表反转,然后先从第一个开始计算,依次进位计算,不过最后需要判断进位是否为1,如果为1表名第二位有了进位,需要添加到里面去
public static ListNode plusOne(ListNode head) {
ListNode newHead = reverseList(head);
int carry = 0;
int adder = 1;
ListNode temp = null;
ListNode cur = newHead;
while (cur != null) {
int num = cur.value;
int sum = carry + adder + num;
carry = sum >= 10 ? 1 : 0;
sum = sum >= 10 ? sum - 10 : sum;
ListNode node = new ListNode(sum);
node.next = temp;
temp = node;
cur = cur.next;
adder = 0;
}
// 检查是否有进位
if (carry > 0) {
ListNode node = new ListNode(carry);
node.next = temp;
temp = node;
}
return temp;
}
public static ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode prev = null;
while (cur != null) {
ListNode listNodeNext = cur.next;
cur.next = prev;
prev = cur;
cur = listNodeNext;
}
return prev;
}
4. 两数相加 II
力扣
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
4.2 分析
4.2.1 这一题和上一题类似,都可以将节点里面的值放入到栈里面去,然后依次取出相加,计算进位,使用哨兵节点存储。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> stackL1 = new Stack<>();
Stack<Integer> stackL2 = new Stack<>();
while(l1!=null){
stackL1.push(l1.val);
l1=l1.next;
}
while(l2!=null){
stackL2.push(l2.val);
l2=l2.next;
}
int carry =0;
ListNode sentinel = new ListNode(0);
while(!stackL1.isEmpty() || !stackL2.isEmpty()|| carry>0){
int num1 = stackL1.isEmpty() ? 0:stackL1.pop();
int num2 = stackL2.isEmpty()?0:stackL2.pop();
int sum = num1+num2+carry;
carry = sum>=10?1:0;
sum=sum>=10?sum-10:sum;
ListNode res = new ListNode(sum);
res.next = sentinel.next;
sentinel.next = res;
}
if(carry>0){
ListNode res = new ListNode(carry);
res.next = sentinel.next;
sentinel.next =res;
}
return sentinel.next;
}
运行截图
时间复杂度:O(l1+l2)遍历两个链表的长度
空间复杂度:O(l1+l2)使用了两个栈来存储,两个栈的长度之和
4.2.2 一样的采用反转链表,先将链表反转,然后相加,计算进位,然后返回。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode newL1 = reverseList(l1);
ListNode newL2 = reverseList(l2);
int carry = 0;
ListNode sentinel = new ListNode(0);
while (newL1 != null || newL2 != null || carry > 0) {
int num1 = (newL1 == null) ? 0 : newL1.val;
int num2 = (newL2 == null) ? 0 : newL2.val;
int sum = num1 + num2 + carry;
carry = sum >= 10 ? 1 : 0;
sum = sum >= 10 ? sum - 10 : sum;
ListNode res = new ListNode(sum);
res.next = sentinel.next;
sentinel.next = res;
if (newL1 != null) newL1 = newL1.next;
if (newL2 != null) newL2 = newL2.next;
}
return sentinel.next;
}
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode prev = null;
while (cur != null) {
ListNode next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
运行截图:
时间复杂度:O(l1+l2)两个链表的长度,然后链表比较值的时候,再次比较O(max(l1,l2)总体上O(l1+l2)
空间复杂度:新建反转链表O(l1+l2),然后额外链表长度O(max(l1,l2)),总体上O(l1+l2)