11.15灵神题单补充

11/15

每日一题:3239. 最少翻转次数使二进制矩阵回文 I

思路:模拟即可

​ 先遍历行,累加每行需要翻转的个数,总和记为diff_row+=的优先级是小于!=的,所以会先比较,再累加。

int diff_row = 0;
for(auto &row : grid){
  for(int j = 0 ; j < n / 2 ; j ++){
    diff_row += row[j] != row[n - 1 - j];
  }
}
优先级(从高往低) 运算符 结合律 助记
1 :: 从左至右 作用域
2 a++a--type()type{}a()a[].-> 从左至右 后缀自增减、 函数风格转型、 函数调用、下标、 成员访问
3 !~++a--a+a-a(type)sizeof&a*anewnew[]deletedelete[] 从右至左 逻辑非、按位非、 前缀自增减、正负、 C 风格转型、取大小、取址、 指针访问、 动态内存分配
4 .*->* 从左至右 指向成员指针
5 a*ba/ba%b 从左至右 乘除、取模
6 a+ba-b 从左至右 加减
7 <<>> 从左至右 按位左右移
8 <<=>>= 从左至右 大小比较
9 ==、!= 从左至右 等价比较
10 a&b 从左至右 按位与
11 ^ 从左至右 按位异或
12 ` ` 从左至右
13 && 从左至右 逻辑与
14 ` `
15 a?b:c=+=-=*=/=%=&=^=、` =<<=>>=` 从右至左
16 , 从左至右 逗号

​ 同理,累加diff_col

int diff_col = 0;
for(int j = 0 ; j < n ; j ++){ //先列后行,固定同一列比较不同行
  for (int i = 0 ; i < m / 2; i ++ ){
    diff_col += grid[i][j] != grid[m - 1 - i][j];
  }
}

​ 返回最小值即可。

class Solution {
public:
    int minFlips(vector<vector<int>>& grid) {
        int m = grid.size() , n = grid[0].size();

        int diff_row = 0;
        for (auto &row : grid){
          for (int j = 0; j < n / 2; j++) {
             diff_row += row[j] != row[n - 1 - j];
          }
        }

        int diff_col = 0;
        for (int j = 0; j < n; j++) {
           for (int i = 0; i < m / 2; i++) {
              diff_col += grid[i][j] != grid[m - 1 - i][j];
           }
        }
        return min(diff_row , diff_col);
    }
};

3217. 从链表中移除在数组中存在的节点

思路:哈希表+遍历链表

​ 由于直接判断节点值是否在 nums 中要遍历 nums,时间复杂度为 O(n)。通过把 nums 中的元素加到一个哈希集合中,然后判断节点值是否在哈希集合中,这样可以做到每次判断时间复杂度为 O(1)。

​ 由于头节点可能会被删除,在头节点前面插入一个哨兵节点 dummyHead

​ 删除一个节点需要它的上一个节点,所以初始化cur = dummyHead时,要先判断下一个节点cur->next->val是否在哈希表中,如果没有才能移动cur。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* modifiedList(vector<int>& nums, ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        unordered_set<int> s(nums.begin() , nums.end());
        ListNode* cur = dummyHead;

          while(cur->next ) {
            if(s.count(cur->next->val))     cur->next = cur->next->next; //这里cur->next->next可能为nullptr,但是没关系。                 
             else cur = cur->next;
          }              
        return dummyHead->next;
    }
};

83. 删除排序链表中的重复元素

思路:空表需要特判一下

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* cur = head;
        if (!cur) return nullptr;
        while(cur->next){
          if(cur->val == cur->next->val) cur->next = cur->next->next;
          else cur = cur->next;
        }
        return head;
    }
};

82. 删除排序链表中的重复元素 II

思路:注意逻辑

​ 由于可能删除头结点,因此需要定义dummyHead(与上题对比,上题保留一个重复数,因此不可能删除head)

​ cur初始在dummyHead处,只有在cur->next->val != cur->next->next->val时(后两个数不同时)才会向后移动,因此if的判断条件为(cur->next->next->val == val)。由于一个重复数都不保留,因此先把cur->next->val取出来给val。当cur->next->next->val == val时,循环判断cur->next && cur->next->val == val后一个数是否也等于val,是就跳过,直到不是为止。

​ 从始至终cur就没移动过,因为可能出现{1,2,2,3,3}的情况。只有在cur->next->val != cur->next->next->val时(后两个数不同时)cur才会向后移动。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next  = head;
        ListNode* cur = dummyHead;
        while(cur->next && cur->next->next){
            int val = cur->next->val;
            if(cur->next->next->val == val){
              while(cur->next && cur->next->val == val){
                cur->next = cur->next->next;
              }
            }
            else cur = cur->next;
        }
         return dummyHead->next;
    }
};

