C++/java中的链表及常见题目汇总
一、链表
链表实现了零碎数据的有效组织,比如说,我们用malloc申请内存,内存足够,而由于内存碎片太多,没有连续内存,只能以申请失败而告终。而用链表这种数据结构来组织数据,可以解决此类问题。
链表中至少有两个域,一个域用于数据元素的存储,一个域指向其他单元的指针。
链表可以分为单向链表和双向链表。
1. 单向链表
单向链表(单链表)是链表的一种,其特点是链表的连接方式是单向的,对俩表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称结点列表。因为链表是由一个个结点组装起来的,其中每个结点都有指针成员变量中的下一个结点
由于STL中有可以直接使用的库list,但是为了更清楚链表的构成,我们尝试自己实现一个链表,包括如下内容
- 链表的创建
- 尾插法
相较与头插法,尾插法是更简单的,其就是每次在链表的尾部插入数据
链表中的内存结构如下
尾插法参考代码如下
1 Node* createListTail() 2 { 3 Node* head = new Node; 4 if (head == nullptr) 5 return nullptr; 6 head->next = nullptr; 7 Node* t = head; 8 Node* cur; 9 int nodeData; 10 scanf_s("%d", &nodeData); 11 while (nodeData) 12 { 13 cur = new Node; 14 cur->data = nodeData; 15 t->next = cur; 16 t = cur; 17 scanf_s("%d", &nodeData); 18 } 19 t->next = nullptr; 20 return head; 21 }
头插法即每次在链表的头部插入数据。
头插法参考代码
1 Node* createListHead() 2 { 3 Node* head = new Node; 4 if (nullptr == head) 5 return nullptr; 6 head->next = nullptr; 7 Node* t = head; 8 Node* cur; 9 int nodeData; 10 scanf_s("%d", &nodeData); 11 while (nodeData) 12 { 13 cur = new Node; 14 if (nullptr == cur) 15 exit(-1); 16 cur->data = nodeData; 17 cur->next = head->next; 18 head->next = cur; 19 scanf_s("%d", &nodeData); 20 } 21 return head; 22 23 }
遍历整个链表,如果链表的值等于查找的值,直接返回,否则返回空指针。
1 Node* searchList(Node* head, int findData) 2 { 3 while (head) 4 { 5 if (head->data == findData) 6 return head; 7 head = head->next; 8 } 9 return nullptr; 10 }
要删除链表中的值,首先我们需要找到待删除链表的前驱节点,然后前驱节点的下一个指针指向待删除结点的下一个指针。但是这样会有O(n)的时间代价,因此我们寻求一个更好的方法。
可以将待删除节点下一个结点的数值复制到待删除结点中,删除待删除结点的下一个结点即可
1 void deleteListNode(Node* head, Node* del) 2 { 3 if (del->next == nullptr) 4 { 5 while (head->next!=del) 6 { 7 head = head->next; 8 } 9 head->next = del->next; 10 delete del; 11 del = nullptr; 12 } 13 else 14 { 15 Node* t = del->next; 16 del->data = del->next->data; 17 del->next = del->next->next; 18 delete t; 19 } 20 }
2. 常见题目及汇总
- 反转链表
- 反转链表2
- 回文链表
- 删除重复项:对比数组删除重复项和链表删除重复项
- 两两交换链表中的节点
- 链表的中间节点
- 复杂链表的复制
- 奇偶链表
- 归并排序
- 两数相加:考虑进位的情况
- 两个链表的第一个公共节点
- 分割链表
- 环形链表
- 倒数第k个结点:
- 涉及两个链表的操作
- 二进制链表转化为整数
- 链表中下一个更大节点
- 二叉树中的列表
- 对链表进行插入排序
- 问题分析
为了正确的反转一个链表,需要调整链表中指针的方向,我们可以借助图形直观的进行分析。下图中,h,i,j是3个相邻的节点,假设经过若干操作,我们已经bah交接点之前的指针调整完毕,这些节点的next指针都指向前面一个节点,接下来我们把i的next指针指向h,此时链表结构如图所示
不难注意到,节点i的next指针指向了其前一个节点,导致无法在链表中遍历到节点j,为了避免链表在节点i处断开,需要在调整节点i的next之前,把节点j保存下来。
三个指针,一个指向其前驱节点,一个指向当前节点,一个指向当前节点的下一个结点
- C++参考代码
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* reverseList(ListNode* head) { 12 if(head==nullptr) 13 return nullptr; 14 ListNode* pNode=head; 15 ListNode* reverseHead=nullptr; 16 ListNode* prev=nullptr; 17 while(pNode) 18 { 19 ListNode* next=pNode->next; 20 if(pNode->next==nullptr) 21 reverseHead=pNode; 22 pNode->next=prev; 23 prev=pNode; 24 pNode=next; 25 } 26 return reverseHead; 27 } 28 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode reverseList(ListNode head) { 11 if(head==null) 12 return null; 13 ListNode prev=null; 14 ListNode pNode=head; 15 ListNode preverseHead=null; 16 while(true) 17 { 18 if(pNode==null) 19 break; 20 ListNode pNext=pNode.next; 21 if(pNext==null) 22 preverseHead=pNode; 23 pNode.next=prev; 24 prev=pNode; 25 pNode=pNext; 26 } 27 return preverseHead; 28 } 29 }
- 问题分析
要实现链表之间部分逆转,我们需要分为两步:
第一步:实现m和n之间的链表逆转
第二步:逆转两个节点之间的部分
由于可能涉及到链表头的操作,因此对于此类问题,我们一般会引入一个哑指针
具体步骤如下图:
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode reverseBetween(ListNode head, int m, int n) { 11 if(head==null) 12 return null; 13 /* 14 要实现链表的部分反转,四个节点 15 a指向m的前一个节点 16 b指向节点m 17 c指向n的后一个节点 18 d指向节点n 19 20 两步实现部分反转 21 1. 首先将m和n之间的节点反转 22 2. 然后将节点m和节点n反转 23 */ 24 //首先找到结点a,b,c,d 25 //由于可能涉及到头结点的操作,因此引入一个哑指针 26 ListNode dummy=new ListNode(0); 27 dummy.next=head; 28 ListNode a=dummy; 29 for(int i=0;i<m-1;++i) 30 a=a.next; 31 ListNode b=a.next; 32 ListNode d=dummy; 33 for(int i=0;i<n;++i) 34 d=d.next; 35 ListNode c=d.next; 36 //首先反转m和n之间的节点 37 ListNode prev=a; 38 ListNode pNode=prev.next; 39 for(int i=m;i<=n;++i) 40 { 41 ListNode pNext=pNode.next; 42 pNode.next=prev; 43 prev=pNode; 44 pNode=pNext; 45 } 46 //然后实现节点m和节点n的反转 47 a.next=d; 48 b.next=c; 49 return dummy.next; 50 } 51 }
- C++代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* reverseBetween(ListNode* head, int m, int n) { 12 //实现一部分反转本质思想和实现整个链表的反转类似,其主要分为两步:首先反转需要反转的内部,然后在将反转后的链表按照我们需要的连接 13 //由于可能涉及到头结点的操作,因此我们需要利用哑节点 14 if(head==nullptr||head->next==nullptr) 15 return head; 16 ListNode* dummy=new ListNode; 17 dummy->next=head; 18 ListNode* a=dummy; 19 ListNode* d=dummy; 20 //首先令节点a指向m的前一个节点 21 for(int i=0;i<m-1;++i) 22 a=a->next; 23 //然后令节点d指向n指向的节点 24 for(int i=0;i<n;++i) 25 d=d->next; 26 ListNode* b=a->next; 27 ListNode* c=d->next; 28 //反转m到n之间的链表 29 for(auto prev=b,cur=b->next;cur!=c;) 30 { 31 ListNode* next=cur->next; 32 cur->next=prev; 33 prev=cur; 34 cur=next; 35 } 36 a->next=d; 37 b->next=c; 38 return dummy->next; 39 } 40 };
- 问题分析
-
- 首先找到l1中待删除部分的第一个节点的前一个节点和待删除部分最后一个节点的后一个节点
- 找到链表l2的头结点e和尾结点f
- 要删除链表l1中的a-b部分,然后l2接在被删除节点的位置,因此m的下一个结点指向e, f的下一个结点指向q即可
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) { 13 //由于可能涉及到头结点的操作,因此引入一个哑指针 14 ListNode dummy=new ListNode(-1); 15 dummy.next=list1; 16 //用4个结点m,n,p,q,e,f 17 /* 18 m代表第一个待删除结点的前一个节点 19 n代表第一个待删除的节点 20 p代表最后一个待删除的节点 21 q代表最后一个待删除的节点的后一个节点 22 23 e代表list2的第一个节点 24 f代表list2的最后一个节点 25 */ 26 //首先找到m,n节点 27 ListNode m=dummy; 28 for(int i=0;i<a;++i) 29 { 30 m=m.next; 31 } 32 ListNode n=m.next; 33 34 //然后找到p,q节点 35 ListNode p=dummy; 36 for(int i=0;i<=b;++i) 37 { 38 p=p.next; 39 } 40 ListNode q=p.next; 41 //然后找到e结点 42 ListNode e=list2; 43 ListNode f=list2; 44 while(f.next!=null) 45 { 46 f=f.next; 47 } 48 m.next=e; 49 f.next=q; 50 return dummy.next; 51 } 52 }
- 题目分析
这道题本质上和之前做过的回文字符串是类似的,不同之处在于,对于单向链表,其尾结点没有指向前面的前驱结点,因此直接两个指针位于链表头和链表尾是不成立的。
因此,判断回文链表需要将链表的后半部分反转,才能实现和回文字符串类似的操作
具体步骤如下
第一步:找到链表的中间节点:利用快指针和慢指针,快指针每次走两步,慢指针每次走一步,当快指针到达尾结点时,慢指针到达终点
第二步:以慢指针为反转头结点对链表的后半部分进行反转
第三步:类似于回文字符串,依次进行判断
- C++代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 bool isPalindrome(ListNode* head) { 12 //要判断链表是否是回文的,其和判断回文字符串是类似的,区别在于链表是没有前驱节点的,因此首先是需要将链表后半部分反转 13 //第一步:找到链表的中间节点,可以使用快指针和慢指针来进行实现 14 if(head==nullptr||head->next==nullptr) 15 return true; 16 ListNode* fast=head; 17 ListNode* slow=head; 18 while(fast&&fast->next) 19 { 20 slow=slow->next; 21 fast=fast->next->next; 22 } 23 //此时slow指向的节点是中间节点,反转slow以后的链表 24 ListNode* prev=slow; 25 ListNode* cur=slow->next; 26 while(cur!=nullptr) 27 { 28 ListNode* next=cur->next; 29 cur->next=prev; 30 prev=cur; 31 cur=next; 32 } 33 //反转之后,令slow->next指向空指针,利用两个指针指向链表头和链表尾 34 slow->next=nullptr; 35 while(head&&prev) 36 { 37 if(head->val!=prev->val) 38 return false; 39 head=head->next; 40 prev=prev->next; 41 } 42 return true; 43 } 44 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public boolean isPalindrome(ListNode head) { 11 /* 12 要判断一个链表是否是回文链表,可以分为以下几步 13 1. 找到链表的中点 14 要找到链表的中点,可以采用快慢指针,快指针每次走两步,慢指针每次走一步,快指针走到尾部时,慢指针到达中点 15 2. 从中点到链表尾部部分反转链表 16 3. 一个指针在尾部,一个指针在头部逐一对比 17 */ 18 if(head==null||head.next==null) 19 return true; 20 //首先快指针和慢指针,快指针每次走两步,慢指针每次走一步 21 ListNode fast=head; 22 ListNode slow=head; 23 while(true) 24 { 25 if(fast!=null&&fast.next!=null) 26 { 27 fast=fast.next.next; 28 slow=slow.next; 29 } 30 else 31 break; 32 } 33 //此时slow处于中点位置。为了便于最后判断,将slow的next指针指向空 34 35 //从中点到链表尾部反转链表 36 ListNode prev=slow; 37 ListNode pNode=prev.next; 38 while(true) 39 { 40 if(pNode==null) 41 break; 42 ListNode pNext=pNode.next; 43 pNode.next=prev; 44 prev=pNode; 45 pNode=pNext; 46 } 47 slow.next=null; 48 while(true) 49 { 50 if(head==null||prev==null) 51 break; 52 if(head.val!=prev.val) 53 return false; 54 head=head.next; 55 prev=prev.next; 56 } 57 return true; 58 } 59 }
4. 删除重复项:对比删除排序数组中的重复项和删除排序链表中的重复项
- 问题分析
我们可以使用两个指针,一个指针指向已重新组织的元素,一个指针指向未处理的元素
- 代码参考
1 class Solution { 2 public: 3 int removeDuplicates(vector<int>& nums) { 4 if(nums.empty()) 5 return 0; 6 int slow=0; 7 int fast=0; 8 int len=nums.size(); 9 for(fast=0;fast<len;++fast) 10 { 11 if(nums[slow]!=nums[fast]) 12 { 13 nums[++slow]=nums[fast]; 14 } 15 } 16 return slow+1; 17 } 18 };
- 问题分析
为了删除排序链表中的重复项,思路同删除排序数组中的重复项,也是利用双指针:快指针和慢指针,快指针遍历整个链表,慢指针指向待操作的元素,如果快指针指向的元素节点不等于慢指针,则直接将慢指针下一个结点指向快指针,然后同时移动快指针和慢指针。否则只移动快指针
- C++代码
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* deleteDuplicates(ListNode* head) { 12 if(head==nullptr||head->next==nullptr) 13 return head; 14 ListNode* slow=head; 15 ListNode* fast=head->next; 16 ListNode* phead=slow; 17 while(fast!=nullptr) 18 { 19 if(slow->val!=fast->val) 20 { 21 slow->next=fast; 22 fast=fast->next; 23 slow=slow->next; 24 } 25 else 26 fast=fast->next; 27 } 28 slow->next=nullptr; 29 return phead; 30 } 31 };
- JAVA代码
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode deleteDuplicates(ListNode head) { 11 if(head==null) 12 return null; 13 ListNode slow=head; 14 ListNode fast=head; 15 while(true) 16 { 17 if(fast==null) 18 break; 19 if(fast.val!=slow.val) 20 { 21 slow.next=fast; 22 slow=slow.next; 23 } 24 fast=fast.next; 25 } 26 slow.next=null; 27 return head; 28 } 29 }
- 问题分析
要删除链表中所有含重复数字的节点,需要考虑头结点是否含重复数字
- 代码参考
递归版本
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* deleteDuplicates(ListNode* head) { 12 if(head==nullptr||head->next==nullptr) 13 return head; 14 ListNode* next=head->next; 15 if(head->val==next->val) 16 { 17 while(next&&head->val==next->val) 18 next=next->next; 19 return deleteDuplicates(next); 20 } 21 else 22 { 23 head->next=deleteDuplicates(head->next); 24 return head; 25 } 26 return nullptr; 27 } 28 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode deleteDuplicates(ListNode head) { 11 //采用递归的方法来实现 12 if(head==null||head.next==null) 13 return head; 14 ListNode next=head.next; 15 if(head.val==next.val) 16 { 17 while(true) 18 { 19 if(next==null||head.val!=next.val) 20 break; 21 next=next.next; 22 } 23 return deleteDuplicates(next); 24 } 25 else 26 { 27 head.next=deleteDuplicates(head.next); 28 return head; 29 } 30 } 31 }
- 问题分析
在本题中,我们需要移除未排序链表中的重复节点,保留最开始出现的节点。我们可以采用哈希表的形式来实现
我们对给定的链表进行一次遍历,并用一个哈希集合(HashSet/unordered_set)来存储所有出现过的节点。由于在大部分语言中,对给定的链表元素直接进行相等比较,实际上是对两个链表元素的地址(而不是值)进行比较。因此,我们在哈希集合中存储链表元素的值,方便直接用等号进行比较
具体地,我们从链表的头结点head开始遍历,遍历的指针记为pos,由于头结点一定不会被删除,因此可以枚举待溢出节点的所有前驱节点
如果当前移动到的链表节点在哈希集合中未出现过,则直接在哈希集合中添加该元素,遍历指针pos向后移动;如果当前移动的链表节点在哈希集合中出现过,则直接删除该结点
- C++代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* removeDuplicateNodes(ListNode* head) { 12 if(head==nullptr||head->next==nullptr) 13 return head; 14 unordered_set<int> occurred={head->val}; 15 ListNode* pos=head; 16 while(pos->next!=nullptr) 17 { 18 ListNode* cur=pos->next; 19 //如果哈希集中没有当前链表的值,则直接将当前链表的值插入 20 if(!occurred.count(cur->val)) 21 { 22 occurred.insert(cur->val); 23 pos=pos->next; 24 } 25 else 26 { 27 pos->next=pos->next->next; 28 } 29 } 30 pos->next=nullptr; 31 return head; 32 } 33 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode removeDuplicateNodes(ListNode head) { 11 if(head==null||head.next==null) 12 return head; 13 //由于第一个节点肯定不会被移除,因此直接将第一个节点移入HashSet 14 //从头到尾遍历链表,将链表的前驱节点移入哈希集,如果哈希集中有该结点,则将该结点移除 15 Set<Integer> occurred=new HashSet<Integer>(); 16 occurred.add(head.val); 17 ListNode pos=head; 18 while(pos.next!=null) 19 { 20 ListNode cur=pos.next; 21 if(occurred.add(cur.val)) 22 { 23 pos=pos.next; 24 } 25 else 26 pos.next=pos.next.next; 27 } 28 pos.next=null; 29 return head; 30 } 31 }
- 问题分析
删除值为val的节点可以分为两步:定位节点,修改引用
1. 定位节点: 由于删除链表中节点需要用到待删除链表的前驱节点,因此使用双指针,由于可能涉及到头结点的操作,因此引入哑指针
遍历整个数组,直到del->val==val时跳出,即可定位目标节点
2. 修改引用,prev->next=del->next
- C++代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* deleteNode(ListNode* head, int val) { 12 if(head==nullptr) 13 return nullptr; 14 ListNode* dummy=new ListNode(-1); 15 dummy->next=head; 16 ListNode* prev=dummy; 17 ListNode* pdel=head; 18 while(pdel->next&&pdel->val!=val) 19 { 20 prev=pdel; 21 pdel=pdel->next; 22 } 23 prev->next=pdel->next; 24 return dummy->next; 25 } 26 };
- JAVA代码参考1
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode deleteNode(ListNode head, int val) { 11 if(head==null||head.next==null) 12 return head; 13 ListNode dummy=new ListNode(0); 14 dummy.next=head; 15 //找到待删除节点的前驱结点 16 ListNode prev=dummy; 17 ListNode pdel=head; 18 while(pdel.next!=null&&pdel.val!=val) 19 { 20 prev=pdel; 21 pdel=pdel.next; 22 } 23 prev.next=pdel.next; 24 return dummy.next; 25 } 26 }
- JAVA代码参考2
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode deleteNode(ListNode head, int val) { 11 if(head==null||head.next==null) 12 return head; 13 ListNode dummy=new ListNode(0); 14 dummy.next=head; 15 ListNode pHead=head; 16 while(pHead!=null) 17 { 18 /* 19 两种情况,第一种情况,当val位于链表的尾结点时,则需要一个一个的去找到结点 20 */ 21 22 if(pHead.val==val) 23 { 24 //如果val位于链表的尾结点,则需要首先遍历到val的前一个节点 25 if(pHead.next==null) 26 { 27 dummy.next=pHead.next; 28 } 29 else 30 { 31 pHead.val=pHead.next.val; 32 pHead.next=pHead.next.next; 33 } 34 } 35 pHead=pHead.next; 36 dummy=dummy.next; 37 } 38 return head; 39 } 40 }
- JAVA代码参考3
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode deleteNode(ListNode head, int val) { 11 if(head==null) 12 return null; 13 ListNode dummy=new ListNode(-1); 14 dummy.next=head; 15 ListNode prev=dummy; 16 while(prev!=null) 17 { 18 if(prev.next.val==val) 19 { 20 prev.next=prev.next.next; 21 break; 22 } 23 prev=prev.next; 24 } 25 return dummy.next; 26 } 27 }
- 问题分析
这道题和上一道题的区别是:可能移除的节点不止一个,因此必须从头遍历到尾,其余思路相同的
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* removeElements(ListNode* head, int val) { 12 if(head==nullptr) 13 return nullptr; 14 ListNode* dummy=new ListNode(-1); 15 dummy->next=head; 16 ListNode* prev=dummy; 17 ListNode* pdel=head; 18 while(pdel!=nullptr) 19 { 20 if(pdel->val==val) 21 { 22 prev->next=pdel->next; 23 pdel=prev->next; 24 } 25 else 26 { 27 prev=pdel; 28 pdel=pdel->next; 29 } 30 } 31 return dummy->next; 32 } 33 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode removeElements(ListNode head, int val) { 11 if(head==null) 12 return null; 13 ListNode dummy=new ListNode(0); 14 dummy.next=head; 15 ListNode prev=dummy; 16 ListNode pdel=head; 17 while(pdel!=null) 18 { 19 //如果找到值为val的节点 20 if(pdel.val==val) 21 { 22 prev.next=pdel.next; 23 pdel=prev.next; 24 } 25 else 26 { 27 prev=pdel; 28 pdel=pdel.next; 29 } 30 } 31 return dummy.next; 32 } 33 }
- JAVA代码参考2
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode removeElements(ListNode head, int val) { 11 if(head==null) 12 return null; 13 ListNode dummy=new ListNode(-1); 14 dummy.next=head; 15 ListNode prev=dummy; 16 while(prev!=null&&prev.next!=null) 17 { 18 if(prev.next.val==val) 19 { 20 prev.next=prev.next.next; 21 } 22 else 23 prev=prev.next; 24 } 25 return dummy.next; 26 } 27 }
- 题目分析
由于删除的是非尾结点,并且只给定了一个节点,因此要实现节点的删除,我们可以采用如下的方法
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 void deleteNode(ListNode* node) { 12 if(node==nullptr) 13 return; 14 node->val=node->next->val; 15 node->next=node->next->next; 16 } 17 };
- JAVA代码
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public void deleteNode(ListNode node) { 11 node.val=node.next.val; 12 node.next=node.next.next; 13 } 14 }
- 问题分析
- 首先生成一带表头的链表,再利用三个指针进行操作
- h作为一个前驱节点,方便进行删除操作
- p, q作为两个前后指针,记录连续和为0的节点的起始位置
- p总从h的下一个结点开始移动,q总从p的下一个结点开始移动
- 当出现sum(p, q)=0时,将p至q的节点进行删除,h.next=q.next
- 注意
- 当某次进行了删除操作之后,p直接从h的后一个节点开始遍历,而不是将h向后移动,否则无法处理1->2->3->-3->-2->-1的情况
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode removeZeroSumSublists(ListNode head) { 11 //要实现删除链表中总和值为0的连续节点,可以采用如下方法 12 /* 13 首先利用三个指针,h,p,q,当h.next指针不为0时,开始遍历,如果出现sum=0,则删除中间节点,此时h不用移 14 */ 15 if(head==null) 16 return null; 17 else if(head.next==null) 18 { 19 //如果head的值为0,则直接删除 20 if(head.val==0) 21 return null; 22 else 23 return head; 24 } 25 //首先创建头结点 26 ListNode dummy=new ListNode(-1); 27 dummy.next=head; 28 ListNode h=dummy; 29 boolean flag=true; 30 while(h.next!=null) 31 { 32 flag=true; 33 ListNode p=h.next; 34 if(p.val==0) 35 { 36 h.next=p.next; 37 continue; 38 } 39 ListNode q=p.next; 40 int sum=p.val; 41 while(q!=null) 42 { 43 sum+=q.val; 44 if(sum==0) 45 { 46 h.next=q.next; 47 flag=false; 48 break; 49 } 50 else 51 q=q.next; 52 } 53 if(flag) 54 h=h.next; 55 } 56 return dummy.next; 57 } 58 }
- 问题分析
即让节点顺序进行两两交换,具体思路如下:
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* swapPairs(ListNode* head) { 12 if(head==nullptr||head->next==nullptr) 13 return head; 14 ListNode* dummy=new ListNode; 15 dummy->next=head; 16 for(auto prev=dummy;prev->next&&prev->next->next;) 17 { 18 auto small=prev->next,big=small->next; 19 prev->next=big; 20 small->next=big->next; 21 big->next=small; 22 prev=small; 23 } 24 25 return dummy->next; 26 } 27 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 public ListNode swapPairs(ListNode head) { 13 if((head==null)||(head.next==null)) 14 return head; 15 //要涉及到头结点的操作,因此引入哑节点 16 ListNode dummy=new ListNode(0); 17 dummy.next=head; 18 for(ListNode prev=dummy;prev.next!=null&&prev.next.next!=null;) 19 { 20 ListNode pcur=prev.next; 21 ListNode pnext=prev.next.next; 22 prev.next=pnext; 23 pcur.next=pnext.next; 24 pnext.next=pcur; 25 prev=pcur; 26 } 27 return dummy.next; 28 } 29 }
- 问题分析
为了找到中间节点,我们可以用两个指针:快指针和慢指针,快指针每次走两步,慢指针每次走一步,当快指针到达尾结点时,慢指针刚好到达中间节点
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* middleNode(ListNode* head) { 12 //两个指针,一个快指针,一个慢指针,快指针每次走两步,慢指针每次走一步,当快指针走到链表尾的时候,慢指针到达终点 13 if(head==nullptr||head->next==nullptr) 14 return head; 15 ListNode* fast=head; 16 ListNode* slow=head; 17 while(fast!=nullptr&&fast->next!=nullptr) 18 { 19 slow=slow->next; 20 fast=fast->next->next; 21 } 22 return slow; 23 } 24 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode middleNode(ListNode head) { 11 if(head==null||head.next==null) 12 return head; 13 //使用快指针和,慢指针,快指针每次走两步,慢指针每次走一步,快指针为空时即找到中间节点 14 ListNode fast=head; 15 ListNode slow=head; 16 while(fast!=null&&fast.next!=null) 17 { 18 fast=fast.next.next; 19 slow=slow.next; 20 } 21 return slow; 22 } 23 }
- 问题分析
要实现复杂链表的复制,主要分为三个步骤:
step1: 将链表中的每个结点进行复制,并将复制后的节点连接到复制前的节点之后
step2: 设置链表中每个复制后节点的random指针指向。如果复制前的节点为N,复制后的节点为N',节点N的random指针指向节点S,则N'的random指针指向节点S',S'是节点S的下一个结点
step3: 将长链表分成两个短链表,一个链表是复制之前的,一个链表是复制之后的
- 代码参考
1 /* 2 struct RandomListNode { 3 int label; 4 struct RandomListNode *next, *random; 5 RandomListNode(int x) : 6 label(x), next(NULL), random(NULL) { 7 } 8 }; 9 */ 10 class Solution { 11 public: 12 //复杂链表的复制,主要分为三步 13 //第一步,复制每一个节点,并将其连接到复制节点的下一个结点 14 void clonedNode(RandomListNode* pHead) 15 { 16 if(pHead==nullptr) 17 return; 18 RandomListNode* pNode=pHead; 19 while(pNode) 20 { 21 RandomListNode* pclonedNode=new RandomListNode(0); 22 pclonedNode->label=pNode->label; 23 pclonedNode->next=pNode->next; 24 pNode->next=pclonedNode; 25 pNode=pclonedNode->next; 26 } 27 } 28 //第二步,设置每一个复制节点的random指针,原始节点N,其random指针指向S,复制节点N',其random指针指向s',则s'是s的下一个结点 29 void setRandom(RandomListNode* pHead) 30 { 31 if(pHead==nullptr) 32 return; 33 RandomListNode* pNode=pHead; 34 while(pNode) 35 { 36 RandomListNode* pclonedNode=pNode->next; 37 if(pNode->random) 38 pclonedNode->random=pNode->random->next; 39 pNode=pclonedNode->next; 40 } 41 } 42 //第三步,将长链表分解成两个短链表 43 RandomListNode* reconnect(RandomListNode* pHead) 44 { 45 if(pHead==nullptr) 46 return nullptr; 47 RandomListNode* pNode=pHead; 48 RandomListNode* pclonedHead=nullptr; 49 RandomListNode* pclonedNode=nullptr; 50 //首先设置头结点 51 if(pNode) 52 { 53 pclonedHead=pclonedNode=pNode->next; 54 pNode->next=pclonedNode->next; 55 pNode=pNode->next; 56 } 57 while(pNode) 58 { 59 pclonedNode->next=pNode->next; 60 pclonedNode=pclonedNode->next; 61 pNode->next=pclonedNode->next; 62 pNode=pNode->next; 63 } 64 return pclonedHead; 65 } 66 RandomListNode* Clone(RandomListNode* pHead) 67 { 68 clonedNode(pHead); 69 setRandom(pHead); 70 return reconnect(pHead); 71 } 72 };
- 问题分析
要实现将所有奇数节点和偶数节点排列在一起,和复制复制链表中的第三步,将长链表分解成两个短链表类似。因此我们采用的方法是先将链表分解成奇数链表和偶数链表,然后再将偶数链表连接到奇数链表的后面,具体操作步骤如下:
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode() : val(0), next(nullptr) {} 7 * ListNode(int x) : val(x), next(nullptr) {} 8 * ListNode(int x, ListNode *next) : val(x), next(next) {} 9 * }; 10 */ 11 class Solution { 12 public: 13 ListNode* oddEvenList(ListNode* head) { 14 if(head==nullptr||head->next==nullptr||head->next->next==nullptr) 15 return head; 16 //odd为奇数,even为偶数 17 ListNode* even=head->next; 18 ListNode* evenHead=even; 19 ListNode* odd=head; 20 while(even&&even->next) 21 { 22 odd->next=even->next; 23 odd=odd->next; 24 even->next=odd->next; 25 even=even->next; 26 } 27 odd->next=evenHead; 28 return head; 29 } 30 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 public ListNode oddEvenList(ListNode head) { 13 if(head==null||head.next==null) 14 return head; 15 //注意这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性 16 //因此可以类似复制复杂链表第三步:将长链表分解成两个短链表 17 ListNode odd=head; 18 ListNode even=head.next; 19 ListNode evenHead=even; 20 while(even!=null&&even.next!=null) 21 { 22 odd.next=even.next; 23 odd=odd.next; 24 even.next=odd.next; 25 even=even.next; 26 } 27 odd.next=evenHead; 28 return head; 29 } 30 }
- 题目描述
在O(nlogn)时间复杂度和常数级空间复杂度下,对数组进行排序
- 问题分析
由于需要空间复杂度在常数级,因此不能使用迭代,归并排序,即先分裂,再合并数组,具体如下
- 代码参考
1 void Merge(int* p, int* p1, int startIdx, int midIdx, int endIdx) 2 { 3 int i = startIdx, j = midIdx + 1, k = startIdx; 4 while (i <= midIdx && j <= endIdx) 5 { 6 if (p[i] < p[j]) 7 { 8 p1[k++] = p[i++]; 9 } 10 else 11 p1[k++] = p[j++]; 12 } 13 while (i <= midIdx) 14 { 15 p1[k++] = p[i++]; 16 } 17 while (j<=endIdx) 18 { 19 p1[k++] = p[j++]; 20 } 21 while (startIdx <= endIdx) 22 { 23 p[startIdx] = p1[startIdx]; 24 ++startIdx; 25 } 26 } 27 28 void MergeSort(int* p, int* p1, int startIdx, int endIdx) 29 { 30 int midIdx; 31 if (startIdx < endIdx) 32 { 33 midIdx = (startIdx + endIdx) / 2; 34 MergeSort(p, p1, startIdx, midIdx); 35 MergeSort(p, p1, midIdx + 1, endIdx); 36 Merge(p, p1, startIdx, midIdx,endIdx); 37 38 } 39 }
- 问题分析
可以用两个指针p1,p2分别指向链表的头部,利用归并排序的方法,如果p1<p2,则不存在逆序对,如果p1>p2,则p1后的所有元素都>p2,每次比较的时候,我们都把较小的数字复制到新的数组中去
- 代码参考
1 class Solution { 2 public: 3 //采用归并排序的方法 4 long long num=0; 5 void Merge(vector<int>& data,vector<int>& temp,int startIdx,int midIdx,int endIdx) 6 { 7 int i=startIdx,j=midIdx+1,k=startIdx; 8 while(i<=midIdx&&j<=endIdx) 9 { 10 if(data[i]<data[j]) 11 { 12 temp[k++]=data[i++]; 13 } 14 else 15 { 16 temp[k++]=data[j++]; 17 num+=midIdx-i+1; 18 } 19 } 20 while(i<=midIdx) 21 { 22 temp[k++]=data[i++]; 23 } 24 while(j<=endIdx) 25 { 26 temp[k++]=data[j++]; 27 } 28 while(startIdx<=endIdx) 29 { 30 data[startIdx]=temp[startIdx]; 31 ++startIdx; 32 } 33 } 34 35 void MergeSort(vector<int>& data,vector<int>& temp,int startIdx,int endIdx) 36 { 37 int midIdx; 38 if(startIdx<endIdx) 39 { 40 midIdx=(startIdx+endIdx)/2; 41 MergeSort(data, temp, startIdx, midIdx); 42 MergeSort(data, temp, midIdx+1, endIdx); 43 Merge(data,temp,startIdx,midIdx,endIdx); 44 } 45 } 46 int InversePairs(vector<int> data) { 47 if(data.empty()) 48 return 0; 49 vector<int> temp=data; 50 int startIdx=0; 51 int endIdx=data.size()-1; 52 MergeSort(data, temp, startIdx, endIdx); 53 return num%1000000007; 54 } 55 };
- 问题分析
方法一:递归操作
如果链表1为空,则排序之后的链表就是链表2;
如果链表2为空,则排序之后的链表就是链表1;
如果链表1并且链表2都不为空,则采用递归操作
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 13 //采用递归的方式 14 ListNode head=null; 15 //如果链表1为空,则直接返回链表2即可 16 if(l1==null) 17 return l2; 18 //如果链表2为空,则最终结果就是链表1 19 else if(l2==null) 20 return l1; 21 else 22 { 23 if(l1.val<l2.val) 24 { 25 head=l1; 26 head.next=mergeTwoLists(l1.next,l2); 27 } 28 else 29 { 30 head=l2; 31 head.next=mergeTwoLists(l1,l2.next); 32 } 33 } 34 return head; 35 } 36 }
- 问题分析
通过递归实现链表的归并排序,主要分为割裂和合并两个环节
-
- 割裂
找到当前链表的中点,并从中点中断裂开(以便在下次递归时,链表片段拥有正确的边界)
找到中点的方法为:双指针,一个快指针,一个慢指针,快指针每次走两步,慢指针每次走一步,快指针到达尾结点时,慢指针到达终点
找到中点后,执行slow->next=nullptr将链表切断
-
- 合并
将两个链表合并。转换为一个排序链表
双指针法合并,建立哑节点dummy
设置两个指针left和right分别指向两个链表的头部,比较两指针处大小,由小大大加入链表
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 //首先是割裂 12 ListNode* Merge(ListNode* head1,ListNode* head2) 13 { 14 ListNode* dummy=new ListNode(-1); 15 ListNode* tail=dummy; 16 while(head1&&head2) 17 { 18 if(head1->val<head2->val) 19 { 20 tail->next=head1; 21 tail=head1; 22 head1=head1->next; 23 } 24 else 25 { 26 tail->next=head2; 27 tail=head2; 28 head2=head2->next; 29 } 30 } 31 tail->next=head1!=nullptr?head1:head2; 32 return dummy->next; 33 } 34 ListNode* sortList(ListNode* head) { 35 if(head==nullptr||head->next==nullptr) 36 return head; 37 //首先需要找到中间节点 38 ListNode* fast=head; 39 ListNode* slow=head; 40 ListNode* prev=new ListNode(-1); 41 while(fast&&fast->next) 42 { 43 fast=fast->next->next; 44 prev=slow; 45 slow=slow->next; 46 } 47 if(prev->next!=nullptr) 48 prev->next=nullptr; 49 //然后开始合并,slow即为中间节点 50 ListNode* left=sortList(head); 51 ListNode* right=sortList(slow); 52 return Merge(left,right); 53 } 54 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 //归并排序链表和归并排序数组一样,分为两步:割裂和合并 13 /* 14 首先是割裂 15 ·找到当前链表的中点,从链表的中点裂开(以便在下次递归时,链表片段拥有正确的边界) 16 找到中点的方法:双指针法,一个快指针一个慢指针,快指针每次走两步,慢指针每次走一步,当快指针到达链表尾部时,慢指针到达中点,slow.next=null将中点割裂 17 然后是合并 18 将两个链表合并,转换为一个排序链表,采用双指针法进行合并 19 */ 20 //合并 21 ListNode Merge(ListNode head1,ListNode head2) 22 { 23 ListNode dummy=new ListNode(-1); 24 ListNode tail=dummy; 25 while(head1!=null&&head2!=null) 26 { 27 if(head1.val<head2.val) 28 { 29 tail.next=head1; 30 tail=head1; 31 head1=head1.next; 32 } 33 else 34 { 35 tail.next=head2; 36 tail=head2; 37 head2=head2.next; 38 } 39 } 40 tail.next=(head1!=null)?head1:head2; 41 return dummy.next; 42 } 43 44 public ListNode sortList(ListNode head) { 45 //首先将链表从中间割裂成两个链表 46 if(head==null||head.next==null) 47 return head; 48 //首先找到中间节点 49 //找到中间节点的方法:一个快指针一个慢指针,快指针每次走两步,慢指针每次走一步,快指针到尾结点时,慢指针到达终点 50 ListNode fast=head; 51 ListNode slow=head; 52 ListNode prev=new ListNode(-1); 53 while(fast!=null&&fast.next!=null) 54 { 55 prev=slow; 56 slow=slow.next; 57 fast=fast.next.next; 58 } 59 //此时slow到达终点部分 60 if(prev.next!=null) 61 prev.next=null; 62 //此时head是第一个链表的头,slow是第二个链表的头 63 //先分裂 64 ListNode left=sortList(head); 65 ListNode right=sortList(slow); 66 return Merge(left,right); 67 } 68 }
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 /* 13 要实现链表归并排序,先割裂,再合并 14 割裂 15 找到链表的中点 16 找到中点的方式:双指针法,快指针和慢指针,快指针每次走两步,慢指针每次走一步,当快指针到达尾结点时,慢指针到达中点位置 17 将链表从中点断裂开 18 从中点断裂开的方式:令中点.next=null 19 合并 20 将两个链表进行合并 21 设置两个指针left和righr分别指向两个链表的头部,依次加入 22 */ 23 //链表的合并即为两个有序链表的归并排序 24 public ListNode merge(ListNode l1,ListNode l2) 25 { 26 ListNode phead=null; 27 if(l1==null) 28 return l2; 29 else if(l2==null) 30 return l1; 31 else 32 { 33 if(l1.val<l2.val) 34 { 35 phead=l1; 36 phead.next=merge(l1.next,l2); 37 } 38 else 39 { 40 phead=l2; 41 phead.next=merge(l1,l2.next); 42 } 43 } 44 return phead; 45 } 46 public ListNode sortList(ListNode head) { 47 if(head==null||head.next==null) 48 return head; 49 //首先找到链表的中点 50 ListNode slow=head; 51 ListNode fast=head; 52 while(fast!=null&&fast.next!=null&&fast.next.next!=null) 53 { 54 slow=slow.next; 55 fast=fast.next.next; 56 } 57 //此时slow位于中点位置,如果是偶数个链表,则slow位于前半部分链表的最后一个节点 58 ListNode head2=slow.next; 59 //割裂链表 60 slow.next=null; 61 //设置两个指针left和right,依次加入 62 ListNode left=sortList(head); 63 ListNode right=sortList(head2); 64 return merge(left,right); 65 } 66 }
- 问题分析
将两个链表看成是相同长度的进行遍历,如果一个链表短则在前面补0,如987+23=987+023=1010
每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值
如果两个链表全部遍历完毕后,进位值为1,则在新链表最前方添加1
小技巧,对于链表问题,如果返回结果为头结点时,通常需要使用哑指针,哑指针的下一个极点指向真正的头结点head,使用哑指针的目的在于链表初始化时无可用节点值,而且链表的构造过程需要指针移动,进而会导致头结点丢失,无法返回结果。
图解:
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 12 ListNode* dummy=new ListNode; 13 ListNode* cur=dummy; 14 ListNode* p=l1; 15 ListNode* q=l2; 16 int carry=0; 17 while(p!=nullptr||q!=nullptr) 18 { 19 int x=((p!=nullptr)?p->val:0); 20 int y=((q!=nullptr)?q->val:0); 21 int sum=x+y+carry; 22 carry=sum/10; 23 cur->next=new ListNode(sum%10); 24 cur=cur->next; 25 if(p!=nullptr) 26 p=p->next; 27 if(q!=nullptr) 28 q=q->next; 29 } 30 if(carry==1) 31 cur->next=new ListNode(carry); 32 return dummy->next; 33 } 34 };
- 题目分析
这道题和上一道题地区别在于,上道题是逆序,这道题是正序,但是正序存在一个问题,如321+32=353,刚开始的第一位是从0开始的,补零是很复杂的,因此我们还是思考将其变成逆序来处理
-
- 方法一:先翻转链表,然后再和逆序一样处理
- 方法二:借助栈来对其进行处理,先将两个链表进入两个栈,此时就编程逆序的了,然后同逆序解决方法
- 代码参考
- 方法一:代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* reverseList(ListNode* head) 12 { 13 if(head==nullptr||head->next==nullptr) 14 return head; 15 ListNode* reverseHead=nullptr; 16 ListNode* pNode=head; 17 ListNode* prev=nullptr; 18 while(pNode) 19 { 20 ListNode* next=pNode->next; 21 if(pNode->next==nullptr) 22 reverseHead=pNode; 23 pNode->next=prev; 24 prev=pNode; 25 pNode=next; 26 } 27 return reverseHead; 28 29 } 30 ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 31 ListNode* dummy=new ListNode(0); 32 ListNode* cur=dummy; 33 ListNode* p=reverseList(l1); 34 ListNode* q=reverseList(l2); 35 int carry=0; 36 while(p!=nullptr||q!=nullptr) 37 { 38 int x=(p!=nullptr)?p->val:0; 39 int y=(q!=nullptr)?q->val:0; 40 int sum=x+y+carry; 41 carry=sum/10; 42 cur->next=new ListNode(sum%10); 43 cur=cur->next; 44 if(p!=nullptr) 45 p=p->next; 46 if(q!=nullptr) 47 q=q->next; 48 } 49 if(carry==1) 50 cur->next=new ListNode(carry); 51 return reverseList(dummy->next); 52 } 53 };
JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 //这道题和两数相加的差别在于:数的最高位位于链表开始位置,之前的最高位位于链表末尾,是逆置的 11 //因此我们可以首先将链表逆置,逆置相加后再将相加后的链表逆置 12 //将链表逆置 13 public ListNode reverseList(ListNode head) 14 { 15 if(head==null||head.next==null) 16 return head; 17 ListNode prev=null; 18 ListNode pNode=head; 19 ListNode preverseHead=null; 20 while(pNode!=null) 21 { 22 ListNode pNext=pNode.next; 23 if(pNext==null) 24 preverseHead=pNode; 25 pNode.next=prev; 26 prev=pNode; 27 pNode=pNext; 28 } 29 return preverseHead; 30 } 31 public ListNode addTwoNumbers(ListNode l1, ListNode l2) { 32 //首先将l1和l2逆置 33 ListNode p1=reverseList(l1); 34 ListNode q1=reverseList(l2); 35 ListNode p=p1; 36 ListNode q=q1; 37 ListNode dummy=new ListNode(-1); 38 ListNode cur=dummy; 39 int carry=0; 40 while(p!=null||q!=null) 41 { 42 int x=(p==null)?0:p.val; 43 int y=(q==null)?0:q.val; 44 int sum=x+y+carry; 45 carry=sum/10; 46 cur.next=new ListNode(sum%10); 47 cur=cur.next; 48 if(p!=null) 49 p=p.next; 50 if(q!=null) 51 q=q.next; 52 } 53 if(carry==1) 54 cur.next=new ListNode(carry); 55 ListNode phead=dummy.next; 56 return reverseList(phead); 57 } 58 }
-
- 方法二:借助栈C++代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 12 stack<int> s1; 13 stack<int> s2; 14 while(l1!=nullptr) 15 { 16 s1.push(l1->val); 17 l1=l1->next; 18 } 19 while(l2!=nullptr) 20 { 21 s2.push(l2->val); 22 l2=l2->next; 23 } 24 int carry=0; 25 ListNode* head=nullptr; 26 //使用头插法 27 while(!s1.empty()||!s2.empty()||carry>0) 28 { 29 int x=(s1.empty()?0:s1.top()); 30 int y=(s2.empty()?0:s2.top()); 31 int sum=x+y+carry; 32 carry=sum/10; 33 //使用尾插法 34 ListNode* cur=new ListNode(sum%10); 35 cur->next=head; 36 head=cur; 37 if(!s1.empty()) 38 s1.pop(); 39 if(!s2.empty()) 40 s2.pop(); 41 } 42 43 return head; 44 } 45 };
-
- 方法二:JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode addTwoNumbers(ListNode l1, ListNode l2) { 11 //第二种方式,输入链表不能修改,可以借助栈来实现 12 Stack<Integer> s1=new Stack<Integer>(); 13 Stack<Integer> s2=new Stack<Integer>(); 14 //首先将链表1入第一个栈 15 while(l1!=null) 16 { 17 s1.push(l1.val); 18 l1=l1.next; 19 } 20 //然后将链表2入第二个栈 21 while(l2!=null) 22 { 23 s2.push(l2.val); 24 l2=l2.next; 25 } 26 int carry=0; 27 ListNode head=null; 28 while(!s1.isEmpty()||!s2.isEmpty()) 29 { 30 int x=s1.isEmpty()?0:s1.peek(); 31 int y=s2.isEmpty()?0:s2.peek(); 32 int sum=x+y+carry; 33 carry=sum/10; 34 ListNode cur=new ListNode(sum%10); 35 cur.next=head; 36 head=cur; 37 cur=cur.next; 38 if(!s1.empty()) 39 s1.pop(); 40 if(!s2.empty()) 41 s2.pop(); 42 } 43 if(carry==1) 44 { 45 ListNode cur=new ListNode(carry); 46 cur.next=head; 47 head=cur; 48 } 49 return head; 50 } 51 }
- 问题分析
由于是单向链表的节点,每个节点只有一个next指针,因此从第一个公共节点开始,之后他们所有的节点都是重合的。
为了找到两个链表的公共节点,我们采用如下方法
首先遍历两个链表得到他们的长度,就能知道哪个链表比较长,以及长的链表比短的链表多几个节点。
然后让唱的链表先走链表长度差这么多步,接着同时在两个链表上遍历,找到的第一个相同节点就是他们的第一个公共节点
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 int lenofList(ListNode* head) 12 { 13 int len=0; 14 while(head) 15 { 16 ++len; 17 head=head->next; 18 } 19 return len; 20 } 21 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { 22 ListNode* plong=headA; 23 ListNode* pshort=headB; 24 int len1=lenofList(plong); 25 int len2=lenofList(pshort); 26 int distance=len1-len2; 27 if(distance<0) 28 { 29 plong=headB; 30 pshort=headA; 31 distance=len2-len1; 32 } 33 for(int i=0;i<distance;++i) 34 { 35 plong=plong->next; 36 } 37 while(plong&&pshort) 38 { 39 if(pshort==plong) 40 return pshort; 41 plong=plong->next; 42 pshort=pshort->next; 43 } 44 return nullptr; 45 } 46 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { 7 * val = x; 8 * next = null; 9 * } 10 * } 11 */ 12 public class Solution { 13 public int lenoflist(ListNode head) 14 { 15 int len=0; 16 while(head!=null) 17 { 18 ++len; 19 head=head.next; 20 } 21 return len; 22 } 23 public ListNode getIntersectionNode(ListNode headA, ListNode headB) { 24 /* 25 1. 若链表长度相等,直接逐一判断即可 26 2. 若链表长度不同,则先让长链表走两个链表长度的差值,然后逐一对比即可 27 */ 28 //首先分别求链表的长度 29 30 ListNode longList=headA; 31 ListNode shortList=headB; 32 int len1=lenoflist(longList); 33 int len2=lenoflist(shortList); 34 //求出两个链表的差值 35 int distance=len1-len2; 36 if(len1<len2) 37 { 38 39 longList=headB; 40 shortList=headA; 41 distance=len2-len1; 42 } 43 //让长链表先走distance步 44 for(int i=0;i<distance;++i) 45 { 46 longList=longList.next; 47 } 48 while(longList!=null&&shortList!=null) 49 { 50 //注意应该是比较链表相等,而不应该是比较值相等 51 if(longList==shortList) 52 return longList; 53 longList=longList.next; 54 shortList=shortList.next; 55 } 56 return null; 57 } 58 }
- 问题分析
使用双指针法,使用两个指针before和after来追踪上述链表,两个指针分别用于创建两个链表,然后将这两个链表连接即可获得新的链表。
1. 首先初始化两个指针before和after,在实现中,由于可能涉及到头指针,我们在before链表和after链表中使用两个哑指针
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 //使用两个链表,before,after来存储,一个链表存储大于x的,一个链表存储小于x的 12 /* 13 before链表和after链表中使用两个哑指针 14 大于x的存放在after,小于x的存放在before 15 */ 16 ListNode* partition(ListNode* head, int x) { 17 if(head==nullptr) 18 return nullptr; 19 ListNode* dummy_before=new ListNode(0); 20 ListNode* before=dummy_before; 21 ListNode* dummy_after=new ListNode(0); 22 ListNode* after=dummy_after; 23 while(head!=nullptr) 24 { 25 if(head->val<x) 26 { 27 before->next=head; 28 before=before->next; 29 } 30 else 31 { 32 after->next=head; 33 after=after->next; 34 } 35 head=head->next; 36 } 37 after->next=nullptr; 38 before->next=dummy_after->next; 39 return dummy_before->next; 40 } 41 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode partition(ListNode head, int x) { 11 //要实现小于x的节点都在大于或等于x的节点之前,我们可以采用如下方式 12 /* 13 创建两个链表,before和after,遍历整个链表,小于x的连接在before后面,大于x的连接在after后面 14 */ 15 if(head==null) 16 return null; 17 ListNode dummy_before=new ListNode(0); 18 ListNode before=dummy_before; 19 ListNode dummy_after=new ListNode(0); 20 ListNode after=dummy_after; 21 //遍历原始链表,比x大的节点连接到after后,比x小的节点连接到before后 22 while(head!=null) 23 { 24 if(head.val<x) 25 { 26 before.next=new ListNode(head.val); 27 before=before.next; 28 } 29 else 30 { 31 after.next=new ListNode(head.val); 32 after=after.next; 33 } 34 head=head.next; 35 } 36 before.next=dummy_after.next; 37 return dummy_before.next; 38 } 39 }
- 题目分析
由于只需要将链表分隔成k段,每部分的长度尽可能相等,任意两部分的长度差距不超过1,算法如下
1. 首先求得链表的长度
2. 链表数组中每个元素中,链表最少有len/k个元素,如果len%k有余数,则有len%k个元素中链表长度要多1
3. 对于0-len%k-1索引上的元素,其链表长度为len/k+1
对于len%k-k-1索引上的元素,其链表长度为len/k
- C++代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 //求得链表的长度 12 int lenoflist(ListNode* head) 13 { 14 int len=0; 15 while(head) 16 { 17 ++len; 18 head=head->next; 19 } 20 return len; 21 } 22 vector<ListNode*> splitListToParts(ListNode* root, int k) { 23 vector<ListNode*> arr; 24 //首先求得链表的长度 25 int len=lenoflist(root); 26 //然后求得每个链表元素中链表最少长度 27 int minSize=len/k; 28 //然后求有几个链表元素长度要多1 29 int moreEleNumber=len%k; 30 //由于长度更多的元素应该在前面,因此首先将多一个的元素入数组 31 ListNode* head=root; 32 //首先是前面更多元素的数据入数组 33 for(int i=0;i<moreEleNumber;++i) 34 { 35 ListNode* dummy=new ListNode(-1); 36 ListNode* cur=dummy; 37 for(int j=0;j<minSize+1;++j) 38 { 39 cur->next=new ListNode(head->val); 40 cur=cur->next; 41 if(head!=nullptr) 42 head=head->next; 43 } 44 arr.push_back(dummy->next); 45 } 46 //然后是后面少一个元素的数据入数组 47 for(int i=moreEleNumber;i<k;++i) 48 { 49 ListNode* dummy=new ListNode(-1); 50 ListNode* cur=dummy; 51 for(int j=0;j<minSize;++j) 52 { 53 cur->next=new ListNode(head->val); 54 cur=cur->next; 55 if(head!=nullptr) 56 head=head->next; 57 } 58 arr.push_back(dummy->next); 59 } 60 return arr; 61 } 62 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 //求出链表的长度 11 public int lenoflist(ListNode head) 12 { 13 int len=0; 14 while(head!=null) 15 { 16 ++len; 17 head=head.next; 18 } 19 return len; 20 } 21 public ListNode[] splitListToParts(ListNode root, int k) { 22 //首先求出链表的长度 23 int len=lenoflist(root); 24 //求出每个链表的最少长度 25 int minSize=len/k; 26 //由于多余的数组就放在前面,因此用moreSizeNumber来表示有几个数组是要多一个元素的 27 int moreSizeNumber=len%k; 28 ListNode head=root; 29 ListNode [] array=new ListNode[k]; 30 31 for(int i=0;i<moreSizeNumber;++i) 32 { 33 ListNode dummy=new ListNode(-1); 34 ListNode phead=dummy; 35 for(int j=0;j<minSize+1;++j) 36 { 37 phead.next=new ListNode(head.val); 38 phead=phead.next; 39 if(head!=null) 40 head=head.next; 41 } 42 array[i]=dummy.next; 43 } 44 for(int i=moreSizeNumber;i<k;++i) 45 { 46 ListNode dummy=new ListNode(-1); 47 ListNode phead=dummy; 48 for(int j=0;j<minSize;++j) 49 { 50 phead.next=new ListNode(head.val); 51 phead=phead.next; 52 if(head!=null) 53 head=head.next; 54 } 55 array[i]=dummy.next; 56 } 57 return array; 58 } 59 }
- 问题分析
要判断链表是否有环,我们可以采用双指针法:快指针和慢指针,快指针每次走两步,慢指针每次走一步,如果两指针相遇,则链表是有环的,否则是没有环的。
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 bool hasCycle(ListNode *head) { 12 //int pos=0; 13 if(head==NULL||head->next==NULL||head->next->next==NULL) 14 return false; 15 ListNode *fast=head->next->next; 16 ListNode *slow=head->next; 17 while(slow!=fast) 18 { 19 if(fast->next!=NULL&&fast->next->next!=NULL) 20 { 21 fast=fast->next->next; 22 slow=slow->next; 23 } 24 else 25 return false; 26 } 27 return true; 28 29 } 30 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { 7 * val = x; 8 * next = null; 9 * } 10 * } 11 */ 12 public class Solution { 13 public boolean hasCycle(ListNode head) { 14 //要判断链表是否有环,可以使用快指针和慢指针,快指针每次走两步,慢指针每次走一步,如果链表是没有环的,则快指针会首先走空指针处,如果链表是没有环的,则快指针和慢指针必然会相遇,相遇点在环的入口节点处。 15 if(head==null||head.next==null||head.next.next==null) 16 return false; 17 ListNode fast=head.next.next; 18 ListNode slow=head.next; 19 while(fast!=null&&fast.next!=null&&fast.next.next!=null) 20 { 21 if(slow==fast) 22 return true; 23 slow=slow.next; 24 fast=fast.next.next; 25 } 26 return false; 27 } 28 }
- 问题分析
要找到链表中环的入口节点,只需要三步
第一步:判断链表是否有环,判断方法:两个指针:快指针和慢指针,快指针每次走两步,慢指针每次走一步,如果快慢指针相遇,则链表有环
第二步:求出链表中环的长度
第三步:两个指针指向头结点,一个指针先走环的长度这么多步,然后两个指针同时移动,相遇点即为环的入节点
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode *detectCycle(ListNode *head) { 12 //判断环的入口节点,主要分为三步 13 //首先判断链表是否有环 14 if(head==nullptr||head->next==nullptr||head->next==nullptr) 15 return nullptr; 16 //判断是否有环的方法:快慢指针,快指针每次走两步,慢指针每次走一步,如果相遇,则其是有环的 17 ListNode* slow=head->next; 18 ListNode* fast=head->next->next; 19 while(fast!=slow) 20 { 21 if(fast&&fast->next&&fast->next->next) 22 { 23 slow=slow->next; 24 fast=fast->next->next; 25 } 26 else 27 return nullptr; 28 } 29 //第二步:计算环的长度 30 int len=1; 31 slow=slow->next; 32 while(slow!=fast) 33 { 34 ++len; 35 slow=slow->next; 36 } 37 //第三步:令一个指针先走环的长度这么多步,然后同时移动快慢指针,相遇点即为入口节点 38 slow=head; 39 fast=head; 40 for(int i=0;i<len;++i) 41 { 42 slow=slow->next; 43 } 44 while(slow!=fast) 45 { 46 slow=slow->next; 47 fast=fast->next; 48 } 49 return slow; 50 } 51 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { 7 * val = x; 8 * next = null; 9 * } 10 * } 11 */ 12 public class Solution { 13 public ListNode detectCycle(ListNode head) { 14 if(head==null||head.next==null||head.next.next==null) 15 return null; 16 //分为三步: 17 //首先判断链表是否有环,判断方法:快指针和慢指针,快指针每次走两步,慢指针每次走一步,若有环,则快指针和慢指针必将相遇 18 ListNode slow=head.next; 19 ListNode fast=head.next.next; 20 while(fast!=slow) 21 { 22 if(fast!=null&&fast.next!=null&&fast.next.next!=null) 23 { 24 fast=fast.next.next; 25 slow=slow.next; 26 } 27 else 28 return null; 29 } 30 //如果走到此步,则证明链表必定是有环的,因此我们需要求出环的长度。用len来确定环的长度 31 int len=1; 32 slow=slow.next; 33 while(slow!=fast) 34 { 35 ++len; 36 slow=slow.next; 37 } 38 //第三步,求出环的长度之后,两个指针都指向头结点,一个指针先走环的长度步,然后两个指针同时往前走,相遇点即为入口节点 39 slow=head; 40 fast=head; 41 for(int i=0;i<len;++i) 42 { 43 slow=slow.next; 44 } 45 while(slow!=fast) 46 { 47 slow=slow.next; 48 fast=fast.next; 49 } 50 return fast; 51 52 } 53 }
- 问题分析
要找到倒数第k个结点,我们可以采用双指针法,两个指针刚开始都指向链表头结点,一个指针p先走K步,此时指针p,q之间的间距为k,当指针p到达链表尾时,指针q刚好达到倒数第k个结点
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* getKthFromEnd(ListNode* head, int k) { 12 if(head==nullptr||k<0) 13 return nullptr; 14 ListNode* p=head; 15 ListNode* q=head; 16 int i=0; 17 while(p!=nullptr) 18 { 19 if(i<k) 20 { 21 ++i; 22 p=p->next; 23 } 24 else 25 { 26 p=p->next; 27 q=q->next; 28 } 29 } 30 return i<k?nullptr:q; 31 } 32 };
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode getKthFromEnd(ListNode head, int k) { 11 //要找到倒数第k个结点,可以采用双指针方法 12 //两个指针,刚开始都指向头结点,一个指针先走k步,因此两个指针之间相隔k步 13 //当快指针到达链表末尾的时候,慢节点刚好到达链表倒数第k个结点 14 if(head==null||k<0) 15 return null; 16 ListNode slow=head; 17 ListNode fast=head; 18 for(int i=0;i<k;++i) 19 fast=fast.next; 20 while(fast!=null) 21 { 22 slow=slow.next; 23 fast=fast.next; 24 } 25 return slow; 26 } 27 }
- 问题分析
这道题类似倒数第k个结点,但是设计删除就要找到倒数第k个结点的前驱节点,由于可能涉及到头结点的操作,引入一个哑节点。
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* removeNthFromEnd(ListNode* head, int n) { 12 if(head==nullptr||n<0) 13 return nullptr; 14 ListNode* dummy=new ListNode(-1); 15 dummy->next=head; 16 ListNode* p=dummy; 17 ListNode* q=dummy; 18 int i=0; 19 while(p!=nullptr) 20 { 21 if(i<n+1) 22 { 23 p=p->next; 24 ++i; 25 } 26 else 27 { 28 p=p->next; 29 q=q->next; 30 } 31 } 32 q->next=q->next->next; 33 return dummy->next; 34 } 35 };
- 第二种问题分析方式
由于第一道题我们已经知道怎么找到链表的倒数第k个链表,这道题只是需要将其前一个链表删除而已,因此需要找到需要删除的链表的前一个节点,因此引入哑节点:
使用双指针方法:刚开始两个指针slow, fast都指向头结点,一个链表fast先向前走k步,此时链表fast和链表slow距离为k步,因此当链表fast到达尾结点时,链表slow到达倒数第k个结点,引入哑节点指向head的前驱节点,同时prev指向哑节点,当fast到达尾结点时,prev到达slow的前驱节点,此时实现删除即可
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 public ListNode removeNthFromEnd(ListNode head, int n) { 13 //这道题是之前链表的升级,需要将倒数第k个结点删除,因此需要找到第k个结点的前驱结点 14 if(head==null||n<0) 15 return null; 16 ListNode dummy=new ListNode(-1); 17 dummy.next=head; 18 ListNode prev=dummy; 19 ListNode fast=head; 20 ListNode slow=head; 21 for(int i=0;i<n;++i) 22 fast=fast.next; 23 while(fast!=null) 24 { 25 fast=fast.next; 26 slow=slow.next; 27 prev=prev.next; 28 } 29 prev.next=prev.next.next; 30 return dummy.next; 31 } 32 }
- 问题分析
注意到目标链表及为将原链表的左半端和反转后的右半端合并后的结果,因此我们的任务可以分为三步
- 找到原始链表的中点
实现方式:快慢指针,快指针每次走两步,慢指针每次走一步,快指针到达尾结点时,慢指针到达中点
- 将原链表的右半端翻转
- 将原链表的两端进行合并
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 class Solution { 12 //首先实现链表的翻转 13 public ListNode reverseList(ListNode head) 14 { 15 ListNode prev=null; 16 ListNode pNode=head; 17 ListNode preverseHead=null; 18 while(pNode!=null) 19 { 20 ListNode pNext=pNode.next; 21 pNode.next=prev; 22 prev=pNode; 23 pNode=pNext; 24 } 25 return prev; 26 } 27 //找到原始链表的中点 28 public ListNode midNode(ListNode head) 29 { 30 //一个快指针一个慢指针,快指针每次走两步,慢指针每次走一步,快指针到达终点时,慢指针达到中点 31 ListNode fast=head; 32 ListNode slow=head; 33 while(fast.next!=null&&fast.next.next!=null) 34 { 35 fast=fast.next.next; 36 slow=slow.next; 37 } 38 return slow; 39 } 40 public void mergeList(ListNode l1,ListNode l2) 41 { 42 ListNode l1_temp; 43 ListNode l2_temp; 44 while(l1!=null&&l2!=null) 45 { 46 l1_temp=l1.next; 47 l2_temp=l2.next; 48 49 l1.next=l2; 50 l1=l1_temp; 51 52 l2.next=l1; 53 l2=l2_temp; 54 } 55 } 56 public void reorderList(ListNode head) { 57 if(head==null) 58 return; 59 //首先找到原始链表的中点 60 ListNode mid=midNode(head); 61 ListNode l1=head; 62 ListNode l2=mid.next; 63 mid.next=null; 64 l2=reverseList(l2); 65 mergeList(l1,l2); 66 } 67 }
- 问题分析
由于链表冲击感高位到低位存放了数字的二进制表示,因此我们可以使用二进制转十进制的方法,在遍历一遍链表的同时得到数字的十进制值
- 代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public int getDecimalValue(ListNode head) { 11 if(head==null) 12 return 0; 13 int sum=0; 14 ListNode phead=head; 15 while(phead!=null) 16 { 17 sum=sum*2+phead.val; 18 phead=phead.next; 19 } 20 return sum; 21 } 22 }
- 问题分析
这道题可以采用单调栈来实现,从头到尾遍历整个链表
-
- 若栈为空,则将元素直接入栈
- 若栈不为空且待插入元素>栈顶元素,则将栈顶元素出栈,直到栈顶元素>待插入元素,且出栈元素下标值所对应的位置为待插入元素的大小
- 若栈不为空且待插入元素<栈顶元素,则将待插入元素入栈
由于其可能涉及到下标值的计算,因此直接将下标值入栈即可
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public int lenoflist(ListNode head) 11 { 12 int len=0; 13 while(head!=null) 14 { 15 ++len; 16 head=head.next; 17 } 18 return len; 19 } 20 public int[] nextLargerNodes(ListNode head) { 21 //使用最小栈来实现 22 int len=lenoflist(head); 23 int[] nextMaxNumber=new int[len]; 24 //首先从头到尾遍历整个链表 25 /* 26 若当前栈为空,则直接将元素入栈 27 若待插入元素>栈顶元素,则将栈顶元素出栈,知道待插入元素<栈顶元素,将待插入元素入栈 28 若待插入元素<栈顶元素,则将待插入元素入栈 29 30 由于涉及到后续赋值问题,因此入栈的是当前元素的下标值 31 */ 32 //由于要将当前元素的下标值入栈,因此将链表转换为数组 33 int [] index=new int[len]; 34 int i=0; 35 //首先将当前链表转换为数组 36 while(head!=null) 37 { 38 index[i]=head.val; 39 head=head.next; 40 ++i; 41 } 42 //创建一个栈 43 Stack<Integer> s=new Stack<>(); 44 for(i=0;i<len;++i) 45 { 46 //当栈不为空且待插入元素大于栈顶元素,则将栈顶元素弹出 47 while(!s.empty()&&index[i]>index[s.peek()]) 48 { 49 nextMaxNumber[s.peek()]=index[i]; 50 s.pop(); 51 } 52 s.push(i); 53 } 54 return nextMaxNumber; 55 } 56 }
- 问题分析
枚举二叉树中的每个节点为起点往下的路径是否有与链表相匹配的路径。为了判断是否匹配,我们设计一个递归函数dfs(rt, head),其中rt表示当前匹配到的二叉树节点,head表示当前匹配到的链表节点,整个函数返回 布尔值表示是否有一条该结点往下的路径与head节点开始的链表匹配,如匹配返回true,否则返回false,一共有四种情况
- 链表已经全部匹配完(链表为空),返回true
- 二叉树访问到了空节点,返回false
- 当前二叉树节点值和链表结点值不相同,返回false
- 如果以上三种情况不满足,说明部分匹配,需要继续调用递归匹配。所以先调用函数dfs(rt.left, head.next),如果返回的结果是fasle,说明没有找到相匹配的路径,继续在右子树中寻找匹配,调用函数dfs(rt.right, head.next)
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode() {} 7 * ListNode(int val) { this.val = val; } 8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; } 9 * } 10 */ 11 /** 12 * Definition for a binary tree node. 13 * public class TreeNode { 14 * int val; 15 * TreeNode left; 16 * TreeNode right; 17 * TreeNode() {} 18 * TreeNode(int val) { this.val = val; } 19 * TreeNode(int val, TreeNode left, TreeNode right) { 20 * this.val = val; 21 * this.left = left; 22 * this.right = right; 23 * } 24 * } 25 */ 26 class Solution { 27 /* 28 为了判断是否匹配设计一个递归函数dfs(rt,head),rt表示当前匹配到的二叉树节点,head表示当前匹配到的链表节点,整个函数返回布尔值表示是否有一条该结点往下的路径与head节点开始的链表匹配,如果匹配返回true,否则返回fasle 29 有如下四种情况 30 1. 链表已经全部匹配完,返回true 31 2. 二叉树访问到了空节点,匹配失败,返回false 32 3. 当前匹配的二叉树上节点的值与链表节点的值不相等,返回false 33 4. 前三种情况都不满足,证明匹配成功一部分,继续递归匹配,dfs(rt.left,head.next),如果返回结果为fasle,说明没有匹配成功,继续再右子树中匹配dfs(rt.right,head.next) 34 */ 35 public boolean dfs(TreeNode rt,ListNode head) 36 { 37 //1. 链表已经全部匹配完,此时链表为空,返回true 38 if(head==null) 39 return true; 40 //2. 二叉树访问到了空节点,匹配失败,返回fasle 41 if(rt==null) 42 return false; 43 //3. 当前二叉树的值和链表的值不相等 44 if(rt.val!=head.val) 45 return false; 46 return dfs(rt.left,head.next)||dfs(rt.right,head.next); 47 } 48 public boolean isSubPath(ListNode head, TreeNode root) { 49 if(root==null) 50 return false; 51 return dfs(root,head)||isSubPath(head,root.left)||isSubPath(head,root.right); 52 } 53 }
- 问题分析
从前往后找插入点
插入排序的基本思想是:维护一个有序序列,初始时有序序列只有一个元素,每次将一个新的元素插入到有序序列中,将有序序列的长度加1,直到全部元素都加入到有序序列中。
如果是数组的插入排序,则数组的前面部分是有序序列,每次找到有序序列后面的第一个元素(待插入元素)的插入位置,将有序序列中的插入位置后面的元素都往后移动一位,然后将待插入元素插入插入位置
对于链表而言,插入元素时只要更新相邻节点的指针即可,不需要像数组一样将插入位置后面的元素往后移动
对于单链表进行插入排序的具体过程如下:
- 由于可能涉及到链表头结点的下一个结点,因此首先判断链表是否为空或者其是否为单结点链表
- 由于可能涉及到将后面的元素插入到头结点之前,因此需要创建一个哑指针dummy
- 维护一个链表已排序部分的最后一个节点LastSortedNode,初始时LastSortedNode指向头结点
- 维护cur为待插入的元素,初始时cur=head.next
- 比较LastSortedNode.val和cur.val
- 如果LastSortedNode.val<cur.val,则直接将LastSortedNode后移一个位置,cur变成新的LastSortedNode
- 否则,从链表的头结点开始往后遍历链表中的节点,寻找插入cur的位置,令prev为插入cur的前一个节点,进行如下操作,完成对cur的插入
LastSortedNode.next = cur.next cur.next = prev.next prev.next = cur
6. 令cur=LastSortedNode,此时cur为下一个待插入的元素
7. 重复5-6,直到cur为空,排序结束
- JAVA代码参考
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 class Solution { 10 public ListNode insertionSortList(ListNode head) { 11 //由于可能涉及到head.next的操作,因此首先进行空指针判断 12 if(head==null||head.next==null) 13 return head; 14 //由于可能涉及到头指针的操作,因此创建一个哑指针 15 ListNode dummy=new ListNode(-1); 16 dummy.next=head; 17 //创建一个链表指针,其指向排序后链表的最后一个节点,初始时,其是在head处 18 ListNode LastSortedNode=head; 19 //创建一个链表指针指向最后一个排序节点的下一个结点 20 ListNode cur=head.next; 21 while(cur!=null) 22 { 23 ListNode prev=dummy; 24 //如果当前cur.val>lastsortedNode.val,则lastsortedNode往后移动 25 if(LastSortedNode.val<=cur.val) 26 { 27 LastSortedNode=LastSortedNode.next; 28 } 29 else 30 { 31 //否则,需要进行插入排序 32 while(prev.next.val<=cur.val) 33 prev=prev.next; 34 LastSortedNode.next=cur.next; 35 cur.next=prev.next; 36 prev.next=cur; 37 } 38 cur=LastSortedNode.next; 39 } 40 return dummy.next; 41 } 42 }