目录
203 移除链表元素
看到题目的第一想法:刷过的题,看到后仍旧感觉无从下手,特别是虚拟头节点的定义,虚拟头节点dummy从-1开始,head从0开始
看完代码随想录之后的想法:卡哥提供的代码简洁明了,虚拟头节点的出现,是为了统一处理各个节点,不需要单独对head节点进行特殊处理。虚拟头节点如何手动书写,用来操作的cur、pre节点,cur用来表示当前正在操作的节点,pre表示cur的前一个节点。
代码实现:
- 虚拟头节点的方式
- while循环的条件是cur != null,cur一直向后移动,等其指向于null时,pre也到了最后一个val值不等于val的节点~
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
//使用虚拟头结点的方式,是删除操作统一化,不需要单独处理删除节点是头节点的问题
//虚拟头节点的定义,有一些印象,但虚拟头结点的定义不了解
ListNode dummy = new ListNode(-1, head);
//pre与cur分别指向虚拟头结点和头结点,用来进行操作,链表会进行删减,dummy用来最后的处理与指向
//定义pre节点
ListNode pre = dummy;
//定义cur节点
ListNode cur = head;
//while的循环条件是cur!=null,等于null时说明已到末尾,用cur代替了head,因为cur会变化
//而head经处理后不能一直变化~
while (cur != null) {
if (val == cur.val) {
//pre节点指向cur的下一个节点,由此来看pre和cur都是动态变化的值
pre.next = cur.next;
}else {
//cur的值与val不相等时,将cur赋值给pre?
pre = cur;
}
//cur向后移动一个节点
cur = cur.next;
}
//返回值时dummy的下一个节点,相当于head,指针的相关赋值和指向还是不太了解。。要再深入看看
return dummy.next;
}
}
- 不使用虚拟头节点的方式(只需要对head节点进行特殊处理,其他与采用虚拟头节点的方式一致)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
//不采用虚拟头结点的方式
while (head != null && head.val == val) {
head = head.next;
}
if (head == null) {
return head;
}
ListNode pre = head;
//此时head.val != val
ListNode cur = head.next;
while (cur != null) {
if(cur.val == val) {
pre.next = cur.next;
}else {
pre = cur;
}
cur = cur.next;
}
return head;
}
}
- 不使用虚拟头节点和pre节点的方式(对head进行特殊处理,那为什么还要加pre节点呢???后续待补充)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
while(head != null && head.val == val) {
head = head.next;
}
if (head == null) {
return head;
}
ListNode cur = head;
while (cur != null) {
//下方应该使用while而非if,即将与val相等的值都移除
while (cur.next != null && cur.next.val == val) {
cur.next = cur.next.next;
}
cur = cur.next;
}
return head;
}
}
实现过程中遇到哪些困难:
- 虚拟头节点dummy定义的代码书写,循环终止条件的确定,最终返回值的确定(return head?还是dummy.next,或者pre?)
707 设计链表
看到题目的第一想法:题目已经看到好几遍了,但是还是没有思路,使用增删查还行,设计的话确实没有思路。
看完代码随想录之后的想法:按部就班、递进的写,就能把一个复杂的问题慢慢解决掉。所以不要怕困难,逃避解决不了任何问题,那些逃避掉的困难,总有一天会更加凶猛的找上你!
- 我这辈子就是吃了逃避的大亏了!
代码实现:
- 先提供一个单链表实体类,而后设置链表元素的个数size,虚拟头节点head,而后使用构造方法进行初始化,此处虚拟头节点设置的是-1,卡哥解法里给的是0 ,感觉不太好理解。
- 当不确定while循环的终止条件时,使用边界值验证一下,但后续也要加强理解,若面试时不能在边界值浪费太多时间。
- 当插入、或者删除某个节点时,应该让cur.next执行要处理的节点,cur指向处理节点的前一个节点,并且链表的长度要相应的增加或者减少;新增节点时要创建节点,给节点赋值后再新增!
class ListNode{
int val;
ListNode next;
public ListNode(){
}
public ListNode(int val) {
this.val = val;
}
}
//要保证操作的节点n是cur.next节点,这样才能方便处理
//先指向后面的边,再指前面的边
class MyLinkedList {
//存储链表元素的个数
int size;
//虚拟头结点
ListNode head;
//初始化链表
public MyLinkedList() {
size = 0;
//此处将虚拟头节点的index设置为-1了!!!
head = new ListNode(-1);
}
//0是第一个节点
public int get(int index) {
if (index < 0 || index >= size) {
return -1;
}
//因为链表包含了虚拟头结点,故去index+1对应的值
//虚拟头结点的位置从-1开始算时
ListNode cur = head;
cur = cur.next;
while (index > 0) {
cur = cur.next;
index--;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index < 0) {
index = 0;
}
if (index > size) {
return;
}
size++;
//包含了虚拟头节点?
ListNode cur = head;
//找到插入节点的前缀
for (int i = 0; i < index; i++) {
//让前一个节点为cur,下一个节点为要操作的位置
cur = cur.next;
}
ListNode tmp = new ListNode(val);
tmp.next = cur.next;
cur.next = tmp;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
//链表长度-1
size--;
//保证要操作的元素为cur.next,cur为前一个元素
ListNode cur = head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
实现过程中遇到哪些困难
- 虚拟头节点的定义,从-1开始
- 保证要操作的元素为cur.next,cur为前一个元素
- 以节点的插入为例,先tmp.next = cur.next,而后再cur.next = tmp;即先后面的边,而后前面的边,认真理解~
- 双链表的设计还未处理,后面周末补上~
206 反转链表
看到题目的第一想法:看到题后没有思路,自己也不敢写写试试,这是大忌,行不行都可以试试!
看完代码随想录之后的想法:双指针法简单且清晰,递归法不是很理解~
代码实现:
- 双指针法(cur.next先指向pre,而后再给pre赋值,最后给cur赋值,cur要赋的值之前由tmp保存着)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//看到题目后完全没有思路。。
ListNode pre = null;
ListNode cur = head;
ListNode tmp = null;
//先将cur.next的值用tmp保存下来,因为断开指针会丢失;而后将cur->pre,在将cur赋值给pre
//而后将tmp赋值给cur;cur始终快pre一步,cur==null时,pre正好为最后一个非空节点
while (cur != null) {
//tmp存放cur的下一个节点值
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
- 递归法(对递归不太了解,此处只做了两件事,保存下一个节点的值和指向前一个节点)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//对递归的写法不太了解
return reverse(null, head);
}
public ListNode reverse(ListNode pre, ListNode cur) {
if (cur == null) {
return pre;
}
ListNode tmp = null;
//保存下一个节点
tmp = cur.next;
//反转
cur.next = pre;
return reverse(cur, tmp);
}
}
实现过程中遇到哪些困难:
- 有点思路,又感觉无从下手,然后就不敢写了,去翻答案了。。
- Just do it !
- 对递归不熟悉,后面debug练习练习
今日收获,记录一下自己的学习时长
- 算法处理约4h,博客编写约2h
- 了解了链表的基本操作,链表类、增加、删除、查询、链表元素移除、链表反转操作等,这块仍不如数组熟练,应该是链表没有数组结构清晰明了的原因,应当多加练习!
- 贵在坚持,加油!