算法通关村第二关—链表反转的拓展问题(白银)

       链表反转的拓展问题

一、指定区间反转

 LeetCode92:给你单链表的头指针head和两个整数left和right,其中left<=right。请你反转从位置left到位置right的链表节点,返回反转后的链表。
截屏2023-12-02 13.01.05.png

1.1 头插法

 反转的整体思想是,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。
image.png

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy, last= dummy;
        int num = 0;
        for(num = 0; num < left - 1; num++){
            pre = pre.next;
        }
        ListNode cur = pre.next;
        for(int i = 1; i <= right - left; i++){
            ListNode next = cur.next;
            cur.next = next.next;
            next.next = pre.next;
            pre.next = next;
        }
        return dummy.next;
    }
}

1.2 穿针引线法

算法步骤:
第1步:先将待反转的区域反转;
第2步:把pre的next指针指向反转以后的链表头节点,把反转以后的链表的尾节点的next指针指向Succ。
image.png

public ListNode reverseBetween(ListNode head,int left,int right){
	//因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
	ListNode dummyNode = new ListNode(-1);
	dummyNode.next = head;
	ListNode pre = dummyNode;

	//第1步:从虚拟头节点走1eft-1步,来到Left节点的前一个节点
	//建议写在fo「循环里,语义清晰
	for (int i = 0;i < left -1; i++)
	pre = pre.next;

	//第2步:从pre再走right-left+1步,来到right节点
	ListNode rightNode = pre;
	for (int i = 0;i < right - left +1; i++)
	rightNode = rightNode.next;

	//第3步:切出一个子链表
	ListNode leftNode = pre.next;
	ListNode succ = rightNode.next;

	//思考一下,如果这里不设置next为null会怎么样
	rightNode.next null;
	//第4步:同第206题,反转链表的子区间
	reverseLinkedList(leftNode);
	//第5步:接回到原来的链表中
	//想一下,这里为什么可以用rightNode
	pre.next = rightNode;
	leftNode.next = succ;
	return dummyNode.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;
}
}

二、两两交换链表中的节点

 LeetCode24.给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)
image.png
image.png
指针调整如下
image.png

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy, cur = head;
        while(cur != null && cur.next != null){
            ListNode next = cur.next;
            cur.next = next.next;
            next.next = pre.next;
            pre.next = next;
            //两两交换,要把pre移到本次交换后的第二个元素,cur移到下一次交换的第一个元素
            pre = cur;
            cur = cur.next;
        }
        return dummy.next;
    }
}

三、单链表加1

 LeetCode369.用一个非空单链表来表示一个非负整数,然后将这个整数加一。你可以假设这个整数除了0本身,没有任何前导的0。这个整数的各个数位按照高位在链表头部、低位在链表尾部的顺序排列。
image.png
我们先看一下加法的计算过程:
 计算是从低位开始的,而链表是从高位开始的,所以要处理就必须反转过来,此时可以使用栈,也可以使用链表反转来实现。
 基于栈实现的思路不算复杂,先把题目给出的链表遍历放到栈中,然后从栈中弹出栈顶数字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; //负责给个位数加1
while (!st.empty() || carry != 0){
	int digit = st.empty() ? 0 : st.pop();
	int sum = digit + adder + carry;
	carry = sum >101  :0;
	sum = sum > 10 ? sum -10 : sum;
	ListNode cur = new ListNode(sum);
	cur.next = dummy.next;
	dummy.next = cur;
	adder = 0;
}
return dummy.next;
}

四、链表加法

 相加相链表是基于链表构造的一种特殊题,反转只是其中的一部分。这个题还存在进位等的问题,因此看似简单,但是手写成功并不容易。
 LeetCode445题,给你两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。你可以假设除了数字0之外,这两个数字都不会
以零开头。示例:
image.png

(1)使用栈实现

 思路是先将两个链表的元素分别压栈,然后再一起出栈,将两个结果分别计算。之后对计算结果取模,模数保存到新的链表中,进位保存到下一轮。完成之后再进行一次反转就行了。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode headsum = new ListNode(0);
		//创建两个栈分别存储l1和l2的每一位
        Deque<ListNode> stack1 = new LinkedList();
        Deque<ListNode> stack2 = new LinkedList();
        while(l1 != null){
            stack1.push(l1);
            l1 = l1.next;
        }
        while(l2 != null){
            stack2.push(l2);
            l2 = l2.next;
        }
        int judge = 0;//判断是否有进位
        while(!stack1.isEmpty() || !stack2.isEmpty() || judge == 1){
			//初始化结点为0,巧妙处理一个栈为空另外一个栈不为空的情形
            ListNode node1 = new ListNode(0);
            ListNode node2 = new ListNode(0);
            if(!stack1.isEmpty()) node1 = stack1.pop();
            if(!stack2.isEmpty()) node2 = stack2.pop();
            int number = (node1.val + node2.val) + judge;
            ListNode num = new ListNode(number % 10);
			//顺势解决链表反转问题
            num.next = headsum.next;
            headsum.next = num;
			
            judge = number > 9 ? 1 : 0;
        }
        return headsum.next;
    }
}

(2)使用链表实现

 如果使用链表反转,先将两个链表分别反转,最后计算完之后再将结果反转,一共有三次反转操作,所以必然将反转抽取出一个方法比较好,代码如下:

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;
}
}

五、再论链表的回文序列问题

 在上一关介绍链表回文串的时候,我们介绍的是基于栈的,相对来说比较好理解,但是除此之外还有可以使用链表反转来进行,而且还可以只反转一半链表,这种方式节省空间。
 我们姑且称之为“快慢指针+一半反转”法。
 这个实现略有难度,主要是在while循环中pre.next=prepre和orepre=pre两行实现了一边遍历一边将访问过的链表给反转了,所以理解起来有些难度,如果不理解可以在学完链表反转之后再看这个问题。

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值