剑指 Offer II 021. 删除链表的倒数第 n 个结点https://leetcode.cn/problems/SLwz0R/
最近都在刷链表的题。这道也是属于单链表的题,难度中等,目的是删除倒数第n个节点,第一想到的是用栈,不过也需要遍历两次,不知道有没有只需要遍历一次的方法,反正当时也想不出其他办法,就先用栈了,代码也是五分钟写完了,测试用例也通过。
思想:先把全部节点遍历一次,然后把他们按顺序全部放入栈中,然后再从栈顶开始往下遍历,找出倒数第n个节点,然后移出栈,此时需要删除的节点已经移出栈了,但是它next指向的节点序列我们仍需保留,所以再用栈顶的节点的next指向最后一个出栈的节点的next。当时我就提交了,感觉还蛮简单的。但是我没有注意一个问题,我后面说。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head==null){
return null;
}
ListNode temp=head;
Stack<ListNode> stack = new Stack<>();
while (temp!=null){
stack.push(temp);
temp = temp.next;
}
while (n!=0){
temp = stack.pop();
n--;
}
stack.peek().next=temp.next;
temp.next=null;
return head;
}
}
本来理论上是可以的,测试用例也是通过的,但遇到删除头节点的用例时就报错了,报的是空栈异常。检查发现:当删除的是头节点时,把头节点移出栈后,就已经是空栈了。然后我们把头节点后移一位就可以了。修改代码为:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head==null){
return null;
}
ListNode temp=head;
Stack<ListNode> stack = new Stack<>();
while (temp!=null){
stack.push(temp);
temp = temp.next;
}
while (n!=0){
temp = stack.pop();
n--;
}
if (stack.isEmpty()){
head=head.next;
}else{
stack.peek().next=temp.next;
temp.next=null;
}
return head;
}
}
提交结果:
然后今天晚上又刷了一到超级重量级的题目,可能难度不是太大,属于中等难度,我花了一晚上的时间去改bug,让我收获非常大。尤其是这里无意间解决了前几篇博客说到的一个面试问题,这个真的是意外的收获。
剑指 Offer II 025. 链表中的两数相加https://leetcode.cn/problems/lMSNwu/
这道题的目的是链表相加,返回新链表。这里我首先想到的是用两个栈分别遍历全部入栈,然后再分别取出栈顶元素相加。感觉思想非常简单,但写代码写到一半的时候,忽然想到了一个更好的办法,反正都要进行两次遍历,而且使用栈空间会很损耗性能,而且相加的时候要自定义加法机制,倒不如用两个字符串保留全部数字,再使用Java提供的int类型加法机制,最后再遍历一次放入链表返回,这样便可以剩下不少空间。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if (l1 == null && l2 == null) {
return null;
}
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode temp = l1;
StringBuffer buffer1 = new StringBuffer();
StringBuffer buffer2 = new StringBuffer();
while (temp!=null){
buffer1.append(temp.val);
temp=temp.next;
}
temp=l2;
while (temp!=null){
buffer2.append(temp.val);
temp=temp.next;
}
Long res = Long.parseLong(buffer1.toString())+ Long.parseLong(buffer2.toString());
String s = res+"";
char[] chars = s.toCharArray();
ListNode head = new ListNode(-1);
ListNode cur=head;
for(int i=0;i<chars.length;i++){{
ListNode node = new ListNode(Integer.parseInt(String.valueOf(chars[i])));
cur.next=node;
cur=node;
}
}
return head.next;
}
}
这里原本我用的不是Long类型,原本是用的Int类型,直到提交时测试用例错误,那个用例的其中一个链表长度超过了十位数,超出了int类型所表示的范围,后面我才改了Long类型,谁知道哪些用例一个比一个奇葩,后面出现了一个长度更长的链表,long类型也无法满足,后面百度到了一篇新大陆——BigInteger和BigDecimal,之前面试问过而我又刚好不会,而且面试过后复盘也找不到解决方案。这里我搜了用法,刚好弥补了我这部分的知识盲区。这两个类是java.math包里面的,能分别无限度处理整数类型和浮点数类型的数学运算。后面我修改了一下代码提交成功。虽然我在这题上花了很长时间,大部分时间都用在代码试错和百度类型转换和结果长度溢出的解决方案,但总的来说收获还是蛮大的。所以说刷多点题还是有用的。
import java.math.BigInteger;
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if (l1 == null && l2 == null) {
return null;
}
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode temp = l1;
StringBuffer buffer1 = new StringBuffer();
StringBuffer buffer2 = new StringBuffer();
while (temp!=null){
buffer1.append(temp.val);
temp=temp.next;
}
temp=l2;
while (temp!=null){
buffer2.append(temp.val);
temp=temp.next;
}
BigInteger res = new BigInteger(String.valueOf(buffer1)).add(new BigInteger(String.valueOf(buffer2)));
String s = res+"";
char[] chars = s.toCharArray();
ListNode head = new ListNode(-1);
ListNode cur=head;
for(int i=0;i<chars.length;i++){{
ListNode node = new ListNode(Integer.parseInt(String.valueOf(chars[i])));
cur.next=node;
cur=node;
}
}
return head.next;
}
}
测试结果:
本来想看下官方怎么处理数据溢出的,翻看了一下官方解法和评论,大部分用的是栈,跟我第一想到的方法差不多。 跟用栈相比我这个解法确实省去了栈空间,但运行时间却长了,确实二者不可兼具。