废话不多说,直接实战!
题目链接:92. 反转链表 II - 力扣(LeetCode)
本题有两种主要的解法,一是头插法,二是穿针引线法, 先说一种之前学过的解法,头插法。
思路:
创建一个虚拟头节点
dummy
,将它的next
指针指向链表的头节点head
定义一个指针
pre
,通过循环将它移动到反转部分的前一个节点。循环的次数是left - 1
,这样pre
就指向了反转部分的前一个节点。定义一个指针
cur
,初始时指向pre.next
,即反转部分的第一个节点。使用循环,循环的次数是
right - left
,这样cur
就指向了反转部分的最后一个节点。在循环中,我们每次都将
cur.next
指针指向下一个节点,然后将cur
插入到pre.next
的位置,从而实现了反转。最后,返回虚拟头节点
dummy
的next
指针,即为反转后的链表头节点。
具体如图:
具体的代码如下:
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 tmp;
for(int i = 0;i < right - left;i++){
tmp = cur.next;
cur.next = tmp.next;
tmp.next = pre.next;
pre.next = tmp;
}
return dummy.next;
}
}
看图慢慢理解就行了(doge)
这种方式听起来很难,但它的确很难(doge),只不过难的是理解。下面我们就来学习一下这个方法。
所谓的穿针引线法,其实就是先找到要反转的区域,也就是 left 到 right 的这个区域,这时看原链表,感觉就像被分成了 3 个部分,这时我们把链表如穿线一样穿起来,就把 要反转区域内的中间链表“倒了过来”,之后的步骤就和平常的链表反转一样了(等于是先把中间部分剪掉进行反转,然后把反转后的与之前的串在一起),具体细节如下图所示:
(图中只是我的理解,有错误欢迎大佬们指正,感谢!)
具体代码如下:
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
// 从虚拟头节点走 left - 1 步,来到反转部分的前一个节点
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
ListNode rightNode = pre;
// 从 pre 再走 right - left + 1 步,来到反转部分的节点
for (int i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 切出一个子链表
ListNode leftNode = pre.next;
ListNode succ = rightNode.next;
pre.next = null;
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 tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
}
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
题目详情:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
对于本题而言,使用虚拟头结点是一个非常正确的选择,避免了处理头结点的优点在这道题目中体现的格外突出(自我感觉~~)
大致的思路如图所示:
具体代码如下:
public ListNode swapPairs(ListNode head) {
ListNode dummy= new ListNode(-1);
dummy.next = head;
ListNode cur = dummy;
while(cur.next != null && cur.next.next != null){
ListNode node1 = cur.next;
ListNode node2 = cur.next.next;
cur.next = node2;
node1.next = node2.next;
node2.next = node1;
cur = node1;
}
return dummy.next;
}
给单链表加一”的问题,即在表示非负整数的单链表上进行加一操作。题目描述如下:
给定一个非负整数,用一个非空单链表来表示。将这个整数加一。
你可以假设这个整数除了 0 本身,没有任何前导的 0。
这道题用栈可以很好的解决问题,因为加 1 操作是从低位开始的,直接操作链表的话需要反转,如果使用栈,第一个pop出的就是低位的,很方便的就可以进行加操作,具体实现如下:
具体代码:
public ListNode plusOne (ListNode head) {
// write code here
Stack st = new Stack<>();
ListNode cur = head;
// 将链表节点{}逐个压入栈中
while (cur != null) {
st.push(cur);
cur = cur.next;
}
// 从栈中依次弹出节点,对链表进行加一操作
int carry = 1;
while (!st.isEmpty() && carry > 0) {
ListNode node = st.pop();
int sum = node.val + carry;
node.val = sum % 10;
carry = sum / 10;
}
// 如果最高位产生了进位,添加一个新的节点
if (carry > 0) {
ListNode newHead = new ListNode(carry);
newHead.next = head;
return newHead;
}
return head;
}
public ListNode plusOne (ListNode head) {
// write code here
// 第一步:反转原始链表
ListNode reversedHead = reverseLinkedList(head);
// 第二步:对反转后的链表进行加一操作
ListNode current = reversedHead;
int carry = 1;
while (current != null && carry > 0) {
int sum = current.val + carry;
current.val = sum % 10;
carry = sum / 10;
current = current.next;
}
// 第三步:如果还有进位,添加一个新节点到链表头部
if (carry > 0) {
ListNode newNode = new ListNode(carry);
if (reversedHead == null) {
reversedHead = newNode;
} else {
ListNode tail = reversedHead;
while (tail.next != null) {
tail = tail.next;
}
tail.next = newNode;
}
}
// 第四步:再次反转链表得到最终结果
return reverseLinkedList(reversedHead);
}
// 辅助函数:反转链表
private ListNode reverseLinkedList(ListNode head) {
ListNode prev = null;
ListNode current = head;
while (current != null) {
ListNode next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;
}
题目链接:445. 两数相加 II - 力扣(LeetCode)
这道题就是上面链表加1的拓展,使用栈完全是没问题的,当然也可以使用链表反转,思路和上面都是差不多的,参考加一的操作来拓展即可。所以我们直接看代码吧!
补充 一点hxd们,学习算法本就是一件枯燥的事,因为就是那些知识,不会的该不会还是不会,我们只要用我们会的方法解决就行了。如果遇到写过的类似的还不会,不要气馁,毕竟我们都已经在路上了,坚持走下去,不说能达到什么高度吧,起码比昨天的自己更好!
鸡汤啥大家都该听的耳朵起茧子了,不管咋样,开心就好!
大致的思路就是把两个链表分别压入栈中,然后一起出栈,对结果进行计算取模,保存到新的链表中,最后将新链表反转即可,如果不想反转我们也可以在新链表插入结点时选择头插法!
这里我们就用头插法来实现!
代码如下:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack st1 = new Stack<>();
Stack st2 = new Stack<>();
// 入栈
while(l1 != null){
st1.push(l1.val);
l1 = l1.next;
}
while(l2 != null){
st2.push(l2.val);
l2 = l2.next;
}
ListNode dummy = new ListNode(0);
int carry = 0;
// 弹出元素进行相加
// 当两个栈都空了,若carry=0则退出循环
while(!st1.isEmpty() || !st2.isEmpty() || carry>0){
// 有进位用sum进行存储,在下个高位添加
int sum = carry;
if(!st1.isEmpty()){
sum += st1.pop();
}
if(!st2.isEmpty()){
sum += st2.pop();
}
// 头插
ListNode newNode = new ListNode(sum % 10);
newNode.next = dummy.next;
dummy.next = newNode;
carry = sum / 10;
}
return dummy.next;
}
大致就是先将两个链表反转,计算之后再将其发转一次即可!
代码如下:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 先反转两个链表
ListNode rs1 = reverseLinkedList(l1);
ListNode rs2 = reverseLinkedList(l2);
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
int carry = 0;
while(rs1 != null || rs2 != null ||carry>0){
int sum = carry;
if(rs1 != null){
sum += rs1.val;
rs1 = rs1.next;
}
if(rs2 != null){
sum += rs2.val;
rs2 = rs2.next;
}
cur.next = new ListNode(sum % 10);
cur = cur.next;
carry = sum / 10;
}
return reverseLinkedList(dummy.next);
}
private ListNode reverseLinkedList(ListNode head){
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
优质又高效!good
有没有收获hxd们,希望能够帮助到大家,也帮助我自己!努力吧!