剑指offer 打卡计划| 每日进步一点点 | 第八天

86 阅读4分钟

图片.png

剑指 Offer 24. 反转链表

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

思路

(双指针,迭代) (n)

反转链表即将所有节点的next指针指向他的前驱节点。由于是单链表,我们在迭代时不能直接找到前驱节点,所以我们需要一个额外的指针保存前驱节点。同时在改变当前节点的next指针前,不要忘记保存它的后继节点。

具体过程如下:

  • 1、定义一个前驱指针precur指针,pre指针用来指向前驱节点,cur指针用来遍历整个链表,初始化cur = headpre = null
  • 2、首先让t指针指向cur指向节点的后继节点,然后让cur指向节点的next指针指向其前驱节点,即cur->next = pre
  • 3、pre指针和cur指针分别后移一位,重复上述过程,直到cur指向空节点。
  • 4、最后我们返回pre节点。

图片.png 时间复杂度分析: 只遍历一次链表,时间复杂度是O(n)。

c++代码

 /**
  * 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;
         ListNode *cur = head;
         while(cur)
         {
             ListNode *t = cur->next; //保留后继节点
             cur->next = pre; //当前节点指向前驱节点
             pre = cur, cur = t;
         }
         return pre;
     }
 };

剑指 Offer 25. 合并两个排序的链表 *

思路

(二路归并) O(n)

1、新建头部的虚拟头节点dummy,设置cur指针指向dummy

2、若当前l1指针指向的节点的值vall2指针指向的节点的值val小,则令curnext指针指向l1,且l1后移一位;否则指向l2,且l2后移一位。

3、然后cur指针按照上一步设置好的位置后移。

4、循环以上步骤直到l1l2为空。

5、将剩余的l1l2接到cur指针后边。

时间复杂度分析: 两个链表各遍历一次,所以时间复杂度为O(n)

c++代码

 /**
  * Definition for singly-linked list.
  * struct ListNode {
  *     int val;
  *     ListNode *next;
  *     ListNode(int x) : val(x), next(NULL) {}
  * };
  */
 class Solution {
 public:
     ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
         ListNode* dummy = new ListNode(-1);
         ListNode* cur = dummy;
         while(l1 != NULL && l2 != NULL){
             if(l1->val < l2->val){
                 cur->next = l1;
                 l1 = l1->next;
             }else{
                 cur->next = l2;
                 l2 = l2->next;
             }
             cur = cur->next;
         }
         cur->next = (l1 != NULL ? l1 : l2); //将剩余的l1或l2接到cur指针后边
         return dummy->next;
     }
 };

剑指 Offer 26. 树的子结构 *

(二叉树,递归) O(nm)

我们首先判断两棵二叉树是否为空,如果一个为空,则直接返回false。遍历树A中的所有非空节点root,判断树A中以root为根节点的子树是不是包含和树B一样的结构,且我们从根节点开始匹配,当从A的根节点开始不匹配的话,我们递归到A的左右子树去匹配。

isSame函数用来判断判断B是否为A的子树,具体设计思路如下:

  • 如果树B中的节点为空,则表示当前分支是匹配的,返回true
  • 如果树A中的节点为空,但树B中的节点不为空,则说明不匹配,返回false
  • 如果两个节点都不为空,但数值不同,则说明不匹配,返回false
  • 否则说明当前这个点是匹配的,然后递归判断左子树和右子树是否分别匹配即可;

时间复杂度分析: 最坏情况下,我们对于树A中的每个节点都要递归判断一遍,每次判断在最坏情况下需要遍历完树B中的所有节点。 所以时间复杂度是 O(nm),其中 n 树A中的节点数, m 是树B中的节点数。

c++代码

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
  * };
  */
 class Solution {
 public:
     bool isSubStructure(TreeNode* A, TreeNode* B) {
         if(!A || !B) return false; //如果A树或者B树一个为空,则不匹配
         if(isSame(A, B)) return true;  // 从根节点开始判断,找到第一个匹配的位置
         // 如果根节点不匹配的话,我们递归到左右子树去判断
         return isSubStructure(A->left, B) || isSubStructure(A->right, B);
     }
 ​
     bool isSame(TreeNode* A, TreeNode* B){
         if(!B) return true; //B树为空,匹配成功
         // B树不为空,若A树为空,或者A,B都不为空,但是值不相等,匹配不成功
         if(!A || A->val != B->val) return false;
         //否则说明当前这个点是匹配的,然后递归判断左子树和右子树是否分别匹配即可
         return isSame(A->left, B->left) && isSame(A->right, B->right);
     }
 };
 ​