Leetcode复盘2——链表

Leetcode复盘2——链表

导读

这是我写的第二篇复盘总结,总结了10道链表相关的题目,里面包含了所有链表的基本操作,包括:

  • 如何定义一个指针(C++ ListNode *pA = head, Java ListNode pA = head)
  • 如何反转链表;(设置一个临时指针temp,先记录cur的下一个,反转,pre和cur各往前走一步)
  • 如何区分指针移动和指向:当为指向时“.next”在等号左边,即用当前指针指向等号右边的节点;当为移动时”.next”在等号右边,即把某一个指针指向的节点赋值给等号左边
1.相交链表 / 找出两个链表的交点(Leetcode160)

难度:简单Easy

idea: 双指针法(two pointers)
定义两个指针pA和pB,用pA指向链表A的开头,pB指向链表B的开头,链表A的长度为a+c,链表B的长度为b+c,其中c为重复的部分
e.g

链表A: 1->2->3->4->5->6->null
链表B:          9->5->6->null, 交点为5
还有可能公共的部分只有一个null

e.g

链表A: 1->2->3->4->5->6->null
链表B:          7->8->9->null,

把A看成主链表,B看成辅链表.当pB指针走完链表B回到链表A的开头时,相当于pA指针比pB指针多走了链表B的长度;同理,当pA在链表A走完后又在链表B走完时,相当于pA又让了pB共链表B的长度.最终两个指针一定是一起走到终点.此时 a + c + b = b + c + a
如果两个链表最后的部分是重合的话,则两个指针会在第一个重合的节点相遇,不会等到最后,因为此时已不满足while(pA!=pB)

若两个链表没有交点,则两个指针都会走到各自链表的结尾null,此时满足(pA = pB = null),此时 a + b = b + a
代码:
C++版

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *pA = headA, *pB = headB;
        // 若有交点,则走到第一个交点的时候跳出while循环; 若没有交点,则走到结尾null时跳出while循环
        while (pA != pB) {
            pA = pA ? pA->next : headB;                   // 若pA不存在,即pA走到头了,则从pB继续开始
            pB = pB ? pB->next : headA;                   // 若pB存在,表示还没走到尾部,pB继续向前走
        }
        return pA;
    }
};

Java版

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;
    }
}
2.反转链表(Leetcode206)

难度:简单Easy

idea: 双指针迭代法(iterative method)
定义两个指针pre和cur,
e.g

 1 -> 2 ->  3 -> 4 -> 5 -> null
pre  cur  temp

分三步:

  1. 用temp记录cur的下一个节点;
  2. 用cur指向pre(关键);
  3. pre和cur各往前一步,顺序不能反了,先把cur给pre,再把temp给cur
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = NULL, *cur = head;                      // 定义两个指针,pre和cur
        while(cur) {
            ListNode* temp = cur -> next;                       // 1.记录cur的下一个节点
            cur -> next = pre;                                  // 2.用cur指向pre(关键)

            // 3.pre和cur各往前一步(顺序不能乱,先把cur给pre,再把temp给cur)
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};
3.合并两个有序链表 / 归并两个有序的链表(LeetCode21)

难度:简单Easy

idea: 双指针法
定义两个指针:dummy(用于返回,即返回dummy.next)和cur(用于移动)
分为两种情况:

  1. 当l1和l2都存在时:
    1. 若l1较小
      cur指向l1: cur.next = l1;
      l1往前一步: l1 = l1.next;
      cur往前一步: cur = cur.next;
      (下一次用新的l1和l2比较)
    2. 若l2较小
      cur指向l2: cur.next = l2;
      l2往前一步: l2 = l2.next;
      cur往前一步: cur = cur.next;
  2. 当l1或l2有一个不存在时:
    用C++或Java里面的三元运算符判断哪个链表为null,用cur指向不为null的,
    或者用Python里的"…=… if… else…"。

代码:
C++版

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode dummy(INT_MIN);                            // 定义dummy为负无穷类型的数?
        ListNode *cur = &dummy;                             // cur取dummy的地址即可

        while(l1 && l2) {
            if(l1 -> val < l2 -> val) {
                cur -> next = l1;
                l1 = l1 -> next;                            // 下次用新的l1指向的节点值和l2比
            }
            else {
                cur -> next = l2;                           // 注意区分"指向"和"移动"
                l2 = l2 -> next;
            }
            cur = cur -> next;
        }
        // 当不满足while循环时,即有一个链表为空了
        cur -> next = l1? l1 : l2;                          // l1? 即 "l1 != null"

        return dummy.next;                                  // dummy是一个数
    }
};