237. 删除链表中的节点

思路:用后一个节点的值赋给当前node,然后改变指针跳过下一个节点即可。

class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val = node->next->val;
        node->next = node->next->next;
    }
};

1669. 合并两个链表

思路:模拟即可

​ 找到a的前一个节点cur1,b节点cur2,list2的尾节点cur。

​ 令cur1->next = list2 , cur->next = cur2->next , cur2->next = nullptr即可。

class Solution {
public:
    ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
        ListNode* cur1 = list1;
        ListNode* cur2 = list1;
        for (int i = 0; i < a - 1; i++) {
           cur1 = cur1->next;
        }
        for (int i = 0; i < b ; i++) {
           cur2 = cur2->next;
        }
        ListNode* cur = list2;
        while(cur->next){
          cur = cur->next;
        }
        //此时cur在list2的尾节点,cur1是在a的前一个节点,cur2在b节点。     
        cur1->next = list2;
        cur->next =  cur2->next;
        cur2->next = nullptr;
        return list1;
    }
};

2487. 从链表中移除节点

思路:这题本质上是说,从链表尾tail开始遍历,比tail->val大的留下,小的删除。

方法一:递归

​ 既然要倒着看最大值,那么用递归解决是最合适的,毕竟递归本质就是在倒着遍历链表

方法二:迭代

通过 反转链表,我们可以从反转后的链表头开始,像 删除排序链表中的重复元素 那样,删除比当前节点值小的元素。最后再次反转链表,即为答案。

​ 代码上来看递归更简介,迭代更清晰。

递归法:

class Solution {
public:
    ListNode* removeNodes(ListNode* head) {
       if(head->next == nullptr)  return head;//结束条件

       ListNode* node = removeNodes(head->next);// 此时node应该为尾节点tail,出栈从后往前遍历      
      //如果tail值大于head(tail的上一个节点)值,返回node,删除head
       if(node->val > head->val)  return node;
       
       //如果node(tail)值小于head值,将head接到node前面
        head->next = node; 
        // 返回的链表头一定是最大的
        return head;
    }
};
  • 时间复杂度:O(n),其中 n 为链表的长度。
  • 空间复杂度:O(n),需要 O(n) 的栈空间。

迭代法:

class Solution {
public:
    ListNode* reverseList(ListNode* head){
      ListNode *pre = nullptr , *cur = head;
      while(cur){
        ListNode* tmp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = tmp;
      }
      return pre;
    }

    ListNode* removeNodes(ListNode* head) {
      head = reverseList(head);
      ListNode* cur = head;
      while(cur->next){
        if(cur->val > cur->next->val){
          cur->next = cur->next->next;
        }
        else cur = cur->next;
      }
      return reverseList(head);
    }
};

2807. 在链表中插入最大公约数

思路:写一个找最大公约数的函数然后循环插入即可。

调用cpp内置的gcp函数可以更简洁。

gcd函数简介

最大公因数(英语:highest common factor,hcf)也称 最大公约数(英语:greatest common divisor,gcd)是数学词汇,指能够整除多个整数的最大正整数。而多个整数不能都为零。例如8和12的最大公因数为4。

求两个整数最大公约数主要的方法:
1.穷举法:分别列出两整数的所有约数,并找出最大的公约数。
2.素因数分解:分别列出两数的素因数分解式,并计算共同项的乘积。
3.短除法:两数除以其共同素因数,直到两数互素时,所有除数的乘积即为最大公约数。
4.辗转相除法:两数相除,取余数重复进行相除,直到余数为0时,前一个除数即为最大公约数。程序如下:

 int gcd(int a,int b) {
    int r;
    while(b>0) {
        r=a%b;
        a=b;
        b=r;
    }
    return a;
}

递归+三元运算符实现

int gcd(int a,int b) {
    return b > 0 ? gcd(b, a % b) : a;
}

本题直接调用gcd函数即可:

 cur->next = new ListNode(gcd(cur->val, cur->next->val), cur->next);
class Solution {
  private:
    int Find(int x , int y){
      if(x == y) return x;
      if(x == 1 || y == 1)  return 1;
      int res = 1;
      for (int i = 2; i <= min(x , y); i++) {
         if(x % i == 0 && y % i == 0) res = max(res , i);
      }
      return res;
    }
  /*亦可写成
   int Find(int x , int y){
      int r;
      while(y > 0){
        r = x % y;
        x = y ;
        y = r;
      }
      return x;
    }*/
public:
    ListNode* insertGreatestCommonDivisors(ListNode* head) {
        ListNode* cur = head;
        if(!cur->next)  return head;
        while(cur->next){
          int val = Find(cur->val , cur->next->val);
          ListNode* node = new ListNode(val);
          node->next = cur->next;
          cur->next = node;
          cur = node->next;
        }
      return head;
    }
};

