链表
- 节点类型:
class Node {
ElemType date
Node next;
}
链表属性:节点Node haed用来引用整个链表;
链表方法:
- 头插法:待插入节点.next指向head指向的节点,head指向新插入的节点(不带头节点情况:唯一一个不用判断链表是否为空的操作)
public void addFirst(int data) {
Node node = new Node(data);
node.next = head;
head = node;
}
- 尾插法:
- 判断链表是否为空?若为空则将head指向待插入节点,否则先找尾节点,将尾节点.next指向待插入节点;
public void addLast(int data){
Node node = new Node(data);
if(this.head == null) {
this.head = node;
}else {
Node cur = this.head;
//找尾巴 cur.next == null
while(cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
}
- 根据索引插入法:
- 判断索引位置是否和法,找到索引位置的前驱,进行插入;
public void addIndex(int date, int pos) {
//pos合法性检查pos从0开始
int size = 0;
Node cur = head;
while (cur != null) {
cur = cur.next;
size++;
}
if (size >= pos) {
if(pos == 0){//头插
addFirst(date);
}else {
Node node = new Node(date);
cur = head;
for (int i = 0; i < pos - 1; i++) {
cur = cur.next;//找前驱
}
node.next = cur.next;
cur.next = node;
}
}
删除指定值元素(只删除第一个即可)
方法一(找前驱法 ):一定要判断删除的节点是否是头节点?是就将head指向head.next,否则找到删除节点的前一个节点,并将其next指向待删除节点的后一个元素;
//找前驱法
public void remove(int val){
if(head != null && head.data == val){
head = head.next;
}else{
Node cur = head;
while(cur.next!=null && cur.next.data != val){
cur = cur.next;
}
cur.next = cur.next.next
}
}
方法二:(同样先判断是否删除头节点,算了不判断了)找到要删除的节点,将他的后继节点赋给它即将他覆盖,然后删掉后继节点;
局限性 : 但是如果删除的的是尾节点,就要不行了,其实也行,只是还有要回到找前驱的方法中,所以需要提前判断将删除尾节点的情况单独考虑;
public void deleteNode(ListNode head, int n) {
ListNode cur = head;
while (cur != null && cur.val != n && cur.next != null && cur.next.next != null) {
cur = cur.next;
}
//若循环是因为找打要删除的节点而结束的,此时cur指向就是要删除节点;
if(cur.val == n){
cur.val = cur.next.val; cur.next = cur.next.next;
}else{
//此时cur所指是尾节点的前驱
ListNode next = cur.next;
if(next.val == n){
cur.next = null;
}
}
}
总结:找前驱法要判断头节点,复制后继节点法要判断尾节点;
1.删除链表中等于给定值va1 的所有节点。
1. 链表为空则结束。
2. 从链表第二个节点开始判断,如果值为val则删除此节点,
3. 循环遍历整个链表结束 。
4. 判断头节点值是否为val,如果是就删除掉
public void removeAll(ElemType val){
if(head == null){
return ;
}
Node cur = head;
while(cur.next != null){
if(cur.next.date == val){//从第二个节点开始判断
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
if(head.date == val){
head = head.next;
}
}
- 设置prev法解决;
public void removeAll(ElemType val){
if(head == null){
return ;
}
Node cur = head.next;
Node prev = head;
while(cur != null ){
if(cur.date == val){
prev.next = cur.next;
cur = cur.next;
}else{
prev = prev.next;
cur = cur.next
}
}
if(head.date == val){
head = head.next;
}
}
2.反转-一个单链表。
1. 设置prev节点,和cur节点
2. 设置临时局部节点next保存cur.next;
3. 让cur.next指向prev,同时prev和cur后移即 prev = cur,cur = next;
public Node reverseList(){
if(head == null || head.next == null){
return head;
}
Node prev = null;
Node cur = head;
while(cur != null){
Node next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
head = prev;
return head;
}
3.给定一个带有头结点head 的非空单链表,返回链表的中间结点。奇数个节点返回中间,偶数个节点返回中间偏后位置。
1. 普通做法:cur=cur.next循环 len/2 次此时cur指向的就是 len/2 + 1处的节点(就是要找的节点)
2. 快慢指针发:快的一次走两步,慢的一次走一步。(快的踩的点是2n+1,慢的踩的点是n+1)
快的.next.next为null时(偶数节点数)慢的.next就是要求节点(偶数节点数)或.next为null时slow就是要求节点(奇数个节点)
如果直接判断'cur' != null,由于快指针一次走两步就可能发生了空指针异常;
//普通方法
public Node midNode(){
if(head == null || head.next == null){
return head;
}
int len = 0;
Node cur = head;
while(cur != null){
len++;
cur = cur.next;
}
cur = head;
for(int i = 0; i < len/2 ; i++){
cur = cur.next;
}
return cur;
}
- 快慢节点法
public Node midNode(){
if(head == null || head.next == null){
return head;
}
Node fast = head;
Node slow = head;
while(fast.next != null && fast.next.next != null){//顺序可不能反,注意空指针异常
fast = fast.next.next;
slow = slow.next;
}
/**
* while(fast != null && fast .next != null){
* fast = fast.next.next;
* slow = slow.next;
* }
* fast = null是偶数情况
* fast.next == null是奇数节点;
* return slow;
*/
if(fast.next == null){ //顺序可不能反,注意空指针异常
// 此时是奇数节点情况;
return slow;
}else{
//此时fast.next.next == null 快节点没到头少走一步,之所以这样判断是为了(若题目要求取偶数中间节点偏前一个节点时,方便)
return slow.next;
}
}
4.输入一个链表,输出该链表中倒数第k个结点。
1. 普通做法:len-k处(当然你应该先判断一下K)
2. 快慢指针发:快的先走K步 ,然后快慢一起走快的到头慢的就是要求节点。
public Node FindKthToTail(int k){
if(head == null ){
return head;
}
int len = 0;
Node cur = head;
while(cur != null){
len++;
cur = cur.next;
}
if( k > len || k <= 0){
return null;
}
cur = head;
for(int i = 0; i < len-k ; i++){
cur = cur.next;
}
return cur;
}
//快慢指针
public Node FindKthToTail(int k){
if(head == null && K <= 0 ){
return head;
}
Node fast = head;
Node slow = head;
for(int i = 0; i < k ; i++){
fast = fast.next;
if(fast == null && i < k){
System.out.println("K不合法");
break;
}
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
5.将两个有序链表合并为一个新的有序第一步判断如果连链表中有任链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
1. 若任意一个为空链表,则返回另一个链表(点两链表都为空是返回的就是null)
2. 设置两个节点作为迭代器分别遍历两个链表,取两节点中val较小的节点插入到第三个链表中对应的迭代器后移,直到有一个链表遍历完。
3. 将有剩余链表部分续到目标链表后面。
public Node mergeTwoLists(ListNode l1, ListNode l2) {
Linklist list3 = new Linklist() ;
Node cur3 = list3.head;
if(list1.head == null){
return list2.head;
}
if(list2.head == null){
return list1.head;
}
Node cur1 = list1.head;
Node cur2 = list2.head;
while(cur1 != null && cur2 != null){
if(cur1.date >= cur2.date){
Node node = new Node(cur2.date);//为了不破坏原来链表;
if(list3.head == null){
list3.head = node;
cur3 = list3.head;
}else{
cur3.next = node;
cur3 = cur3.next;
cur2 = cur2.next;
}
}else{
Node node = new Node(cur1.date);
if(list3.head == null){
list3.head = node;
cur3 = list3.head;
}else{
cur3.next = node;
cur3 = cur3.next;
cur1 = cur1.next;
}
}
}
if(cur1 != null){
cur3.next = cur1;
}else{
cur3.next = cur2;
}
return list3.head;
}
6.编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。
1. 新创建两个链表一个存储大于X的节点一个存储小于X的节点。将两链表拼接将链表尾节点置为null
public Node partition(int x){
if(head == null){
return null;
}
LinkedList small = new LinkedList();
LinkedList large = new LinkedList();
Node cur = this.head;
Node smallTail = small.head;
Node largeTail = large.head;
while(cur != null){
Node node = new Node(cur.date);
if(cur.date > x){
if(large.head == null){
large.head = node;
largeTail = large.head;
cur = cur.next;
}else{
largeTail.next = node;
largeTail = largeTail.next;
cur = cur.next;
}
}else{
if(small.head == null){
small.head = node;
smallTail = small.head;
cur = cur.next;
}else{
smallTail.next = node;
smallTail = smallTail.next;
cur = cur.next;
}
}
}
largeTail = null;
if(smallTail == null){
return large.head;
}
smallTail.next = large.head;
return small.head;
}
7.在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
1.链表为空或只有一个节点就直接结束;
2. if(head.date == head.next.date)
** 注意**:如果所有的尾节点都重复(ABBCCCCCCC型)如果采用一蹴而就的做法,即(对于)
index优化方法;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
while (head != null && head.next!= null && head.val == head.next.val) {
while (head.next != null && head.val == head.next.val) {
head = head.next;
}
head = head.next;
}
if(head == null){
return null;
}
ListNode cur = head.next;
ListNode prev = head;
int index = 0;
while (cur!=null ) {
if (cur.next != null && cur.val == cur.next.val) {
while (cur.next != null && cur.val == cur.next.val) {
cur = cur.next;
}
cur = cur.next;
index = 1;
}
if(index == 0){
//此时cur为不重复元素,只需移动prev和cur即可
prev = prev.next;
cur = cur.next;
}
if(index == 1){
// 此时的cur指向或为null,或为一个不重复元素;
prev.next = cur;
index = 0;
}
}
return head;
}
}
- 不必要优化法使用null收尾法
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
//只有一个节点,不存在重复;
return head;
}
while (head !=null && head.next != null&&head.val == head.next.val){
while(head.next != null&&head.val == head.next.val){
head = head.next;
}
head = head.next;
}
if(head == null){
return head;
}
ListNode prev = head;
ListNode cur = head.next;
while (cur != null) {
if (cur.next != null &&cur.val == cur.next.val) {
while (cur.next != null && cur.val == cur.next.val) {
cur = cur.next;
}
cur = cur.next;//此时cur指向的是不同于之前的重复元素,应进行继续循环,判断此元素是否唯一
} else {
//不重复和是尾节点不重复;
prev.next = cur;
prev = cur;
cur = cur.next;
}
}
//如果循环时因为尾节点都重复而结束的,此时prev.next没有改变,还连接着原来的链表,所以应该置为null
//如果正常结束prev.next本来就是指向null
prev.next = null;
return head;
}
}
- 方法二:将不重复节点放入一个新链表中
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null){
return head ;
}
ListNode retList = new ListNode(-1);
ListNode cur2 = retList;
ListNode cur = head;
while (cur != null) {
if (cur.next != null && cur.val == cur.next.val) {
while ( cur.next != null && cur.val == cur.next.val ) {
cur = cur.next;
}
cur = cur.next;
cur2.next = cur;
} else {
//这个节点不重复,将他放在新链表中;
cur2.next = cur;
cur2 = cur2.next;
cur = cur.next;
}
}
return retList.next;
}
}
8.链表的回文结构。
1. 将链表从len/2 + 1处开始逆置,当然你也可以逆置整个链表,然后比较;
public boolean chkPalindrome() {
if(head == null || head.next == null){
return true ;
}
int len = 0;
Node cur = head;
while(cur != null){
cur = cur.next;
len++;
}
cur = head;
len/=2;
for(int i = 0; i < len; i++){
cur = cur.next;
}
Node prev = null;
Node head2 = null;
while(cur != null){
if(cur.next == null){
head2 = cur;
}
Node next = cur.next;
//prev初始为null将后端链表独立;
cur.next = prev;
prev = cur;
cur = next;
}
Node cur1 = head;
Node cur2 = head2;
while(cur2 != null){
if(cur1.date != cur2.date){
return false;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
return true;
}
使用慢指针发从中间逆置;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null){
return true;
}
ListNode fast = head;
ListNode slow = head;
while(fast.next != null && fast.next.next!= null){
fast = fast.next.next;
slow = slow.next;
}
ListNode cur = slow.next;
slow.next = null ;
while(cur!= null){
ListNode Next = cur.next;
cur.next = slow;
slow = cur;
cur = Next;
}
while(head != null){
if(head.val != slow.val){
return false;
}
slow = slow.next;
head = head.next;
}
return true;
}
}
9.输入两个链表,找出它们的第一个公共结点。
1. 长链表迭代器先走完两个链表长度的差值,然后两迭代器同步进行遍历链表找到第一个.next相同的节点,此时.next所指向就是交点。
public Node getIntersectionNode(Node headA, Node headB){
int lenA = 0;
int lenB = 0;
Node cur1 = headA;
Node cur2 = headB;
while(cur1 != null){
cur1 = cur1.next;
lenA++;
}
while(cur2 != null){
cur2 = cur2.next;
lenB++;
}
cur1 = headA;
cur2 = headB;
if(lenA > lenB){
int len = lenA-lenB;
for(int i = 0 ; i < len; i++ ){
cur1 = cur1.next;
}
}else{
int len = lenB-lenA;
for(int i = 0 ; i < len; i++ ){
cur2 = cur2.next;
}
}
while(cur1.next != cur2.next ){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1.next;
}
10.给定一个链表, 判断链表中是否有环。
快慢指针法,快指针一次走两步,慢指针一次走一个节点,如果两只这相遇
public boolean Ishuan(){
if(head == null || head.next == null){
return false ;
}
Node fast = head;
Node slow = head;
while(fast.next != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow ){
return true;
}
}
return false;
}
11. 给定一个链表,返回链表开始入环的第一 个节点。 如果链表无环,则返回nu11
从快慢指针相遇点开始,让慢指针重回head处,快慢指针各自一步走,直到相遇,此时再次相遇就是入口点
```java
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null){
return null;
}
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next!= null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
//快慢指针相遇时,重置慢指针指向head,同步走直到相遇;
slow = head;
while(true){
if(slow == fast){
return slow;
}
fast = fast.next;
slow = slow.next;
}
}
}
return null;
}
}