指定区间反转
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
头插法
反转的整体思想是,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
for(int i=0; i<left-1;i++) {
pre = pre.next;
}
ListNode cur = pre.next;
ListNode next;
for(int i=0; i< right - left;i++) {
next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
return dummy.next;
}
}
穿针引线法
算法步骤:
- 第 1 步:先将待反转的区域反转;
- 第 2 步:把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ。
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
for(int i=0; i<left-1;i++) {
pre = pre.next;
}
ListNode rightNode = pre;
for(int i=0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
ListNode leftNode = pre.next;
ListNode succ = rightNode.next;
rightNode.next = null;
reverseLinkedList(leftNode);
pre.next = rightNode;
leftNode.next = succ;
return dummy.next;
}
private void reverseLinkedList(ListNode head) {
// 也可以使用递归反转一个链表
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
}
两两交换链接中的节点
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
这道题因为要成对反转,所以我们可以先画图看一下调转的时候如何调整每个结点的指向。
如果原始顺序是 dummy -> node1 -> node2,交换后面两个节点关系要变成 dummy -> node2 -> node1,事实上我们只要多执行一次next就可以拿到后面的元素,也就是类似node2 = temp.next.next这样的操作。
两两交换链表中的节点之后,新的链表的头节点是 dummyHead.next,返回新的链表的头节点即可。指针的调整可以参考如下图示:
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode temp = dummyHead;
while (temp.next != null && temp.next.next != null) {
ListNode node1 = temp.next;
ListNode node2 = temp.next.next;
temp.next = node2;
node1.next = node2.next;
node2.next = node1;
temp = node1;
}
return dummyHead.next;
}
}
单链表加1
用一个非空单链表来表示一个非负整数,然后将这个整数加一。你可以假设这个整数除了 0 本身,没有任何前导的 0。这个整数的各个数位按照 高位在链表头部、低位在链表尾部 的顺序排列
我们先看一下加法的计算过程:
计算是从低位开始的,而链表是从高位开始的,所以要处理就必须反转过来,此时可以使用栈,也可以使用链表反转来实现。
基于栈实现的思路不算复杂,先把题目给出的链表遍历放到栈中,然后从栈中弹出栈顶数字 digit,加的时候再考虑一下进位的情况就ok了,加完之后根据是否大于0决定视为下一次要进位
public ListNode plusOne(ListNode head) {
Stack<Integer> st = new Stack();
while (head != null) {
st.push(head.val);
head = head.next;
}
int carry = 0;
ListNode dummy = new ListNode(0);
int adder = 1;
while (!st.empty() || carry > 0) {
int digit = st.empty() ? 0 : st.pop();
int sum = digit + adder + carry;
carry = sum >= 10 ? 1 : 0;
sum = sum >= 10 ? sum - 10 : sum;
ListNode cur = new ListNode(sum);
cur.next = dummy.next;
dummy.next = cur;
adder = 0;
}
return dummy.next;
}
链表加法
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
使用栈实现
思路是先将两个链表的元素分别压栈,然后再一起出栈,将两个结果分别计算。之后对计算结果取模,模数保存到新的链表中,进位保存到下一轮。完成之后再进行一次反转就行了。
我们知道在链表插入有头插法和尾插法两种。头插法就是每次都将新的结点插入到head之前。而尾插法就是将新结点都插入到链表的表尾。两者的区别是尾插法的顺序与原始链表是一致的,而头插法与原始链表是逆序的,所以上面最后一步如果不想进行反转,可以将新结点以头插法
public static ListNode addInListByStack(ListNode head1, ListNode head2) {
Stack<ListNode> st1 = new Stack<ListNode>();
Stack<ListNode> st2 = new Stack<ListNode>();
while (head1 != null) {
st1.push(head1);
head1 = head1.next;
}
while (head2 != null) {
st2.push(head2);
head2 = head2.next;
}
ListNode newHead = new ListNode(-1);
int carry = 0;
//这里设置carry!=0,是因为当st1,st2都遍历完时,如果carry=0,就不需要进入循环了
while (!st1.empty() || !st2.empty() || carry != 0) {
ListNode a = new ListNode(0);
ListNode b = new ListNode(0);
if (!st1.empty()) {
a = st1.pop();
}
if (!st2.empty()) {
b = st2.pop();
}
//每次的和应该是对应位相加再加上进位
int get_sum = a.val + b.val + carry;
//对累加的结果取余
int ans = get_sum % 10;
//如果大于0,就进位
carry = get_sum / 10;
ListNode cur = new ListNode(ans);
cur.next = newHead.next;
//每次把最新得到的节点更新到neHead.next中
newHead.next = cur;
}
return newHead.next;
}
使用链表反转实现
如果使用链表反转,先将两个链表分别反转,最后计算完之后再将结果反转,一共有三次反转操作,所以必然将反转抽取出一个方法比较好,代码如下:
public class Solution {
public ListNode addInList (ListNode head1, ListNode head2) {
head1 = reverse(head1);
head2 = reverse(head2);
ListNode head = new ListNode(-1);
ListNode cur = head;
int carry = 0;
while(head1 != null || head2 != null) {
int val = carry;
if (head1 != null) {
val += head1.val;
head1 = head1.next;
}
if (head2 != null) {
val += head2.val;
head2 = head2.next;
}
cur.next = new ListNode(val % 10);
carry = val / 10;
cur = cur.next;
}
if (carry > 0) {
cur.next = new ListNode(carry);
}
return reverse(head.next);
}
private ListNode reverse(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while(cur != null) {
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
再论回文序列问题
之前是使用栈的方法,这次使用反转,而且只反转链表的一半,那么就可以使用快慢指针和反转了
核心就是反转、快慢指针找到中间位置,需要注意 fast 是否为 null 。为 null 说明节点为偶数,否则为技术,后面需要根据这个去遍历判断链表是否相等
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) {
return true;
}
ListNode slow = head, fast = head;
ListNode pre = head, prepre = null;
while(fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
//将前半部分链表反转
pre.next = prepre;
prepre = pre;
}
if(fast != null) {
slow = slow.next;
}
while(pre != null && slow != null) {
if(pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}