203移除链表中元素
题目要求:
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
思路:
第一想法:
就是去遍历整个链表,找到对应元素进行删除即可。单链表的删除涉及到两个指针,一个前驱一个后继,但是这里还隐藏了一个特殊的指针即当前结点的工作指针 cur 。
困难:
- 虽然是二刷,但是没有想到设置一个虚拟结点,为了避免删除头结点时删除操作与其他结点操作不同的后果。
- 其他结点都是前驱的next指向后继,而如果删除头结点,需要将头结点向后移动。
- 所以设置虚拟结点的好处就是操作完全一样了,也可以不设置但是得分别操作。
正解:
- 设置虚拟结点,注意删除链表元素所需指针。
- 注意Java中ListNode类其中的ListNode方法重写了,一共有三种不同的使用。
代码:
/**
* 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) {
/**
遍历地去找值等于val的元素,删除之。具体删除操作怎么做,让后一个
直接指向前一个即可。结点是一个构造器的感觉,有值和next
注意链表的删除需要引入虚拟头结点
*/
//如果链表为空,返回头结点
if(head == null){
return head;
}
//不为空,则设计虚拟头结点,利用有参数构造器(上面三个构造器利用了方法的重写)
ListNode dummy = new ListNode(-1,head);//值为-1,指向原本的头结点
//设置一个前驱结点和一个当前结点,因为删除的时候需要用到前驱
ListNode pre = dummy;
ListNode cur = head;
while(cur != null){
//如果要删除
if(cur.val == val){
pre.next = cur.next;
}else{
//如果不删除pre就往后走,pre始终在cur的前面一个身位
pre = pre.next;
}
//无论删除与否,cur都要一直往后走
cur = cur.next;
}
return dummy.next;//最终实际的头结点一直在dummy的后面
}
}
ext;
}
return dummy.next;//最终实际的头结点一直在dummy的后面
}
}
707设计链表(十分重要)
题目要求:
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev
以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
- get(index):获取链表中第
index
个节点的值。如果索引无效,则返回-1
。 - addAtHead(val):在链表的第一个元素之前添加一个值为
val
的节点。插入后,新节点将成为链表的第一个节点。 - addAtTail(val):将值为
val
的节点追加到链表的最后一个元素。 - addAtIndex(index,val):在链表中的第
index
个节点之前添加值为val
的节点。如果index
等于链表的长度,则该节点将附加到链表的末尾。如果index
大于链表长度,则不会插入节点。如果index
小于0,则在头部插入节点。 - deleteAtIndex(index):如果索引
index
有效,则删除链表中的第index
个节点。
思路:
第一想法:
看到这一大串方法,有点慌;对于链表的操作大部分都需要采取虚拟头结点的设置。
困难:
- 如何控制工作指针在正确的位置上。插入删除这种操作都要让工作指针停在要进行修改的位置前面一个位置。
- 在控制过程中的边界条件(因为java中不允许 while(n)这样的语句,只能用for循环向后走)
- 为什么要写初始化链表那个操作?以及完整的语句是怎么写的?
正解:
- 其实最重要的是任意插入那个方法,因为头插和尾插都可以直接调用它。
- 注意为什么要有头结点的存在,为了让空链表时操作与平常链表一样。
代码:
//本题注意要操作的点一定是cur.next才能够进行增删
//My里面为什么写这些东西?在以下的方法可以直接用size吗?
//所有的方法都是有一个虚拟头结点的dummyHead
class MyLinkedList {
int size;
ListNode dummyHead;
//这一句初始化链表又是做什么?不应该使用构造器什么的吗
public MyLinkedList(){
size = 0;
dummyHead = new ListNode(0);
//这里是虚拟头结点
}
//获取第index个节点的值,这里得知道链表的长度(index从0开始)
public int get(int index) {
//我想用自己写的获得长度的方法,但是这个长度变量应该所有方法共享,都能访问。
if(index < 0 || index >= size ){
return -1;
}
ListNode cur = dummyHead;
//那如果此时链表为空怎么办?java里面不能写while(n)类型不匹配。。。
for(int i = 0; i <= index; i ++){
cur = cur.next;
}
return cur.val;
}
//头插节点
public void addAtHead(int val) {
ListNode cur = dummyHead;
//先创建一个新节点待插入
ListNode newList = new ListNode(val);
//有了虚拟结点之后,所有地方插入方式都一样,都是在两个之间插入。
newList.next = cur.next;
dummyHead.next = newList;
size ++;
}
//尾插法
public void addAtTail(int val) {
//得先走到最后一个节点,然后进行插入,要改变长度吗?
//创建一个节点待插入
ListNode newList = new ListNode(val);
ListNode cur = dummyHead;//工作指针要设计成从虚拟开始,因为链表可能为空
while(cur.next != null){
cur = cur.next;
}
cur.next = newList;//完成插入
size ++;
}
//第n个结点之前添加结点,必须保证工作指针指向第n个结点之前的结点。
public void addAtIndex(int index, int val) {
if(index > size){
return;//不要忘了这些健壮性条件
}
if(index < 0){
index = 0;//这里为什么等于0?
}
size ++;
ListNode cur = dummyHead;
for(int i = 0; i < index;i ++){//注意这里小于的不是-1
cur = cur.next;
}
//创建新节点
ListNode newList = new ListNode(val);
//将cur定在操作结点之前
//插入,要插入的主动
newList.next = cur.next;
cur.next = newList;
}
//删除第n个结点
public void deleteAtIndex(int index) {
if(index < 0 || index > size - 1){
return;
}
size --;
ListNode cur = dummyHead;
//先走到第index个结点之前,看是否走到可以尝试极端情况(只有一个节点时)
for(int i = 0; i < index ;i ++){
cur = cur.next;
}
cur.next = cur.next.next;
}
}
//写一个链表元素的定义,参考第203写的
class ListNode{
//先写链表的定义内容
int val;
ListNode next;
ListNode(){}
public ListNode(int val){
this.val = val;
}
}
/**
* 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);
*/
206 翻转链表
题目要求
思路一
只需要改变每个结点的next指向,从指向自己的后方改为前方,往回甩一下。要注意保存每个正操作结点的下一个结点。
代码一
/**
* 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) {
/**思路一,修改掉每个结点的next指针,让他的箭头向前甩,但是注意要保存结点的下一个位置 */
ListNode pre = null;
ListNode cur = head;
ListNode serv ;
while(cur != null){
serv = cur.next;//保存当时的下一个结点,因为要修改next
cur.next = pre;
pre = cur;//注意这里不要写成pre.next因为pre是你自己假设的
cur = serv;
//先保存下一个,再甩链子,再移动pre;最后移动当前位置。
}
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) {
/**思路二,头插法。从第二个开始到最后一个结点,先保存下一个,再删除它,最后头插在头部。
并且注意保存头结点,用来最后返回。 */
ListNode master = head;
//这么看来master一直是指在原来的头结点上方的
while(master != null){
ListNode newhead = DeletServe(master);//这里为什么传master,因为第二个第三个结点都是需要原来那个首结点来定位的,master的下一个每次指向第二个第三个...最后有一个。
if(newhead != null){
//头插
newhead.next = head;
head = newhead;
}else{
break;//会直接退出整个while循环。
}
}
return head;//而head是不断变化,一直在跑的。所以最后返回head
}
//写一个保存,然后删除的方法.删除now的next结点
public ListNode DeletServe(ListNode now){
if(now == null || now.next ==null ){
return null;
}
ListNode del = now.next;
now.next = now.next.next;//删除
return del;//这里返回的被删除的结点,因为最后还要头插
}
}
思路三
使用栈来逆转链表。但是写这道题时,对于Java的栈操作不是很熟悉,照着抄了一遍。
注意防止成环。但是为什么有环呢,因为最后的next并不知道指向哪里,万一指回去了呢?
代码三
/**
* 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) {
Stack<ListNode>stack = new Stack<>();//新建栈
//把链表的元素全部放入栈中
while(head != null){
stack.push(head);
head =head.next;
}
if(stack.isEmpty()){
return null;
}
ListNode master = stack.pop();
ListNode dummy = master;//用dummy来保存栈顶元素指向,最后要返回输出的
//将栈中元素全部输出组成一个链表
while(!stack.isEmpty()){
ListNode tempNode = stack.pop();
master.next = tempNode;
master = master.next;//master一直指向链表的最后
}
//防止成环
master.next = null;
return dummy;
}
}