06.从尾到头打印链表

  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. vector<int> reversePrint(ListNode* head) {
  12. vector<int> ret;
  13. function<void(ListNode*)>dfs = [&](ListNode* head){
  14. if(!head){
  15. return;
  16. }
  17. dfs(head->next);
  18. ret.push_back(head->val);
  19. return;
  20. };
  21. dfs(head);
  22. return ret;
  23. }
  24. };

24.反转链表

递归

递归的想法就在于假设链表的其余部分已经被反转,现在应该如何反转它前面的部分。
推广来说,递归就是选一个完成到中间的情况来看下一步应该怎么做。
image.png

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head || !head->next) {
            return head;
        }
        ListNode* newHead = reverseList(head->next);//这一行只是用来记载反转后的链表的第一个节点
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

迭代

迭代写法

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL, *cur = head, *next;
        while(cur){
            next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

复杂链表的复制(重点)

这一题有细节的,关键就在这个哈希表的设计,设计的目的就是避免重复拷贝的情况发生。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;

    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    unordered_map<Node*, Node*> cachedNode;

    Node* copyRandomList(Node* head) {
        if (head == nullptr) {
            return nullptr;
        }
        if (!cachedNode.count(head)) {
            Node* headNew = new Node(head->val);
            cachedNode[head] = headNew;
            headNew->next = copyRandomList(head->next);
            headNew->random = copyRandomList(head->random);
        }
        return cachedNode[head];
    }
};

快慢指针

