11.22-双指针

3233. 统计不是特殊数字的数字数量

思路:埃式筛选法+前缀和

思考方法,可以写一个简单的程序看看题目中提到的特殊数字有没有什么规律:

image-20241122173431814

正难则反,统计区间 [l,r] 内有多少个特殊数字==统计[l , r]内有多少质数的平方

这等价于区间 [0,r] 内的特殊数字个数,减去区间 [0 , l−1] 内的特殊数字个数。(想到前缀和)

根据题意,只有质数的平方 \(p ^2\) 才是特殊数字,因为 \(p ^2\) 恰好有两个真因数 1 和 p。而其他的数,1 没有真因数,质数只有 1 个真数,不是 1 不是质数也不是质数平方的数有至少三个真因数。

所以区间 [0,i] 内的特殊数字个数等于:区间 [ 0,\(\sqrt{i}\)​] 中的质数个数。

埃式筛选法:

image-20241122174802208

每次从i*i开始,去掉当前i的倍数,标记为合数(-1)。

预处理 \(⌊ \sqrt{10^9}⌋\)=31622 内的质数,然后用前缀和计算 [0,i] 中的质数个数 π(i),那么区间 [l,r] 内的特殊数字个数就是\(π(\lfloor r \rfloor)−π(\lfloor l−1 \rfloor)\)
答案为区间内的整数个数,减去区间内的特殊数字个数,即\(r - l + 1 -(π(\lfloor r \rfloor)−π(\lfloor l−1 \rfloor))\)

const int MX = 31622;
int pi[MX + 1];

auto init = [] {
    for (int i = 2; i <= MX; i++) {
        if (pi[i] == 0) { // i 是质数
            pi[i] = pi[i - 1] + 1;
            for (int j = i * i; j <= MX; j += i) { // 注:如果 MX 比较大,小心 i*i 溢出
                pi[j] = -1; // 标记 i 的倍数为合数
            }
        } else {
            pi[i] = pi[i - 1];
        }
    }
    return 0;
}();

class Solution {
public:
    int nonSpecialCount(int l, int r) {
        return r - l + 1 - (pi[(int) sqrt(r)] - pi[(int) sqrt(l - 1)]);
    }
};

328. 奇偶链表

思路:模拟

定义奇数指针even=head,偶数指针odd=head->next,当然先判断特殊情况空链表和1个节点的情况。

然后当even后面至少还有两个节点(后面至少还有一个奇节点时)循环进行。

even跳一个:even->next = odd->next;

odd也跳一个: odd->next = even->next->next;//当然这里可能为null

然后依次后移,最后把记下的偶数头odd1接到even后。

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
      if(!head || !head->next) return head;
        ListNode* even = head;
        ListNode* odd = head->next;
        ListNode* odd1 = head->next;
        while(even && even->next && even->next->next){
          even->next = odd->next;
          odd->next = even->next->next;
          even = even->next;
          odd = odd->next;
        }
        even->next = odd1;
        return head;
    }
};

86. 分隔链表

思路:

考虑通过「新建两个链表」实现原链表分割,算法流程为:

  1. 新建两个链表 sml_dummy , big_dummy ,分别用于添加所有「节点值 <x 」、「节点值 ≥x 」的节点。
  2. 遍历链表 head 并依次比较各节点值 head.val 和 x 的大小:
    head.val < x ,则将节点 head 添加至链表 sml_dummy 最后面;
    head.val >= x ,则将节点 head 添加至链表 big_dummy 最后面;
  3. 遍历完成后,拼接sml_dummybig_dummy 链表。
  4. 最终返回头节点 sml_dummy.next 即可。
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
      ListNode* smlDummy = new ListNode(0);
      ListNode* bigDummy = new ListNode(0);
      ListNode *s = smlDummy , *b = bigDummy , *cur = head;
      while(cur){
        if(cur->val < x) {
          s->next = cur;
          s = s->next;
        }
        else {
          b->next = cur;
          b = b->next;
        }
        cur = cur->next;
      }
      s->next = bigDummy->next;
      b->next = NULL;
      return smlDummy->next;        
    }
};

160. 相交链表

思路:先走长度差步

求出A、B的链表长度n1、n2,保证n1 < n2(如果不是就交换),然后让curB提前走gap=n2 - n1步,然后curA与curB一起走,每步判断节点是否相同。

class Solution {
private:
    int lenth(ListNode* head){
      ListNode* cur = head;
      int n = 0;
      while(cur){
        n ++;
        cur = cur->next; 
      }
      return n;
    }
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
       int n1 = lenth(headA) , n2 = lenth(headB);
       if(n1 >= n2){
        swap(headA , headB);
        swap(n1 , n2);
       }
       int gap = n2 - n1;
       ListNode* curA = headA;
       ListNode* curB = headB;
       while(gap --)  curB = curB->next;
       while(curA != curB){
        curA = curA->next;
        curB = curB->next;
       }
       return curA;
    }
};

另解:两表拼接消除长度差

设「第一个公共节点」为 node ,「链表 headA」的节点数量为 a ,「链表 headB」的节点数量为 b ,「两链表的公共尾部」的节点数量为 c ,则有:

头节点 headA 到 node 前,共有 a−c 个节点;头节点 headB 到 node 前,共有 b−c 个节点;

Picture1.png

考虑构建两个节点指针 A , B 分别指向两链表头节点 headA , headB ,做如下操作:

指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:a+(b−c)

指针 B 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:b+(a−c)

如下式所示,此时指针 A , B 重合,并有两种情况:a+(b−c)=b+(a−c)

  • 若两链表 有 公共尾部 (即 c>0 ) :指针 A , B 同时指向「第一个公共节点」node 。
  • 若两链表 无 公共尾部 (即 c=0 ) :指针 A , B 同时指向 null 。
    因此返回 A 即可。

代码解释:为什么把链表连起来,就可以得到相交的部分。

首先是两个链表(约定,值相同代表同一节点,0 代表空节点) A表:[1, 2, 3, 7, 8, 9] B表:[4, 5, 7, 8, 9]

连接两个链表(表与表之间用 0 隔开)

AB表:[1, 2, 3, 7, 8, 9, 0, 4, 5, 7, 8, 9, 0]

BA表:[4, 5, 7, 8, 9, 0, 1, 2, 3, 7, 8, 9, 0]

观察连接后的两个表,可以发现相交的部分整齐的排列在末尾, 只需要逐个比较这两张表的节点,就能找到相交的起始位置。

如果没有相交会如何?会陷入死循环吗? A表:[1, 2, 3] B表:[4, 5] 连接两个链表(表与表之间用 0 隔开) AB表:[1, 2, 3, 0, 4, 5, 0] BA表:[4, 5, 0, 1, 2, 3, 0] 观察连接后的两个表,可以发现末尾相交的部分必然为空, 参照上面的逻辑,返回首个相同的节点,为空是符合题意的。

如果连接两表时,不用 0 隔开,表不相交时,就会陷入死循环。 但是写代码时,不可能往链表中插入空节点,所以就用一个指针,模拟遍历两个相交表的过程,当指针指向空时,重新指向另一个链表的头节点,否则就指向下一个节点。 思考一下,如果两个链表长度相等会如何? 可以试着在纸上画一下~

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *A = headA, *B = headB;
        while (A != B) {
            A = A != nullptr ? A->next : headB;//走完A表之后开始遍历B,相当于把A、B连起来
            B = B != nullptr ? B->next : headA;
        }//最后如果A、B都走完必然都为空,跳出循环返回NULL。
        return A;
    }
};
posted @ 2024-11-22 22:19  七龙猪  阅读(1)  评论(0)    收藏  举报
-->