地址相等用“==”
谁指向谁,用“
思路
遍历数组,根据数组的值创建新的结点,把新的节点尾插~
创建两个引用,一个记录头结点,一个记录尾节点
需要注意当前是链表否空,(头插还是尾插)
public static Node arrayToLinkedList(int[] array){
//遍历数组,把元素依次进行尾插
//每次尾插都需要知道当前链表的末尾结点
//每次招末尾及诶单太麻烦,可以直接使用一个引用tail,把末尾结点记住
Node head=null;
Node tail=null;
for (int i = 0; i < array.length; i++) {
Node node=new Node(i);
//使用node进行尾插
//需要判断当前链表是否为空
if(head==null){
head=node;
tail=node;
}else{
//如果链表不为空,再进行新的插入,直接操作tail
tail.next=node;
//一旦插入完成,更新tail的指向
tail=tail.next;
}
}
return head;
}
//使用带傀儡结点
//使用带傀儡节点的链表
public static Node arrayToLinkedList1(int[] array) {
//如果使用带傀儡的链表,后续的插入不需要分两种情况
//创建一个傀儡结点,此时head指向傀儡结点
Node head=new Node(100);
//创建一个tail,保存尾结点
Node tail=head;
for (int i = 0; i < array.length; i++) {
Node node=new Node(i);
tail.next=node;
tail=tail.next;
}
//如果返回head,head指向的是傀儡结点,不符合要求
//返回head.next就可以
return head.next;
}
切记 判断非空
思路:
首先判断链表是否为空,其次看链表头结点是不是为val(这段代码放在最后,因为1123,val=1.这种情况如果放在前面,第二个1将不能被删除~)
双指针,cur找到val值所在的节点,prev记录前一个位置,依次删除,细节请看下图:
力扣网址
public static ListNode removeElements(ListNode head, int val) {
//删除操作,需要考虑待删除元素师投机诶单
//删除操作,需要找到当前节点的前一个节点
if(head==null){
return null;
}
//删除操作,找到待删除及结点的前一个结点
ListNode prev=head; //待删除结点的前一个
ListNode cur=head.next; //待删除节点的
while(cur!=null){
if(cur.val==val){
//找到了值为val的结点
//需要删除这个节点
prev.next=cur.next;
cur=prev.next;
}else{
prev=prev.next;
cur=cur.next;
}
}
if(head.val==val){
head=head.next;
}
return head;
}
思路:创建三个引用,链表反转,让cur的next指向prev,然后更新head
class Solution {
public static ListNode reverseList(ListNode head) {
if(head==null){
return null;
}
if(head.next==null){
return head;
}
//定义个引用
ListNode prevNode=null;
ListNode curNode=head;
while(curNode!=null){
ListNode nextNode =curNode.next;
curNode.next=prevNode;
//修改指针指向
prevNode=curNode;
curNode=nextNode;
}
return prevNode;
}
}
思路1:
public int getLength(ListNode head){
int len=0;
for (ListNode cur=head;cur!=null;cur=cur.next){
len++;
}
return len;
}
public ListNode middleNode(ListNode head) {
// 虽然题目要求中, 说了 head 是一个非空单链表.
// 但是题目的实际测试用例, 可能不讲武德, 拿一个空链表偷袭你~
// 实际面试的时候, 面试官一般还是希望你能写出这些合法性校验的代码的(良好的编程意识)
if(head==null){
return null;
}
// 求链表的长度
int len =getLength(head);
//得到引用走的步数
int steps=len/2;
ListNode cur=head;
for(int i=0;i<steps;i++){
cur=cur.next;
}
return cur;
}
思路2
快慢指针,快指针走2步,慢指针走1步,当快指针走到最后一个(fast.next!=null),慢指针的当前位置就是中间节点
class Solution {
//快慢指针
public ListNode middleNode(ListNode head) {
if(head==null){
return null;
}
if(head.next==null){
return head;
}
ListNode fast=head;
ListNode slow=head;
//遍历到最后一个元素是f.next不为空
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
}
思路
public int getLength(ListNode head){
int len=0;
for(ListNode cur=head;cur!=null;cur=cur.next){
len++;
}
return len;
}
public ListNode FindKthToTail(ListNode head,int k) {
if(head==null){
return head;
}
int len=getLength(head);
if(k>len){
return null;
}
if(k<0){
return null;
}
// 得到倒数第 k 个节点, 就让引用从头开始, 走 length - k 步即可
int step=len-k;
ListNode cur=head;
for(int i=0;i<step;i++){
cur=cur.next;
}
// 此时, cur 就指向了倒数第 k 个节点
return cur;
}
创建一个带头结点的链表,
比较两个
力扣网址
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
// 两个链表都非空, 进行合并.
ListNode cur1 = l1;
ListNode cur2 = l2;
// 创建了一个新的链表, 用来保存最终结果.
// 为了简化后续的插入操作, 此处使用一个 带傀儡节点 的链表.
ListNode newHead = new ListNode(0);
// 后续需要频繁进行尾插. 为了尾插方便, 使用一个变量把链表的尾部给记录下来.
// 虽然链表一般都是用头结点来表示, 但是也是完全可以通过其他引用记录其他位置. 典型的就是记录尾节点.
ListNode newTail = newHead;
// 进行循环遍历两个链表, 并比较. 任意引用到达链表末尾, 都算循环结束.
while (cur1 != null && cur2 != null) {
if (cur1.val < cur2.val) {
// 就把 cur1 插入到新链表末尾
newTail.next = cur1;
// 更新循环变量
cur1 = cur1.next;
} else {
// 就把 cur2 插入到新链表末尾
newTail.next = cur2;
// 更新循环变量
cur2 = cur2.next;
}
newTail = newTail.next;
}
// 当上述循环结束, 意味着一定有一个引用已经先到达了链表末尾.
// 于是就把另一个链表的剩余部分插入过来即可.
if (cur1 == null) {
newTail.next = cur2;
} else {
newTail.next = cur1;
}
// 返回结果链表. 此时我们需要把傀儡节点跳过.
return newHead.next;
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//判断其中一链表是否为空
if(l1==null){
return l2;
}
if(l2==null){
return l1;
}
ListNode cur1=l1;
ListNode cur2=l2;
ListNode newHead=new ListNode(0);
ListNode newTail=newHead;
while(cur1 !=null && cur2 !=null){
if(cur2.val>cur1.val){
//和老师写的大致相同
// newTail.next=cur1;
//newTail=newTail.next;
//循环变量更新语句
//cur1=cur1.next;
newTail.next=new ListNode(cur1.val);
newTail =newTail.next;
cur1=cur1.next;
}else{
newTail.next=new ListNode(cur2.val);
newTail =newTail.next;
cur2=cur2.next;
}
}
if(cur1==null){
newTail.next=cur2;
}
if(cur2==null){
newTail.next=cur1;
}
//切记返回傀儡节点后面的代码
return newHead.next;
}
思路
public ListNode partition(ListNode pHead, int x) {
if (pHead == null) {
return null;
}
if (pHead.next == null) {
return pHead;
}
// 处理一般情况. 需要创建两个链表, 用来保存两部分结果.
// 为了方便后续的尾插操作, 仍然使用带傀儡节点的链表, 同时记录链表末尾
ListNode smallHead = new ListNode(0);
ListNode smallTail = smallHead;
ListNode largeHead = new ListNode(0);
ListNode largeTail = largeHead;
for (ListNode cur = pHead; cur != null; cur = cur.next) {
if (cur.val < x) {
// 比基准值小, 就插入到 smallHead 的末尾
// 由于旧的链表是使用 for 的方式直接遍历, 就会一直执行到 cur = cur.next
// 通过这样的方式尾插, 可能会对原来链表的遍历造成影响. 稳妥起见, 创建新的节点, 而不是拆掉
// 旧的链表.
smallTail.next = new ListNode(cur.val);
smallTail = smallTail.next;
} else {
// 大于等于基准值, 就插入到 largeHead 的末尾
largeTail.next = new ListNode(cur.val);
largeTail = largeTail.next;
}
}
// 经过了上面的循环, 此时链表已经被拆成两个部分了.
// 第一部分就都是小于 x 的元素.
// 第二部分就都是大于等于 x 的元素.
// 最后一步, 需要把两个链表合并成一个, 直接收尾相接即可.
smallTail.next = largeHead.next;
return smallHead.next;
}
}
思路:排序结点的重复结点应该是相邻的,重复结点不一定重复一次**
遍历链表,需要找到当前哪些结点是重复节点~
如果当前节点是重复节点,直接跳过,下一个。如果当前结点不是重复结点,就把节点插入到新链表。
创建一个新链表,把无重复节点尾插到新链表中。
public class LinkedList {
public ListNode deleteDuplication(ListNode pHead) {
// 先考虑特殊情况
if (pHead == null) {
return null;
}
if (pHead.next == null) {
// 链表里只有一个节点.
return pHead;
}
// 用来保存结果的链表. 这个链表也是一个带傀儡节点的链表.
// 为了尾插方便, 记录链表的尾部
ListNode newHead = new ListNode(0);
ListNode newTail = newHead;
// 遍历链表, 判定其中是否存在重复的节点
ListNode cur = pHead;
while (cur != null) {
//如果把 cur.next !=null放在while的括号后面,则会跳过最后一个结点
//所以把cur.next !=null放在if括号前面,确保cur.next不为空。
if (cur.next != null && cur.val == cur.next.val) {
// 发现 cur 是重复节点, 就需要找到接下来不重复的节点位置.
while (cur != null && cur.next != null
&& cur.val == cur.next.val) {
cur = cur.next;
}
// 上面的循环结束, 要么是 cur 已经到达了链表末尾, 要么是 cur 已经到达了重复元素中的最后一个.
// 此时需要让 cur 再往后走一步, 指向下一个不重复的节点
} else {
// cur 不是重复节点. 直接插入到 newHead 末尾即可.
newTail.next = new ListNode(cur.val);
newTail = newTail.next;
}
// 循环需要往下走一步.
cur = cur.next;
}
return newHead.next;
//如果没有重复结点不保留,这个是对的
if(cur.val !=cur.next.val){
//进行尾插
newTail.next=cur;
newTail=newTail.next;
}
思路
1.给定一个单链表A,先把这个A拷贝一份,得到B
2.然后把B逆置
3.然后对比A和B是不是一样的单链表
public boolean chkPalindrome(ListNode A) {
// write code here
if(A==null){
return true;
}
if(A.next==null){
return true;
}
ListNode cur=A;
//1.创建新链表,然后通过尾插复制链表
//创建带傀儡节点的链表
ListNode newHead=new ListNode(0);
ListNode newTail=newHead;
while(cur!=null){
newTail.next=cur;
newTail=newTail.next;
cur=cur.next;
}
//新链表为B
ListNode B=newHead.next;
//2新链表逆置
ListNode prevNode=null;
ListNode curNode=B;
while(curNode!=null){
ListNode nextNode=curNode.next;
if(nextNode==null){
//curNode就指向最后一个节点,也就是逆置后链表的头结点
B=curNode;
}
//1.拜倒叉
curNode.next=prevNode;
//2.更新
prevNode=curNode;
curNode=nextNode;
}
//3.判断新链表和旧链表是否相等
ListNode cur1=A;
ListNode cur2=B;
//因为cur1和cur2指向不同的节点,所以先判断相等,在遍历循环变量
while(cur1 != null && cur2 !=null){
//判断相等
if(cur1.val==cur2.val){
return true;
}
//遍历循环变量
cur1=cur1.next;
cur2=cur2.next;
}
return false;
}
思路
1.分别求出两个链表的长度l1和L2;
2.分别创建两个引用,指向两个链表的头结点cur1和cur2;
3.判断这两个链表谁长,如果l1>l2,就让cur1先走l1-l2步;如果l1
public int getLength(ListNode head){
int len=0;
for (ListNode cur=head;cur!=null;cur=cur.next){
len++;
}
return len;
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//1.判断两个链表的长度
int l1=getLength(headA);
int l2=getLength(headB);
//2..分别创建两个引用,指向两个链表的头结点cur1和cur2;
ListNode cur1=headA;
ListNode cur2=headB;
//3.判断这两个链表谁长
if(l1>l2){
int steps=l1-l2;
for(int i=0;i<steps;i++){
cur1=cur1.next;
}
}else{
int steps=l2-l1;
for(int i=0;i<steps;i++){
cur2=cur2.next;
}
}
//4.此时cur1和cur2在同一处,与世同步往后走,看是否相遇。
while(cur1!=null && cur2!=null ){
// 此处比较的不是节点里的 val, 而是节点对象的身份.
//因为刚开始时cur1和cur2没有指向同一个节点,所以,先判断是否相等,在遍历循环
if(cur1==cur2){
//相交了,找到了交点。
return cur1;
}
//遍历循环变量
cur1=cur1.next;
cur2=cur2.next;
}
//没相交,返回空
return null;
}
有环,就是链表的最后一个节点指向了链表中的一个结点(没有哪个节点的next是null)
采用快慢指针的方法
思路:首先定义两个指针 fast和slow,其次快指针一次走两步,慢指针一次走一步。如果有环,快指针会和慢指针总会重合,所以直接判断快慢指针是否相等,可以知道是否有环。
注意循环条件:如果有环,fast走两步,所以fast先到达终点,直接判断fast是否为空,但因为fast一次走两步,所以也判断fast.next是否为空(避免fast.next.next为空)
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
//fast走两步,所以fast先到达终点,直接判断fast是否为空,
// 但因为fast一次走两步,所以也判断fast.next是否为空
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(fast==slow){
return true;
}
}
return false;
}
}
思路
2.让头结点和交汇处结点同样的速度走,两个节点相等,就是环的入口结点。
public ListNode detectCycle(ListNode head) {
//判断有无环
ListNode fast=head;
ListNode slow=head;
while(fast != null && fast.next !=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
break;
}
}
// //循环跳出的条件:1.fast==slow 2.fast==null 或slow==null,
//所以,我们应该判断是否因为第二个原因跳出循环。
if(fast==null || fast.next==null){
return null;
}
ListNode cur1=head;
ListNode cur2=fast;
while(cur1!=cur2){
cur1= cur1.next;
cur2=cur2.next;
}
return cur1;
}
让 cur走 step步
for(int i=0;i<step;i++){
cur=cur.next;
}