剑指 Offer II 021. 删除链表的倒数第 n 个结点

  • 这题写过,先使用一个快指针来指向n所在的位置,然后使用一个慢指针从头开始
  • 然后快慢指针同时开始,当快指针指向空时,满指针所在的位置就是倒数第n个节点
  • 注意如果在赋值之前调用属于链表的功能,就需要使用 new来初始化!!!

    class Solution {
    public:
      ListNode* removeNthFromEnd(ListNode* head, int n) {
          ListNode *quick = head,*dummy = new ListNode(),*pre;
          dummy->next = head;
          pre = dummy;
          while(n){
              n--;
              quick = quick->next;
          }
          while(quick){
              quick = quick->next;
              pre = head;
              head = head->next;
          }
          pre->next= head->next;
          return dummy->next;
      }
    };
    

    剑指 Offer II 022. 链表中环的入口节点

  • 先使用快慢指针同时从head开始跑,当相遇时就代表有环

  • 然后head指针和slow同时同速度开始跑,当相遇时,这个节点即为入口节点
  • 这是由数学方法得到的

    class Solution {
    public:
      ListNode *detectCycle(ListNode *head) {
          ListNode*slow = head, *fast = head;
          do{
              if(!fast||!fast->next) return NULL;
              fast = fast->next->next;
              slow = slow->next;
          } while (fast != slow);
    
          fast = head;
    
          while(fast != slow){
              slow = slow->next;
              fast = fast->next;
          }
          return fast;
      }
    };
    

    剑指 Offer II 023. 两个链表的第一个重合节点

  • 两个指针同时从头开始跑,当到达null之后就从对方的头开始,当相等的时候就代表是第一个重合节点,如果相等且为空就代表没有重合部分

  • 这里可以使用数学来证明!!!

    class Solution {
    public:
      ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
          ListNode*node1 = headA, *node2 = headB;
          while(node1!=node2){
              if(!node1) node1 = headB;
              else node1 = node1->next;
              if(!node2) node2 = headA;
              else node2 = node2->next;
          }
          return node1;
      }
    };
    

    链表的数学操作

    剑指 Offer II 025. 链表中的两数相加

    反转链表写法

    我的写法,比较笨,先反转两条,然后相加,然后反转回来
    显然使用迭代来做更加简单!!!

    class Solution {
    public:
      ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
          l1 = reverseList(l1);
          l2 = reverseList(l2);
          int bite = 0;
          return reverseList(dfs(l1, l2, bite));
      }
      ListNode* dfs(ListNode* l1, ListNode* l2, int bite){
          if(!l1||!l2){
              if(bite == 0) return l1? l1:l2;
              else {
                  l1 = l1? l1:l2;
                  if(!l1){
                      ListNode* node = new ListNode(1);
                      node->next = nullptr;
                      return node;
                  } else {
                      ListNode* l3 = l1;
                      while(bite){
                          int sum = l3->val+bite;
                          bite = sum/10;
                          l3->val = sum%10;
                          if(!bite) break;
                          if(!l3->next) {
                              ListNode* node = new ListNode(1);
                              l3->next = node;
                              node->next = nullptr;
                              break;
                          }
                          l3 = l3->next;
                      }
                      return l1;
                  }
              }
          }
          int sum = l2->val + l1->val + bite;
          bite = sum/10;
          ListNode* node = new ListNode(sum%10);
          node->next = dfs(l1->next, l2->next, bite);
          return node;
      }
      ListNode* reverseList(ListNode* head) {
          if(!head||!head->next) {
              return head;
          }
          ListNode* newhead = reverseList(head->next);
          head->next->next = head;
          head->next = nullptr;
          return newhead;
      }
    };
    

    简单写法
    这里的加法部分使用迭代比较简单
    注意使用一个哑节点,dummy来指向前一个节点

    class Solution {
    public:
      ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
          //若任一链表为空,则直接返回另一链表
          if (l1 == nullptr || l2 == nullptr) return l1 == nullptr ? l2 : l1;
    
          l1 = reverseList(l1), l2 = reverseList(l2);     //反转链表,便于进行加法运算
          return reverseList(addList(l1, l2));            //相加后再反转,最后返回结果
      }
    
      ListNode* reverseList(ListNode* head)               //反转链表模板
      {
          if (head == nullptr) return head;
          ListNode *pre = head, *cur = head->next;
    
          while (cur)
          {
              ListNode *ne = cur->next;
              cur->next = pre;
              pre = cur, cur = ne;
          }
    
          head->next = nullptr;
          return pre;
      }
    
      ListNode* addList(ListNode* l1, ListNode* l2)       //链表相加
      {
          ListNode *dummy = new ListNode(0), *p = dummy;
    
          int carry = 0;              //进位标志
          while (l1 || l2)
          {
              //链表不为空就将当前的两个节点的值加起来,为空就加0
              carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val);
    
              //如果当前节点有进位,就取模后为1,没有进位就保持原值
              //p = p->next = xx 表示 p->next = xx, p = p->next两条语句的组合
              p = p->next = new ListNode(carry % 10);
    
              //如果有进位,则carry余1, 没有进位则carry重置为0
              carry /= 10;
    
              if (l1) l1 = l1->next;
              if (l2) l2 = l2->next;
          }
    
          //判断最高位是否有进位,有的话就再追加一个1即可
          if (carry) p->next = new ListNode(1);
          return dummy->next;
      }
    };
    

    使用栈来做

    这里主要是因为加法操作是从低位到高位,链表的元素需要反着来遍历,利用栈的先进后出的特性也能满足条件

    class Solution {
    public:
      ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
          stack<int> num1,num2;
          //使用栈来存储元素
          while(l1 != nullptr){
              num1.push(l1->val);
              l1 = l1->next;
          }
    
          while(l2 != nullptr){
              num2.push(l2->val);
              l2 = l2->next;
          }
    
          int jw = 0;//进位符号
          ListNode* p = nullptr;//直接反着构造,这里的p就是最后一个节点的next
    
          while(!num1.empty() || !num2.empty() || jw){
              int n1 =num1.empty() ? 0 : num1.top();
              int n2 = num2.empty() ? 0 :num2.top();
              if(!num1.empty())   num1.pop();
              if(!num2.empty())   num2.pop();
    
              int num = n1 + n2 + jw;
              jw = num / 10;
              num = num % 10;
    
              ListNode* newnode = new ListNode(num,p);//直接反着构造!!
              p = newnode;
          }
    
          return p;//因为是直接反着构造的,所以最终的p就是头节点
      }
    };
    

    剑指 Offer II 026. 重排链表

    线性链表

    使用一个vector来储存链表的节点,因为链表不支持下标访问,所以利用线性表的下标来直接按照顺序访问指定元素

    class Solution {
    public:
      void reorderList(ListNode *head) {
          if (head == nullptr) {
              return;
          }
          vector<ListNode *> vec;
          ListNode *node = head;
          while (node != nullptr) {
              vec.emplace_back(node);
              node = node->next;
          }
          int i = 0, j = vec.size() - 1;
          while (i < j) {
              vec[i]->next = vec[j];
              i++;
              if (i == j) {
                  break;
              }
              vec[j]->next = vec[i];
              j--;
          }
          vec[i]->next = nullptr;
      }
    };
    

    寻找链表中点+链表逆序+合并链表

  • 通过快慢指针找到链表的中点

  • 将链表的右半端翻转
  • 将链表的左半端和右半端和并

    class Solution {
    public:
      void reorderList(ListNode* head) {
          if (head == nullptr) {
              return;
          }
          ListNode* mid = middleNode(head);
          ListNode* l1 = head;
          ListNode* l2 = mid->next;//注意mid为第一个链表的结尾
          mid->next = nullptr;
          l2 = reverseList(l2);
          mergeList(l1, l2);
      }
    
      ListNode* middleNode(ListNode* head) {
          ListNode* slow = head;
          ListNode* fast = head;
          while (fast->next != nullptr && fast->next->next != nullptr) {
              slow = slow->next;
              fast = fast->next->next;
          }
          return slow;
      }
    
      ListNode* reverseList(ListNode* head) {
          ListNode* prev = nullptr;
          ListNode* curr = head;
          while (curr != nullptr) {
              ListNode* nextTemp = curr->next;
              curr->next = prev;
              prev = curr;
              curr = nextTemp;
          }
          return prev;
      }
    
      void mergeList(ListNode* l1, ListNode* l2) {
          ListNode* l1_tmp;
          ListNode* l2_tmp;
          while (l1 != nullptr && l2 != nullptr) {
              l1_tmp = l1->next;
              l2_tmp = l2->next;
    
              l1->next = l2;
              l1 = l1_tmp;
    
              l2->next = l1;
              l2 = l2_tmp;
          }
      }
    };
    

    剑指 Offer II 027. 回文链表(同上)

  • 与上一题类似

  • 先找中点,然后翻转,然后比较即可

    双向链表

    剑指 Offer II 028. 展平多级双向链表(深度优先搜索)

    出现了几个问题,其他的都比较简单

  • 返回的head已经改变了没有注意到

  • 没有考虑到空指针的情况。

    /*
    // Definition for a Node.
    class Node {
    public:
      int val;
      Node* prev;
      Node* next;
      Node* child;
    };
    */
    class Solution {
    public:
      Node* flatten(Node* head) {
          if(!head) return NULL;
          Node* newhead = head;
          if(head->child) {
              Node* next = head->next;
              head->child->prev = head;
              head->next = flatten(head->child);
              head->child = NULL;
              while(head->next){
                  head = head->next;
              }
              head->next = next;
              if(next) next->prev = head;//这里注意考虑到空指针的情况
          }   
          flatten(head->next);
          return newhead;//这里注意不要改变输入进来的head,因此先使用一个newhead来保存
      }
    };
    

    循环链表

    剑指 Offer II 029. 排序的循环链表(重点)

  • 注释已经写的很清楚了

  • 三种情况
  • 一个节点的情况不用考虑,因为是循环链表
    class Solution {
    public:
      /*
      3种插入情况:
          1) cur.val <= x <= cur.next.val 顺序插入 
          2) 插入点为为序列的边界跳跃点:
              如 3->4->1 插入5,这样 1(cur->next)<4(cur)<5(x) 4为插入点的前驱节点;这种情况表示x比最大值都大
              如 3->4->1 插入0,这样 0(x)<1(cur->next)<4(cur) 4为插入点的前驱节点;这种情况表示x比最小值都小
      */
      Node* insert(Node* head, int x) {
          if(!head){
              head=new Node(x);
              head->next=head;
              return head;
          }
          Node *cur=head;
          while(cur->next!=head){
              // cur 为边界跳越点
              if(cur->next->val<cur->val){//找边界点比较好
                  if(x>=cur->val)break;// x比最大值都大
                  if(x<=cur->next->val)break;// x比最小值都小
              }   
              // 顺序插入x中升序序列中
              if(x>=cur->val&&x<=cur->next->val)break;
              cur=cur->next;
          }
          // 将x插入到cur与cur->next之间
          cur->next=new Node(x,cur->next);
          return head;
      }
    };