203.移除链表元素
思路:链表的基础操作,若要删除的元素是头节点,则有两种方式:①直接使用原来的链表进行删除②设置一个虚拟头节点再进行删除
直接删除
class Solution {
public ListNode removeElements(ListNode head, int val) {
while(head != null && head.val == val) {
head = head.next;
}
if(head == null) {
return head;
}
ListNode p = head;
ListNode q = head.next;
while(q != null) {
if(q.val == val) {
p.next = q.next;
} else {
p = q;
}
q = q.next;
}
return head;
}
}
小结:直接删除中对于是否删除头节点的判断为什么是用while而不是if:如果链表中的每一个元素都是需要被删除的元素或者从头节点开始的一段链表中都是需要被删除的元素,则直接使头节点不断地移动,直到移动到不满足条件的位置为止。
设置虚拟头节点再删除
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode prev = new ListNode(0, head);
ListNode p = prev;
ListNode q = head;
while(q != null) {
if(q.val == val) {
p.next = q.next;
q = q.next;
} else {
q = q.next;
p = p.next;
}
}
return prev.next;
}
}
小结:意识到删除头节点元素的操作和删除其他节点元素的操作不同,若使用设置虚拟头节点再删除的方式便可以统一所有的删除操作,而使用直接删除的方式则需要对头节点删除进行单独的操作
707.设计链表
思路:链表的常规操作,不涉及算法思想
class ListNode {
int val;
ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
}
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if(index <0 || index >= size) {
return -1;
}
ListNode p = head;
for(int i = 0; i <= index; i++) {
p = p.next;
}
return p.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
ListNode p = head;
ListNode node = new ListNode(val);
if(index < 0) {
index=0;
}
if(index > size) {
return;
}
size++;
for(int i = 0; i < index; i++){
p = p.next;
}
node.next = p.next;
p.next = node;
}
public void deleteAtIndex(int index) {
ListNode p = head;
if(index < 0 || index >= size) {
return;
}
size--;
if(index == 0) {
head = head.next;
return;
}
for(int i = 0; i < index; i++){
p = p.next;
}
p.next = p.next.next;
}
}
小结:实现难度不大,但代码的很多细节处理需要注意
- 对于在头插入和尾插入的函数实现可以利用更一般的在指定位置插入函数来实现,进一步减小代码的复杂度
- 在插入和删除操作时,对于Index的值是否满足相应的要求要进行合理的分类
- 对于size变化的位置不能随便乱放,一般是在排除不能添加或删除的情况后立马对size的值进行相应的变化。如在删除元素的函数中,若将size--放在最后,便会导致当index=0的情况下返回时,未对链表的大小进行正确的更新。
206.反转链表
思路:可以采用栈解法和双指针法
- 栈解法:观察链表中元素的顺序由先变为后,便可以考虑结合栈先进后出的特性来解决。先将链表上的元素依次入栈,然后再将栈中元素依次出栈去更新链表上对应的元素
- 双指针法:观察链表元素的顺序可以发现,反转后的链表就可以看成是由初始链表两两之间交换指针所指的方向而得到,故使用pre和cur两个指针来完成指针所指方向的转变
栈解法
class Solution {
public ListNode reverseList(ListNode head) {
Stack stack = new Stack();
ListNode p = head;
while(p != null) {
stack.push(p.val);
p = p.next;
}
p = head;
while(p != null) {
p.val = (int)stack.pop();
p = p.next;
}
return head;
}
}
双指针解法
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
ListNode temp = null;
while(cur != null) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
小结:在双指针解法中有许多需要注意的点:
- 循环条件的判断:当pre移到到链表末尾时,cur的值为null,此时不需要再进行操作。故可以根据此确定循环条件为:cur != null。结合前面做题的经验,不难发现循环条件的判断应当善于利用边界条件,根据边界条件来确定循环条件
- 由于链表的特殊性,在进行交换的过程中,需要使用temp来暂存cur移动的下一位置
- 关于返回值的判断,需要结合题目具体的情况来选择