指定区域反转
头插法
第一反应,拆开成三个链表(有可能是两个或者一个),指定区域反转
之前做过反转,这次就属于只是设置一个范围,固定好两侧链表的指针,对中间区域做头插法
em自己按照这个思路,搞了四十分钟。。。没搞出来,边界问题太多,中间还出现了有环的情况。
作为目前第一道完全没搞出来的题目,我真是彩笔,要仔细想想这题了
public static ListNode reverseBetween2(ListNode head, int left, int right) {
// 设置 dummyNode 是这一类问题的一般做法
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
//pre,cur,next就是上一篇的直接操作数组
//但是这里是用三个指针实现头插法
//pre始终指向前一个链表的尾节点(可以理解为中间区域的虚拟头节点)
//操作其实是对next指向的节点,做头插
ListNode cur = pre.next;
ListNode next;
for (int i = 0; i < right - left; i++) {
//这里非常需要自己画图
next = cur.next;
//把next指向的节点拆出来
//第一步,指向next的指针指向next.next
cur.next = next.next;
//第二步,把next指针指向虚拟头节点的next
next.next = pre.next;
//第三步,把next节点塞到头部
pre.next = next;
}
return dummyNode.next;
}
穿针引线法
看过解析之后,发现,穿针引线法,就是我最开始的想法。
再把之前的烂代码,修修补补吧
public static ListNode reverseBetweenHead(ListNode head, int left, int right) {
//使用虚拟头节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
//第一步,从虚拟头节点走left-1步,来到left节点前的一个节点
for(int i = 0; i < left; i++) {
pre = pre.next;
}
//第二步,从pre再走 right - left + 1 步,来到right节点
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;
//第四步,用206题目中的反转方式
reverseList(leftNode);
//第五步,接上原来的链表
//rightNode本身是末尾节点,但是反转之后称为第一个节点
pre.next = rightNode;
//反转后leftNode称为末尾节点,连接上原本在后面的子链表
leftNode.next = succ;
return dummyNode.next;
}
public static ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
两两交换链表中的节点
要多多考虑虚拟头节点,这题还是图画的有点懒,逻辑出问题了
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;
}
给单链表加一
这题第一反应就是用栈
全部进栈,出栈时加一,头插法建立新链表,用carry存储进位信息
注意最后进位
第二个思路:链表先反转,加一,再反转回去
private static ListNode initLinkedList(ListNode head) {
Stack<Integer> nodeStack = new Stack<Integer>();
ListNode current = head;
while(current != null) {
nodeStack.push(current.val);
current = current.next;
}
ListNode dummyHead = new ListNode(-1);
int carry = 1;
while(!(nodeStack.empty())) {//栈非空
int number = nodeStack.pop();
number += carry;
number = number%10;
//如果刚刚处理的数字,结果为0,说明需要进位
//否则carry位应该为0
if(!(number == 0 && carry == 1)) {
carry = 0;
}
ListNode newNode = new ListNode(number);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
}
//最高位进位
if(carry == 1) {
ListNode newNode = new ListNode(1);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
}
return dummyHead.next;
}
这道题目,答案写得非常秒,抄下来学习一下
private static ListNode initLinkedList(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);
//其实这个adder也可以用carry优化掉
int adder = 1;//初始的加一
//开始了,就下面这段很厉害
//这里考虑到了最高位进位的问题,这里不一样的点在于用运算符来区分两种情况
while(!st.empty() || carry > 0) {
//如果st空了,就说明要处理最高进位了
//最高进位相当于0 + adder + carry
int digit = st.empty() ? 0 : st.pop();
int sum = digit + adder + carry;
//计算进位
carry = sum >= 10 ? sum - 10 : sum;
ListNode cur = new ListNode(sum);
cur.next = dummy.next;
dummy.next = cur;
adder = 0;
}
return dummy.next;
}
链表加法
应该是先分别反转,然后进行相加,有进位信息carry,要注意最高位进位的问题
两个链表反转,然后遍历相加(carry也要加上),接着用头插法创新新链
注意最高位的特殊情况
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode list1 = reversList(l1);
ListNode list2 = reversList(l2);
//进位
int carry = 0;
ListNode newList = new ListNode(-1), newNode = newList;
while(list1 != null || list2 != null) {//两个链表不同时空
int num1 = (list1==null) ? 0 : list1.val;
int num2 = (list2==null) ? 0 : list2.val;
int num = num1 + num2 + carry;
//更新下一次循环的进位
carry = num >= 10 ? 1 : 0;
num = num >= 10 ? num % 10 : num;
if(list1 != null) list1 = list1.next;
if(list2 != null) list2 = list2.next;
//头插法
newNode = new ListNode(num);
newNode.next = newList.next;
newList.next = newNode;
}
if(carry == 1) {
newNode = new ListNode(carry);
newNode.next = newList.next;
newList.next = newNode;
}
return newList.next;
}
//这里复习一下反转
public static ListNode reversList(ListNode head) {
if(head == null || head.next == null) {
return head;
}
//虚拟头节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
//头插法
ListNode pre = dummyNode, cur = head, next = cur.next;
while(next != null) {
cur.next = next.next;
pre.next = next;
next.next = head;
next = cur.next;
head = pre.next;
}
return dummyNode.next;
}
回文序列问题
链表反转和回文数结合
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true; // 空链表或只有一个节点的链表视为回文序列
}
ListNode slow = head;
ListNode fast = head;
// 使用快慢指针找到链表的中间节点
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 反转链表的后半部分
ListNode reversedHead = reverseList(slow);
// 比较前半部分和反转后的后半部分是否相同
while (reversedHead != null) {
if (head.val != reversedHead.val) {
return false; // 值不相等,不是回文序列
}
head = head.next;
reversedHead = reversedHead.next;
}
return true; // 值全部相等,是回文序列
}
private ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}