总结:
主要是迭代(遍历)和递归。迭代遍历的话一般可能用的多的是双指针,快慢指针,三指针(pre,cur,next)这些,最好画图理清链表节点next域的指向要如何修改,相交链表、环这种问题适合尝试双指针。因为链表的遍历查询只能从头节点一次向后遍历,所以检索的效率不高,这时时间复杂度为O(1)的哈希表可以发挥作用。递归的话则是将问题拆成小问题了,主要时考虑清楚每一个节点是如何处理的,最好也是画图分析比较清楚一点。
剑指 Offer 06. 从尾到头打印链表
链表的特点是只能从头节点开始向尾节点方向遍历,要实现这种逆序打印链表,得依靠类似栈这种先进后出的数据结构,或者是递归调用(递归也是栈的一种形式),或者是得先确定链表的长度然后以此创建数组,便可实现逆序的效果。
//go实现
func reversePrint(head *ListNode) []int {
backup:=head
//统计链表节点个数
n:=0
for head!=nil{
n++
head=head.Next
}
//创建对应大小的数组,重新遍历链表并从右到左放入数组中
nums:=make([]int,n)
for i:=n-1;i>=0;i--{
nums[i]=backup.Val
backup=backup.Next
}
return nums
}
//java实现
class Solution {
public int[] reversePrint(ListNode head) {
//使用栈
Stack<Integer> stack=new Stack<>();
while(head!=null){
stack.push(head.val);
head=head.next;
}
int n=stack.size();
int[] res=new int[n];
for(int i=0;i<n;i++){
res[i]=stack.pop();
}
return res;
}
}
剑指 Offer 24. 反转链表
这种问题一般就递归或者迭代,迭代的话主要是需要另外声明两个指针来记录前后两个链表节点,从头节点开始不断修改链表节点的指向。最好画图,可以看清如何操作链表的指针。
//go实现
func reverseList(head *ListNode) *ListNode {
//递归实现
if head==nil || head.Next==nil{
return head
}
nex:=head.Next
newHead:=reverseList(nex)
nex.Next=head
head.Next=nil
return newHead
}
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null || head.next==null) return head;
ListNode nex=head.next;
ListNode newHead=reverseList(nex);
nex.next=head;
head.next=null;
return newHead;
}
}
剑指 Offer 25. 合并两个排序的链表
双指针迭代或者递归
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
if l1==nil{
return l2
}
if l2==nil{
return l1
}
if l1.Val<l2.Val{
l1.Next=mergeTwoLists(l1.Next,l2)
return l1
}else{
l2.Next=mergeTwoLists(l1,l2.Next)
return l2
}
}
160. 相交链表
方法一是使用哈希表,遍历一条链表的节点并将其加入哈希表中,然后遍历另一条链表查看是否有节点已经存在于哈希表中,如果有则是第一个相交的节点
方法二是双指针,想法比较好,见代码
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null) return null;
ListNode pa=headA,pb=headB;
while(pa!=pb){
pa= pa==null? headB:pa.next;
pb= pb==null? headA:pb.next;
}
return pa;
}
}
func FindFirstCommonNode( pHead1 *ListNode , pHead2 *ListNode ) *ListNode {
// write code here
p1:=pHead1
p2:=pHead2
for p1!=p2{
if p1!=nil{
p1=p1.Next
}else {
p1=pHead2
}
if p2!=nil{
p2=p2.Next
}else{
p2=pHead1
}
}
return p1
}
剑指 Offer II 022. 链表中环的入口节点
方法一,哈希表
方法二,快慢指针,这个方法需要脑筋稍微转一下,理解一下为什么这样就是入口节点,可以参考题解剑指 Offer II 022. 链表中环的入口节点(双指针) - 链表中环的入口节点 - 力扣(LeetCode) (leetcode-cn.com)
func detectCycle(head *ListNode) *ListNode {
//快慢指针解法
slow:=head
fast:=head
for fast!=nil && fast.Next!=nil{
fast=fast.Next.Next
slow=slow.Next
//快慢指针第一次相遇
if fast==slow{
slow=head
for slow!=fast{
slow=slow.Next
fast=fast.Next
}
//当fast==slow时,此时正是入口处
return fast
}
}
return nil
}
剑指 Offer 22. 链表中倒数第k个节点
快慢指针
func getKthFromEnd(head *ListNode, k int) *ListNode {
fast:=head
slow:=head
for fast!=nil && k>0{
fast=fast.Next
k--
}
if k>0{
return nil
}
for fast!=nil{
fast=fast.Next
slow=slow.Next
}
return slow
}
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
//快慢指针
ListNode fast=head,slow=head;
while(k>0){
if(fast==null) return null;
fast=fast.next;
k--;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
剑指 Offer 35. 复杂链表的复制
如果是复制普通链表的话,只需要遍历一次链表,以尾插法不断建立新节点即可。本题的难点在于在构建新链表的时候还需要构建各节点random域的引用指向,而构建的难点在于如何找到对应的新节点,而且可能这时对应的新节点还未构建。
难点在于如何找到random域对应的新节点,可以使用哈希表,记录原链表节点和新链表对应节点的键值对映射关系。第一次遍历创建新链表的时候random先指向原链表节点,第二次遍历新链表,从哈希表中查询,将random域的指向改为对应新节点的指向。
func copyRandomList(head *Node) *Node {
originToCopyMap:=make(map[*Node]*Node)//原始节点到新节点的映射
copyHead:=&Node{0,nil,nil}
copyTail:=copyHead
//第一次遍历原链表,复制新链表,并初始化Val和Next域的值,记录原始节点到新节点的映射关系至map中
cur:=head
for cur!=nil{
node:=&Node{cur.Val,nil,nil}
node.Random=cur.Random//先记录原始节点的
copyTail.Next=node
copyTail=copyTail.Next
originToCopyMap[cur]=node
cur=cur.Next
}
//第二次遍历新链表,从map中找到对应的Random域节点在新链表中对应的节点,进行替换
cur=copyHead.Next
for cur!=nil{
if cur.Random!=nil{
cur.Random=originToCopyMap[cur.Random]
}
cur=cur.Next
}
return copyHead.Next
}
83. 删除排序链表中的重复元素
因为链表已经排好序了,一次遍历迭代即可,每次比较cur与cur.next
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next==null) return head;
ListNode pre=head;
ListNode cur=pre.next;
while(cur!=null){
if(cur.val==pre.val){
pre.next=cur.next;
cur=pre.next;
}else{
pre=cur;
cur=pre.next;
}
}
return head;
}
}