Python版

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        # 在两个头节点之外再定义一个0节点
        dummy = cur = ListNode(0)
        while l1 and l2:
            if l1.val < l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next
        
        # 跳出while循环了,证明l1或l2有一个为空了
        cur.next = l1 if l1 else l2                                 # 或cur.next = l1 or l2

        return dummy.next
4.删除排序链表中的重复元素 / 从有序链表中删除重复节点(LeetCode83)

难度:简单Easy

本题考察点:指针的指向和移动

定义一个指针cur用于移动,while循环保证cur指向的元素是存在的,即cur.next存在(不然拿cur当前的值跟谁比呢),分两种情况:

  1. 当cur当前指向的值和下一个相等时(cur == cur.next),cur跳过下一个,再往前指一个(cur.next = cur.next.next)
  2. 当cur当前指向的值和下一个不相等时,cur往前移一步(cur = cur.next)

代码:
C++版

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head == NULL || head -> next == NULL) return head;
        ListNode *cur = head;

        while(cur -> next != NULL) {                      // 此处一定是看cur指向的是否为空,因为cur可能一直不动
            if(cur -> val == cur -> next -> val) {        // 若cur当前的值和指向的值一样,cur就往前指,再循环
                cur -> next = cur -> next -> next;
            }
            else {                                        // 若不一样,则cur往前走一步
                cur = cur -> next;
            }
        }
        return head;
    }
};

Java版:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode cur = head;
        while(cur.next != null) {				// 要保证cur指向的节点一直存在,否则拿cur跟谁比
            if(cur.val == cur.next.val) {		// 当cur当前的值和指向的值相等时,cur跳过下一个指向下下个
                cur.next = cur.next.next;
            } else {							// 当cur当前的值和指向的值不相等时,cur往前走一步
                cur = cur.next;
            }
        }
        return head;
    }
}
5.删除链表的倒数第 N 个结点(LeetCode19)

idea: 双指针法(two pointers)
定义快慢指针fast和slow,以及防守指针dummy
分三步:
1.用for循环fast先走n步;
2.然后fast和slow一起走(期间fast和slow始终差n);
3.fast走到头,此时slow指的就是要删除的(即从尾数倒数第n个),跳过它

代码
C++

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *dummy = new ListNode(0);
        dummy -> next = head;

        ListNode *slow = dummy, *fast = dummy;
        // 1.fast先走n步
        for(int i = 0 ; i < n + 1 ; i ++) {
            fast = fast -> next;
        }

        // 2.fast和slow一起走到头,期间fast和slow的差距始终为n,fast走到头后,slow指向的即为要删除的(从尾数第n个)
        while(fast) {
            slow = slow -> next;
            fast = fast -> next;
        }

        // 3.此时slow指向的就是要删除的节点,删除它
        ListNode *delNode = slow -> next;
        slow -> next = delNode -> next;
        delete delNode;

        ListNode* retNode = dummy->next;                    // 有些情况head被删除了
        delete dummy;

        return retNode;
    }
};
6.两两交换链表中的节点 / 交换链表中的相邻结点(LeetCode24)

idea: 双指针法(two pointers)
e.g
1 -> 2 -> 3 -> 4
目的是把2指向1,4指向3,定义两个指针start和end,start指向1或3,end指向3或4,还需要一个临时指针temp,用来指向下一个start的前一个,即当前end指向的节点
假如要交换1和2,分三步:先定住1和2的首位,最后一步再交换,(开始交换前temp指向1,1赋值给start,2赋值给end)
1.定住首: temp指向2(temp.next = end);
2.定住尾: 1指向3(start.next = end.next)
3.交换(end.next = start)
因为temp指向下一个start的前一个,即当前的start位置(temp = start)

代码:
Java

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);                           // 设一个最后返回的哑指针dummy
        dummy.next = head;                                          // dummy指向head,用于最后返回
        ListNode temp = dummy;
        // 确保temp有下一个和下下个
        while(temp.next != null && temp.next.next != null) {
            // 每次循环开始时都把start和end定义好,即要互相交换的两个节点
            ListNode start = temp.next;
            ListNode end = temp.next.next;
            
            // 三步指开始:
            temp.next = end;                                     // 1.定住首
            start.next = end.next;                               // 2.定住尾
            end.next = start;                                    // 3.交换
            
            // temp始终指向下一个start的前一个,即当前start处
            temp = start;
        }
        return dummy.next;
    }
}
7.两数相加 II / 链表求和(LeetCode445)