灵神的简单写法:

class Solution {
public:
    ListNode *insertGreatestCommonDivisors(ListNode *head) {
        for (auto cur = head; cur->next; cur = cur->next->next) {
          //Listnode有两个参数val , *next
            cur->next = new ListNode(gcd(cur->val, cur->next->val), cur->next);
        }
        return head;
    }
};

147. 对链表进行插入排序

思路:

​ 对于链表而言,插入元素时只要更新相邻节点的指针即可,不需要像数组一样将插入位置后面的元素往后移动,因此插入操作的时间复杂度是 O(1),但是找到插入位置需要遍历链表中的节点,时间复杂度是 O(n),因此链表插入排序的总时间复杂度仍然是 \(O(n ^2)\),其中 n 是链表的长度。

​ 对于单向链表而言,只有指向后一个节点的指针,因此需要从链表的头节点开始往后遍历链表中的节点,寻找插入位置,具体过程如下。

  1. 先判断链表是否为空,若为空直接返回。

  2. 创建dummyHead,便于在 head 节点之前插入节点。维护 last 为链表的已排序部分的最后一个节点,初始时 last= head。维护 cur 为待插入的元素,初始时 cur = head.next。

  3. 每次比较lastcur 的节点值。若 last.val <= cur.val,说明 cur 应该位于 last 之后,将 last 后移一位,cur 变成新的 last

    否则,从链表的头节点开始往后遍历链表中的节点,寻找插入 cur 的位置。令 pre 为插入 cur 的位置的前一个节点,进行如下操作,完成对 cur 的插入:

last.next = cur.next//注意保存cur->next,cur是在原链表上往后移动
cur.next = prev.next
prev.next = cur
  1. cur = last.next,此时 cur 为下一个待插入的元素。

  2. 重复直到 cur 变成空,排序结束。

  3. 返回 dummyHead.next,为排序后的链表的头节点。

注意细节:是要加大于等于的,如果只写>,后面else里pre会走到last,while跳不出循环。

if(cur->val >= last->val) last = last->next;
class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
      if(!head)  return head;      
       
       ListNode* dummyHead = new ListNode(0);
       dummyHead->next = head;
       ListNode* last = head;
       ListNode* cur = head->next;
       while(cur){
        if(last->val <= cur->val){//如果cur比last大,则直接插到last后面
          last = last->next;
        }
        else{//如果cur比last小,则每次从头寻找插入点
              ListNode* pre = dummyHead;
              while(pre->next->val <= cur->val){
              pre = pre->next;
              }//此时pre < cur < pre->next
            last->next = cur->next;//防止cur->next链丢失,用last->next存起来,cur是在原链表上滑动
            cur->next = pre->next;
            pre->next = cur;
       }
       cur = last->next;//每次cur往后移一位
       }
       return dummyHead->next;
    }
};

LCR 029. 循环有序列表的插入

思路:分类讨论

​ 首先根据样例分为三种情况:

  1. 空链表则直接插入
  2. 链表只有一个值,直接插到后边
  3. 链表有两个以上的值:初始定义cur=head, tmp为head->next。遍历过程中每次把cur、tmp后移。过程中判断inseVal是否可以在cur、tmp之间插入,因为题目只要找到就可以,一共分为以下三种情况:
  • cur.val <= insertVal <= tmp.val
  • cur.val > tmp.val && insertVal > cur.val,此时 curtmp 分别是循环链表中的值最大的节点和值最小的节点。insertVal比最大值还大。
  • cur.val > tmp.val && insertVal < tmp.val,此时 curtmp 分别是循环链表中的值最大的节点和值最小的节点。insertVal比最小值还小。
class Solution {
public:
    Node* insert(Node* head, int insertVal) {
        Node* node = new Node(insertVal);
        if(!head) {
          node->next = node;
          return node;
        }
        if(head->next == head) {         
          head->next = node;
          node->next = head;
          return head;
        }

        Node *cur = head, *tmp = head->next;
        while(tmp != head){//定义tmp是为了方便判断走一圈
          //cur < insertVal < tmp 升序排列
          if(insertVal >= cur->val && insertVal <= tmp->val) break;
        
          if(cur->val > tmp->val){//当cur是最大值,tmp是最小值时
            //如果insertVal比最大值大,最小值小
            if(insertVal > cur->val || insertVal < tmp->val)  break;
          }
          cur = cur->next;
          tmp = tmp->next;
      }
      //以上情况都是把node插入cur、tmp之间
        cur->next = node;
        node->next = tmp;

      return head;
    }
};
posted @ 2024-11-15 22:37  七龙猪  阅读(1)  评论(0)    收藏  举报
-->