BG:day4.今天本来是想分享些自己做题里遇到的问题,但是想到需要作为博客发出来就写的啰嗦了些,写着写着自己原本想表达的东西就消失了,变成了crtl+cv的无营养品,为了写而写。后续自己会注意这个问题,做到尽量把自己最深的感悟写出来,而不是堆砌
1.移除链表元素
描述很简单的一道题目,下意识就写出了下面的代码
class Solution {
public ListNode removeElements(ListNode head, int val) {
//头结点可能被删除,所以考虑适用虚拟节点
ListNode dummyNode=new ListNode(-1);
dummyNode.next=head;
ListNode temp=dummyNode;
while(curr.next!=null){
if(curr.next.val==val){
curr.next=curr.next.next;
}
curr=curr.next;
}
return dummyNode.next;
}
}
- 问题1:会出现
空指针异常
:在代码第9行可能会出现curr指向的下一个节点为null
。那么经过第11行再次移动
后,while判断则会出现空指针异常。- 问题2:当我们删除某个节点后继续移动curr,会使得某些等于val值的指针被跳过。
解决以上问题也很简单,画图,一画图就非常清晰了,所以链表问题一定
多画图
,弄清指向
关系。//改正后的代码 while(curr.next!=null){ if(curr.next.val==val){ curr.next=curr.next.next; }else{ curr=curr.next; } }
2.设计链表
这道题目是让我们设计一个链表,完成新增,删除,查找的功能,leetcode中这样的题目还算比较多:像自己实现一个“LRU[最近最少使用]”,“实现一个字典树”,“实现一个特殊功能的栈等等”。第一遍做的时候可能比较懵,但第二三遍就比较熟练了,今天要提升的是**简洁自己的代码
**。
//题目初始的代码框架
class MyLinkedList {
public MyLinkedList() {
}
public int get(int index)
}
public void addAtHead(int val) {
}
public void addAtTail(int val) {
}
public void addAtIndex(int index, int val) {
}
public void deleteAtIndex(int index) {
}
}
以下是我完成的代码,能通过。但是看看addAtHead(),addAtTail(),addAtIndex()这三个函数的实现,都有很相似的部分
,比较冗余[完全可以进行替换]。这类优化还是比较常见的,即:如果我们实现了一个**一般的操作
**,特殊的操作
完全可以由一般的操作
来完成
class MyLinkedList {
int size;
ListNode dummyNode;
public MyLinkedList() {
this.size=0;
this.dummyNode=new ListNode(-1);
}
public int get(int index) {
}
//可简化成:addAtIndex(0,val);
public void addAtHead(int val) {
ListNode node=new ListNode(val);
ListNode tmp=dummyNode.next;
dummyNode.next=node;
node.next=tmp;
this.size++;
}
//可见简化成:addAtIndex(size,val);
public void addAtTail(int val) {
ListNode curr=dummyNode;
while(curr.next!=null){
curr=curr.next;
}
curr.next=new ListNode(val);
this.size++;
}
public void addAtIndex(int index, int val) {
if(index>size){
return;
}else if(index==size){
addAtTail(val);
}else{
ListNode curr=dummyNode;
int count=0;
while(count<index){
curr=curr.next;
//找到插入位置的前一个节点
count++;
}
ListNode tmp=curr.next;
ListNode node=new ListNode(val);
curr.next=node;
node.next=tmp;
this.size++;
}
}
public void deleteAtIndex(int index) {
}
}
class ListNode{
int val;
ListNode next;
public ListNode(int value){
this.val=value;
}
}
另外需要注意的是:使用单链表完成,优点是省空间,操作简单;缺点也很明显:查找,新增,删除元素都需要先遍历到咱们想去的位置再进行下一步的操作。双向链表可以优化此问题,再加上map来存储节点和索引的关系可进一步优化{比如实现LRU}
3.反转链表
反转链表1还是比较简单的,进一步的,反转链表2思路也比较容易,但想写出bug free的代码依然是比较困难的。
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
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 curr = rightNode.next;
// 注意:切断链接
pre.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间,注意:反转后头变成了尾巴,尾巴变成了头
reverseLinkedList(leftNode);
// 第 5 步:接回到原来的链表中
pre.next = rightNode;
leftNode.next = curr;
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;
}
}
}
class Solution {
public ListNode reverseBetween(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;
}
ListNode cur = pre.next;
ListNode next;
//pre是待反转区域的前一个节点,cur是待反转节点的前一个节点,next是cur.next,是需要反转的节点
for (int i = 0; i < right - left; i++) {
next = cur.next;
cur.next = next.next;
next.next = pre.next;//注意不是next.next=cur;
pre.next = next;
}
return dummyNode.next;
}
}