idea: 栈(stack)
考虑到个位在链表的尾部,加法要从个位开始加起,故考虑栈的方法,先入后出
申请两个栈s1和s2,把链表的值加到栈里,然后开始pop,分为以下几步:
1.求计算当前位的和sum(有可能包括上一次的进位1);
2.生成当前位节点tmp,其值为sum模10即可;
3.设置当前位的指向,即和头节点dummy一样;
4.设置新的dummy指针的指向;
5.处理进位,用于下一轮迭代;

代码:
C++

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if(!l1 || !l2) return l1 == NULL? l2 : l1;

        stack<int> s1, s2;                             // Java的是Stack<Integer> = new Stack<Integer>();
        while(l1 != NULL) {
            s1.push(l1 -> val);
            l1 = l1 -> next;                           // l1往前走一步;
        }

        while(l2 != NULL) {
            s2.push(l2 -> val);
            l2 = l2 -> next;
        }

        int carry = 0, n1 = 0, n2 = 0, sum = 0;
        ListNode* dummy = new ListNode(-1);             // 创建头节点
        ListNode* tmp = NULL;
        while(!s1.empty() || !s2.empty() || carry) {
            // 求计算当前位的和sum
            if(s1.empty()) n1 = 0;
            else {n1 = s1.top(); s1.pop();}
            if(s2.empty()) n2 = 0;
            else {n2 = s2.top(); s2.pop();}

            sum = (n1 + n2 + carry);
            
            // 2.生成当前位节点tmp
            tmp = new ListNode(sum % 10);
            // 3.设置当前位的指向,即和头节点dummy一样
            tmp -> next = dummy -> next;
            // 4.设置新的dummy指针的指向
            dummy->next = tmp;
            // 5.处理进位,给下一次迭代用
            carry = sum / 10;
        }

        // 释放dummy指针防止内存泄露
        ListNode* res = dummy->next;                          // 此时dummy指向最终生成的头节点
        delete dummy;
        
        return res;
    }
};

Java

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> s1 = new Stack<Integer>();
        Stack<Integer> s2 = new Stack<Integer>();
        
        while (l1 != null) {                            // 把链表1的每个值存入栈中,最高位的在最下面,个位在栈顶
            s1.push(l1.val);
            l1 = l1.next;
        }
        
        while (l2 != null) {
            s2.push(l2.val);
            l2 = l2.next;
        }
        
        int sum = 0;
        ListNode d = new ListNode(0);
        while (!s1.empty() || !s2.empty()) {
            // 从个位开始加起,1.求计算当前位的和sum
            if (!s1.empty()) sum += s1.pop();
            if (!s2.empty()) sum += s2.pop();
            d.val = sum % 10;                               // 2.求当前位
            ListNode head = new ListNode(sum / 10);         // 3.生成下一位节点head,值是0或1只是暂时的,需要下一轮更正,若没有下一轮了,生成的0或1就是最终的了.(关键)
            head.next = d;                                  // 4.设置好指向
            d = head;                                       // 5.d后退一步,准备下一轮
            sum /= 10;                                      // 6.求进位
        }
        
        return d.val == 0 ? d.next : d;                     // 最后没有下一轮了,head生成的0或1就是最终的(此时d也指head),当d为0时,返回0的下一个;当d为1时,从d开始返回
    }
}
8. / 回文链表(LeetCode234)

idea: 快慢指针法(fast and slow pointers)
定义快慢指针fast和slow,一共分3步:
1.快指针每次走两步,慢指针每次走一步;
2.反转后半部分,即slow指向的部分(a.记录cur的下一个;b.反转;c.cur和pre各往前走一步,注意先后顺序)
3.头指针(指向前半部分))和slow指针(指向后半部分)挨个比数;
e.g
1 -> 2 -> 3 -> 3 -> 2 -> 1

代码:
Java

class Solution {
    public boolean isPalindrome(ListNode head) {
            if (head == null || head.next == null) {
                return true;
            }
        // 定义为ListNode fast = head.next也行,区别在于是第一个3指null,而ListNode fast = head是第二个3指null
            ListNode fast = head;
            ListNode slow = head;
            while (fast != null && fast.next != null) {
                fast = fast.next.next;
                slow = slow.next;
            }
            slow = reverse(slow);
            while (head != null && slow != null) {
                if (head.val != slow.val) {
                    return false;
                }
                head = head.next;
                slow = slow.next;
            }
            return true;
        }
    // 定义反转函数
    public ListNode reverse(ListNode cur) {
        ListNode pre = null;
        while (cur != null) {
            ListNode tmp = cur.next;                        // a.先记录
            cur.next = pre;                                 // b.再反转
            pre = cur;                                      // c.最后pre和cur各往前一步(注意先后顺序)
            cur = tmp;
        }
        return pre;                                         // cur=null了,不满足while循环,返回pre即可
    }
}
9.分隔链表(LeetCode725)

idea:
1.计算链表的长度;
2.由题意一共要分成k个块, 计算每个块的基本大小,以及较长块的个数,得到要返回的res信息,即每个块的个数;
3.切分;
e.g
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11, k = 3
对于最后的切分过程: res = [4,4,3]
index = 0, num = 4; index = 1, num = 4; index = 2, num = 3
第一轮
prev = null, curr = 1, 不执行if prev;
res[0] = 1, prev和curr一起往前走4个位置: prev = 4, curr = 5
第二轮
prev = 4, curr = 5, 执行if prev,切断;
res[1] = 5, prev和curr一起往前走4个位置: prev = 8, curr = 9
第三轮
prev = 8, curr = 9, 执行if prev,切断;
res[2] = 9, prev和curr一起往前走3个位置,结束

代码
Java

class Solution {
    public ListNode[] splitListToParts(ListNode root, int k) {
        // 1.计算链表的长度
        int length = 0;
        ListNode cur = root;
        while(cur != null) {
            length++;
            cur = cur.next;
        }

        int longer_chunks = length % k;
        int chunk_size = length / k;
        ListNode[] res = new ListNode[k];
        cur = root;
        for (int i = 0; cur != null && i < k; i++) {
            res[i] = cur;
            // 一共有longer_chunk较长的,比正常的多1.curSize要么为4,要么为3
            int curSize = chunk_size + (longer_chunks-- > 0 ? 1 : 0);
            for (int j = 0; j < curSize - 1; j++) {
                cur = cur.next;
            }
            // 3.切割
            ListNode tmp = cur.next;                    // a.先记录cur的下一个
            cur.next = null;                            // b.断开
            cur = tmp;                                  // c.cur再往前进一位
        }
        return res;
    }
}

Python

class Solution:
    def splitListToParts(self, root: ListNode, k: int) -> List[ListNode]:
        # 1.计算链表的长度
        curr, length = root, 0
        while curr:
            curr, length = curr.next, length + 1                # length = 11
        
        # 计算每个块的大小,以及较长快的个数
        chunk_size, longer_chunks = length // k, length % k
        # chunk_size=3(每个块的基本大小),longer_chunks=2(较长块即长度为4的个数为2个,剩下的1个为基本大小即3)
        res = [chunk_size + 1] * longer_chunks + [chunk_size] * (k - longer_chunks)
        # res = [3+1]*2+[3]*(3-2) = [4,4,3]
        
        # 切分链表
        prev, curr = None, root
        for index, num in enumerate(res):              # index = 0,num = 4; index = 1,num=4, index=2, num=3
            if prev:                                   # 若当前prev指的不是空(即排除刚开始的情况),让其指向空
                prev.next = None
            # 对于每组index和num
            res[index] = curr                          # 先在res中确定每个链表的头
            for i in range(num):                       # cur和prev一起往前走,再由上面的if prev确定尾
                prev, curr = curr, curr.next

        return res
10.链表元素按奇偶聚集(LeetCode328)

idea: 双指针法(two pointers)
定义两个指针odd和even, odd指向奇数下标,even指向偶数下标; dummy指针指向偶数下标的开头且不再移动
分为三步:
1.odd和even各指向其下一个节点:odd.next = odd.next.next, even.next = even.next.next, 注意这里的顺序不能反了,必须奇数下标先指,偶数下标后指
2.odd和even各往前走一步:odd = odd.next, even = even.next(这里二者的顺序可以反过来不影响)
3.最后把odd的尾接到even的头(刚开始用dummy记录even的头节点)

代码
Java

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head == null) return null;
        ListNode odd = head, even = head.next, dummy = head.next;  // 可写到一行,用dummy记录even头节点
        while(odd.next != null && even.next != null) {
            // 1.先指,注意下面两行顺序不能颠倒了,必须是先奇后偶
            odd.next = odd.next.next;
            even.next = even.next.next;
            // 2.再各往前走一步
            odd = odd.next;
            even = even.next;
        }

        // 3.用odd的尾指向even的头
        odd.next = dummy;

        return head;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值