链表&矩阵

Table of Contents

链表

  • 🍁 链表
    • 🍄 PCN指针
      • 🌸 25. K个一组反转链表 [PSEN区间] [PCN指针]
      • 🌸 61. 旋转链表 [PSEN区间] [PCN指针]
      • 🌸 92. 链表的区间反转 [PSEN区间] [PCN指针]
      • 🌸 143. 重排链表 [P12指针] [CP12指针] [PCN指针]
      • 🌸 206. 反转链表 [PCN指针]
      • 🌸 234. 回文链表 [P12指针] [PCN指针]
    • 🍄 CP12指针
      • 🌸 2. 链表相加 [CP12指针]
      • 🌸 21. 合并两个有序链表 [CP12指针]
      • 🌸 23. 合并 K 个有序链表 [优先队列] [等效CP12指针]
      • 🌸 86. 分隔链表 [CP12指针]
      • 🌸 142. 环形链表 [P12指针]
      • 🌸 160. 链表相交 [P12指针]
    • 🍄 RW指针
      • 🌸 82. 删除有序链表中的重复元素 [RW指针]
  • 🍁 矩阵
    • 🍄 二分法
      • 10.09. 排序矩阵查找 [二分法]
  • 🌸 双指针
    • 🍻 数组
      • 🍻 两数之和 II – 输入有序数组 [有序数组] > [子序列和] > (合并指针)
      • 🍻 删除有序数组中的重复项 [有序数组] > [去重] > (读写指针)
      • 🍻 移除元素 [数组] > [去值] > (读写指针)
      • 🍻 移动零 [数组] > [去值] > (读写指针)
    • 🍻 字符串
      • 🍻 反转字符串 [字符串] > [反转] > (合并指针)
      • 🍻 最长回文子串 [字符串] > [回文子串] > (扩散指针)
      • 🍻 反转字符串中的单词 [字符串] > [单词匹配] > (窗口指针)
      • 🍻 无重复字符的最长子串 [字符串] > [无重子串匹配] > (窗口指针) > (哈希表)
      • 🍻 找到字符串中所有字母异位词 [字符串] > [异位词匹配] > (窗口指针) > (哈希表)
      • 🍻 字符串的排列 [字符串] > [子串匹配] > (窗口指针) > (哈希表)
      • 🍻 最小覆盖子串 [字符串] > [子串匹配] > (窗口指针) > (哈希表)
      • 🍻 重复的DNA序列 [字符串] > [重复子串寻找] > (窗口指针) > (哈希表) > (KMP算法)
      • 🍻 找出字符串中第一个匹配项的下标 [字符串] > [子串匹配] > (窗口指针) > (KMP算法)
  • 🍺 二分法
    • 🍻 框架
      • 🍻 寻找元素
      • 🍻 寻找元素边界
      • 🍻 线性函数
    • 🍻 数组
      • 🍻 二分查找 [有序数组] > [寻找元素] > (二分法)
      • 🍻 在排序数组中查找元素的第一个和最后一个位置 [有序数组] > [寻找元素边界] > (二分法)
      • 🍻 在 D 天内送达包裹的能力 [线性函数] > [寻找元素边界] > (二分法)
      • 🍻 分割数组的最大值 [线性函数] > [寻找元素边界] > (二分法)
      • 🍻 爱吃香蕉的珂珂 [线性函数] > [寻找元素边界] > (二分法)
  • 🍺 前缀和
    • 🍻 数组
      • 🥂 [笔试]. 平均数为 K 的最长连续子数组 (区间均值) (区间长度) (区间和) (前缀和) (哈希表 PRE, IDX)
      • 🥂 560. 和为 K 的子数组 (区间和) (区间个数) (前缀和) (哈希表 PRE, NUM)
      • 🍻 区域和检索 – 数组不可变 [数组] > [区间和] > (前缀和)
    • 🍻 矩阵
      • 🍻 二维区域和检索 – 矩阵不可变 [矩阵] > [子矩阵和] > (前缀和)
  • 🍺 差分
    • 🍻 数组
      • 🍻 拼车 [数组] > [区间叠加和] > (差分)
      • 🍻 航班预订统计 [数组] > [区间叠加和] > (差分)

🍁 链表

🍄 PCN指针

🌸 25. K个一组反转链表 [PSEN区间] [PCN指针]

题目:给定一个链表,{1,2,3,4}变为{2,1,4,3},k 为 2。

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* dummy = new ListNode(-1); dummy->next = head;
        ListNode* pre = dummy, * end = dummy;
        while (end->next != nullptr) {
            for (int i = 0; i < k && end != nullptr; ++i) {
                end = end->next;
            }
            if (end == nullptr) break;
            // [PRE] >> [START] >> [NODE] >> [END] >> [NEXT] >> [NODE]
            ListNode* start = pre->next, * next = end->next;
            end->next = nullptr;
            pre->next = reverse(start);
            start->next = next;
            pre = start;
            end = start;
        }
        return dummy->next;
    }

    ListNode* reverse(ListNode* head) {
        ListNode* pre =  nullptr, * cur = head;
        while (cur != nullptr) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

🌸 61. 旋转链表 [PSEN区间] [PCN指针]

题目:给定一个链表,旋转。

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        ListNode* dummy = new ListNode(-1);
        dummy->next = reverse(head);
        int size = 0;
        ListNode* cur = dummy->next;
        while (cur != nullptr) {
            ++size;
            cur = cur->next;
        }
        if (size <= 1) return head;
        k = k % size;
        // [DUMMY/PRE] >> [START] >> [NODE] >> [END] >> [NEXT] >> [NODE]
        ListNode* pre = dummy, * end = dummy;
        for (int i = 0; i < k; ++i) end = end->next;
        ListNode* start = pre->next, * next = end->next;
        // 防止 K 为 0 的情况发生
        if (start == next) return reverse(start);
        // [PRE] >> [START] >> [NODE] >> [END] || [NEXT] >> [NODE]
        end->next = nullptr;
        // [PRE] >> [END] >> [NODE] >> [START] || [NODE] >> [NEXT]
        pre->next = reverse(start);
        start->next = reverse(next);
        return dummy->next;
    }

    ListNode* reverse(ListNode* head) {
        ListNode* pre =  nullptr, * cur = head;
        while (cur != nullptr) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

🌸 92. 链表的区间反转 [PSEN区间] [PCN指针]

题目:给定一个链表,进行区间反转。

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* dummy = new ListNode(-1); dummy->next = head;
        ListNode* pre = dummy, * end = dummy;
        // [NODE] >> [PRE] >> [START] >> [NODE] >> [END] >> [NEXT]
        for (int i = 0; i < left - 1; ++i) pre = pre->next;
        for (int j = 0; j < right; ++j) end = end->next;
        ListNode* start = pre->next, * next = end->next;
        // [NODE] >> [PRE] >> [START] >> [NODE] >> [END] || [NEXT]
        end->next = nullptr;
        // [NODE] >> [PRE] >> [END] >> [NODE] >> [START] || [NEXT]
        pre->next = reverse(start);
        // [NODE] >> [PRE] >> [END] >> [NODE] >> [START] >> [NEXT]
        start->next = next;
        return dummy->next;
    }
    // 链表反转
    ListNode* reverse(ListNode* head) {
        ListNode* pre = nullptr, * cur = head;
        while (cur != nullptr) {
            // [PRE] >> [CUR] >> [NEXT]
            ListNode* next = cur->next;
            // [PRE] << [CUR] >> [NEXT]
            cur->next = pre;
            // [NULL] << [PRE] << [CUR]
            pre = cur;
            cur = next;
        }
        // [NODE] << [PRE] << [NULL]
        return pre;
    }
};

🌸 143. 重排链表 [P12指针] [CP12指针] [PCN指针]

题目:给定一个链表,{1,2,3,4,5} 变为{1,5,2,4,3}

class Solution {
public:
    void reorderList(ListNode* head) {
        ListNode* dummy = new ListNode(-1); dummy->next = head;
        // 找到中点拆成两半
        ListNode* p1 = dummy, * p2 = dummy;
        while (p2 != nullptr && p2->next != nullptr) {
            p1 = p1->next;
            p2 = p2->next->next;
        }
        // 反转链表
        p2 = reverse(p1->next);
        p1->next = nullptr;
        p1 = dummy->next;
        // 合并链表
        ListNode* cur = dummy;
        while (p1 != nullptr && p2 != nullptr) {
            cur->next = p1;
            p1 = p1->next;
            cur = cur->next;
            cur->next = p2;
            p2 = p2->next;
            cur = cur->next;
        }
        if (p1 != nullptr) cur->next = p1;
    }

    ListNode* reverse(ListNode* head) {
        ListNode* pre =  nullptr, * cur = head;
        while (cur != nullptr) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

🌸 206. 反转链表 [PCN指针]

题目:给定一个链表,进行全部反转。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr, * cur = head;
        while (cur != nullptr) {
            // [PRE] >> [CUR] >> [NEXT]
            ListNode* next = cur->next;
            // [PRE] << [CUR] >> [NEXT]
            cur->next = pre;
            // [NULL] << [PRE] << [CUR]
            pre = cur;
            cur = next;
        }
        // [NODE] << [PRE] << [NULL]
        return pre;
    }
};

🌸 234. 回文链表 [P12指针] [PCN指针]

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode* dummy = new ListNode(-1); dummy->next = head;
        ListNode *p1 = dummy, *p2 = dummy;
        // [1, 2, 3]: 2     [1, 2, 3, 4]: 2
        while (p2!= nullptr && p2->next != nullptr) {
            p1 = p1->next;
            p2 = p2->next->next;
        }
        ListNode* newHead = reverse(p1->next);
        p1 = head, p2 = newHead;
        while (p2 != nullptr) {
            if (p1->val != p2->val) return false;
            p1 = p1->next;
            p2 = p2->next;
        }
        return true;
    }

    ListNode* reverse(ListNode* head) {
        ListNode* pre = nullptr, * cur = head;
        while (cur != nullptr) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

🍄 CP12指针

🌸 2. 链表相加 [CP12指针]

题目:给定两个链表相加,如 {2, 4, 3} + {5, 6, 4} = {7, 0, 8}。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* p1, ListNode* p2) {
        ListNode* dummy = new ListNode(-1), * cur = dummy;
        int carry = 0;
        while (p1 != nullptr || p2 != nullptr) {
            int x = p1 != nullptr ? p1->val : 0;
            int y = p2 != nullptr ? p2->val : 0;
            int sum = x + y + carry;
            // 判断相加是否大于等于 10
            carry = sum / 10;
            sum = sum % 10;
            // 创建节点
            ListNode* node = new ListNode(sum);
            cur->next = node;
            cur = cur->next;
            if (p1 != nullptr) p1 = p1->next;
            if (p2 != nullptr) p2 = p2->next;
        }
        // 剩余还有进位
        if (carry == 1) cur->next = new ListNode(1);
        return dummy->next;
    }
};

🌸 21. 合并两个有序链表 [CP12指针]

题目:给定两个有序链表,合并。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* p1, ListNode* p2) {
        ListNode* dummy = new ListNode(-1), * cur = dummy;
        while (p1 != nullptr && p2 != nullptr) {
            if (p1->val < p2->val) {	// 取出 p1
                cur->next = p1;
                p1 = p1->next;
            } else {					// 取出 p2
                cur->next = p2;
                p2 = p2->next;
            }
            cur = cur->next;
        }
        // 拼接剩余的
        if (p1 != nullptr) cur->next = p1;
        if (p2 != nullptr) cur->next = p2;
        return dummy->next;
    }
};

🌸 23. 合并 K 个有序链表 [优先队列] [等效CP12指针]

题目:给定 K 个有序链表,合并。

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* dummy = new ListNode(-1), * cur = dummy;
        priority_queue<ListNode*, vector<ListNode*>, cmp> pq;
        // 依次把节点放入 pq
        for (auto list : lists) {
            if (list != nullptr) pq.push(list);
        }
        // 每次取出一个,移动一个
        while (!pq.empty()) {
            ListNode* node = pq.top(); pq.pop();
            cur->next = node;
            cur = cur->next;
            if (node->next != nullptr) pq.push(node->next);
        }
        return dummy->next;
    }

    struct cmp {
        bool operator()(ListNode* a, ListNode* b) {
            return a->val > b->val;
        }
    };
};

🌸 86. 分隔链表 [CP12指针]

题目:给定一个链表,分成大于 x 和小于 x 两部分

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        // 两条链表分别存放大于 x 和小于 x 的数
        ListNode* dummy1 = new ListNode(-1), * cur1 = dummy1;
        ListNode* dummy2 = new ListNode(-1), * cur2 = dummy2;
        ListNode* cur = head;
        while (cur != nullptr) {
            ListNode* node = cur;
            cur = cur->next;
            if (node->val < x) {
                cur1->next = node;
                cur1 = cur1->next;
            } else {
                cur2->next = node;
                cur2 = cur2->next;
            }
        }
        // 拼接起来,并且将 dummy2 的末尾置空。
        cur1->next = dummy2->next;
        cur2->next = nullptr;
        return dummy1->next;
    }
};

🌸 142. 环形链表 [P12指针]

题目:判断链表是否有环,且返回环的交点

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if (head == nullptr) return head;
        ListNode* dummy = new ListNode(-1); dummy->next = head;
        ListNode* p1 = dummy, * p2 = dummy;
        while (p2 != nullptr) {
            p1 = p1->next;
            if (p2->next != nullptr) p2 = p2->next->next;
            else  p2 = p2->next;
            // p1 p2 相遇, 一定有环
            if (p1 == p2) break;
        }
        // 此时 P2 为空, 一定无环
        if (p2 == nullptr) return nullptr;
        // 如果 p2 不为空,找环
        p1 = dummy;
        while (p1 != p2) {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;
    }
};

🌸 160. 链表相交 [P12指针]

题目:给定两个链表,判断他们是否相交

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

🍄 RW指针

🌸 82. 删除有序链表中的重复元素 [RW指针]

题目:比如{1,2,3,3,4,4,5} >> {1,2,5}

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummy = new ListNode(-1); dummy->next = head;
        ListNode* read = head, * write = dummy;
        while (read != nullptr) {
            // 找到一个非重复节点
            while (read->next != nullptr && read->val == read->next->val) {
                read = read->next;
            }
            if (write->next == read) {      // WRITE 和 READ 相邻,肯定无重复节点
                write = read;
            } else {                        // WRITE 和 READ 不相邻,肯定有重复节点
                write->next = read->next;   
            }
            read = read->next;
        }
        return dummy->next;
    }
};

🍁 矩阵

🍄 二分法

10.09. 排序矩阵查找 [二分法]

题目:给定一个排序的矩阵,找到 target。
思路:一层一层的二分查找,注意过滤无用的行。

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if (matrix.size() == 0 || matrix[0].size() == 0) return false;
        for (auto& nums : matrix) {
            if (nums[0] > target || nums[nums.size() - 1] < target) continue;
            if (search(nums, target) != -1) return true;
        }
        return false;
    }

    int search(vector<int>& nums, int target) {
        // [LEFT, RIGHT)
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            // [LEFT, MID) [MID] [MID + 1, RIGHT)
            if (nums[mid] > target) right = mid;
            else if (nums[mid] < target) left = mid + 1;
            else return mid;
        }
        return -1;
    }
};

🌸 双指针

🍻 数组

🍻 两数之和 II – 输入有序数组 [有序数组] > [子序列和] > (合并指针)

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int left = 0, right = numbers.size() - 1;
        // 二分查找
        while (left < right) {
            int sum = numbers[left] + numbers[right];
            if (sum > target) {
                // 减少最大值
                right--;
            } else if (sum < target) {
                // 增加最小值
                left++;
            } else {
                // 找到
                return {left, right};
            }
        }
        return {};
    }
};

🍻 删除有序数组中的重复项 [有序数组] > [去重] > (读写指针)

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int write = 1, read = 1;
        while (read < nums.size()) {
            if (nums[read] != nums[write - 1]) {
                // 读取的内容不重复, 写入, 读写移动
                nums[write++] = nums[read++];
            } else {    
                // 读取的内容重复, 读移动
                read++;
            }
        }
    }
};

🍻 移除元素 [数组] > [去值] > (读写指针)

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int write = 0, read = 0;
        while (read < nums.size()) {
            if (nums[read] != val) {
                // 无需去除, 写入新值
                nums[write++] = nums[read++];
            } else {
                // 去除
                read++;
            }
        }
        return write;
    }
};

🍻 移动零 [数组] > [去值] > (读写指针)

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int write = 0, read = 0;
        while (read < nums.size()) {
            if (nums[read] != 0) {
                // 无需去除, 写入新值
                nums[write++] = nums[read++];
            } else {
                // 去除
                read++;
            }
        }
        // 剩余位置补0
        while (write < nums.size()) {
            nums[write++] = 0;
        }
    }
};

🍻 字符串

🍻 反转字符串 [字符串] > [反转] > (合并指针)

class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0, right = s.size() - 1;
        while (left < right) {
            char tmp = s[left];
            s[left] = s[right];
            s[right] = tmp;
            left++, right--;
        }
    }
};

🍻 最长回文子串 [字符串] > [回文子串] > (扩散指针)

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        int max_num = 1;
        string res = s.substr(0, 1);
        // 奇数情况
        for (int i = 0; i < n; ++i) {
            int left = i - 1, right = i + 1;
            string s1 = getSub(s, left, right);
            if (s1.size() > max_num) {
                max_num = s1.size();
                res = s1;
            }
        }
        // 偶数情况
        for (int i = 0; i < n - 1; ++i) {
            int left = i, right = i + 1;
            string s2 = getSub(s, left, right);
            if (s2.size() > max_num) {
                max_num = s2.size();
                res = s2;
            }
        }
        return res;
    }

    string getSub(string s, int left, int right) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
                left--, right++;
        }
        return s.substr(left + 1, right - left - 1);
    }
};

🍻 反转字符串中的单词 [字符串] > [单词匹配] > (窗口指针)

class Solution {
public:
    string reverseWords(string s) {
        string res = "";
        int n = s.size();
        int i = 0, j = n - 1;
        while (s[i] == ' ') i++;
        while (s[j] == ' ') j--;
        s = s.substr(i, j - i + 1);
        // 滑动
        n = s.size();
        i = n - 1, j = n - 1;
        while (i >= 0) {
            // 找到第一个空格
            while (i >= 0 && s[i] != ' ') i--;
            res += s.substr(i + 1, j - i) + " ";
            // 找到下一个非空格
            while (i >= 0 && s[i] == ' ') i--;
            j = i;
        }
        // 去掉多余的空格
        return res.substr(0, res.size() - 1);
    }
};

🍻 无重复字符的最长子串 [字符串] > [无重子串匹配] > (窗口指针) > (哈希表)

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> umap;  // 滑动窗口
        int n = s.size();
        int res = 0;                    // 存放结果
        int left = 0, right = 0;        // 窗口边界
        while (right < n) {
            char cur_r = s[right++];    // 取当前字符串
            umap[cur_r]++;              // 更新窗口内容
            // 判断是否该缩小窗口, 目的是 hash 中无出现 2 次的元素
            while (umap[cur_r] > 1) {
                char cur_l = s[left++];
                umap[cur_l]--;
            }
            res = max(res, right - left);
        }
        return res;
    }
};

🍻 找到字符串中所有字母异位词 [字符串] > [异位词匹配] > (窗口指针) > (哈希表)

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int left = 0, right = 0, valid = 0;
        vector<int> res;
        // 当前窗口内容, 满足需要的内容
        unordered_map<char, int> window, need;
        for (auto ch : p) {
            need[ch]++;
        }
        // 开始滑动
        while (right < s.size()) {
            char ch = s[right++];
            if (need.count(ch)) {
                window[ch]++;
                if (window[ch] == need[ch]) {
                    valid++;
                }
            }
            // 左边界
            while (right - left == p.size()) {
                if (valid == need.size()) {
                    res.push_back(left);
                }
                // 左移
                char ch = s[left++];
                if (need.count(ch)) {
                    if (window[ch] == need[ch]) {
                        valid--;
                    }
                    window[ch]--;
                }
            }
        }
        return res;
    }
};

🍻 字符串的排列 [字符串] > [子串匹配] > (窗口指针) > (哈希表)

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        // 当前窗口内容, 满足需要的内容
        unordered_map<char, int> window, need;
        for (auto ch : s1) {
            need[ch]++;
        }
        int left = 0, right = 0, valid = 0;
        // 开始滑动
        while (right < s2.size()) {
            char ch = s2[right++];
            if (need.count(ch)) {
                window[ch]++;
                if (window[ch] == need[ch]) {
                    valid++;
                }
            }
            // 左边界
            while (right - left == s1.size()) {
                if (valid == need.size()) {
                    return true;
                }
                // 左移
                char ch = s2[left++];
                if (need.count(ch)) {
                    if (need[ch] == window[ch]) {
                        valid--;
                    }
                    window[ch]--;
                }
            }
        }
        return false;
    }
};

🍻 最小覆盖子串 [字符串] > [子串匹配] > (窗口指针) > (哈希表)

class Solution {
public:
    string minWindow(string s, string t) {
        int left = 0, right = 0;
        int start = 0, len = INT_MAX;
        // 统计符合条件的字符数
        int valid  = 0;
        unordered_map<char, int> window, need;
        for (auto ch : t) {
            need[ch]++;
        }
        // 开滑
        while (right < s.size()) {
            char ch = s[right++];
            if (need.count(ch)) {
                window[ch]++;
                if (window[ch] == need[ch]) {
                    valid++;
                }
            }
            // 缩小
            while (left <= right && valid == need.size()) {
                // 更新最优子串
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                // 移动左边界
                char ch = s[left++];
                if (need.count(ch)) {
                    if (window[ch] == need[ch]) {
                        valid--;
                    }
                    window[ch]--;
                }
            }
        }
        if (len == INT_MAX) {
            return "";
        } else {
            return s.substr(start, len);
        }
    }
};

🍻 重复的DNA序列 [字符串] > [重复子串寻找] > (窗口指针) > (哈希表) > (KMP算法)

class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        // 将字符串映射为数字数组
        vector<int> nums(s.size());
        for (int i = 0; i < s.size(); ++i) {
            switch (s[i]) {
                case 'A':
                    nums[i] = 0;
                    break;
                case 'G':
                    nums[i] = 1;
                    break;
                case 'C':
                    nums[i] = 2;
                    break;
                case 'T':
                    nums[i] = 3;
                    break;
            }
        }
        // 滑动窗口, 找相同的数字
        int left = 0, right = 0;
        // 进制, 数字长度
        int R = 4, L = 10, RL = pow(R, L - 1);
        int windowHash = 0;
        // 已经出现的字符串的哈希值
        unordered_set<int> seen; 
        // 记录重复出现的字符串, 用 set 去重
        unordered_set<string> res;
        // 开始滑动窗口
        while (right < nums.size()) {
            windowHash = windowHash * R + nums[right++]; 
            if (right - left == L) {
                if (seen.count(windowHash)) {
                    res.insert(s.substr(left, right - left));
                } else {
                    seen.insert(windowHash);
                }
                // 缩小窗口, 左边界移动
                windowHash = windowHash - nums[left] * RL;
                left++;
            }
        }
        return vector<string>(res.begin(), res.end());
    }
};

🍻 找出字符串中第一个匹配项的下标 [字符串] > [子串匹配] > (窗口指针) > (KMP算法)

class Solution {
public:
    int strStr(string haystack, string needle) {
        // 长度, 进制, 最大位权值, 模
        int L = needle.size(), R = 256, RL = 1;
        int Q = 61111;
        for (int i = 0; i < L - 1; ++i) {
            RL = RL * R % Q;
        }
        // 计算目标值的哈希
        int hashN = 0;
        for (int i = 0; i < needle.size(); ++i) {
            hashN = (hashN * R + int(needle[i])) % Q;
        }
        // 寻找哈希值相同的数
        int left = 0, right = 0;
        int hashW = 0;
        while (right < haystack.size()) {
            hashW = (hashW * R % Q + int(haystack[right++]) % Q) % Q;
            // 判断是否满足条件
            if (right - left == L) {
                if (hashW == hashN) {
                    if (haystack.substr(left, right - left) == needle) {
                        return left;
                    }
                }
                // 缩窗口, 因为 hashW - RL*s[left] 可能为负数, +Q 确保不为负
                hashW = (hashW - int(haystack[left]) * RL % Q + Q) % Q;
                left++;
            }
        }
        return -1;
    }
};

🍺 二分法

🍻 框架

🍻 寻找元素

int find(vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    // [left, right)
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] > target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            return mid;
        }
    }
    // 如果存在目标值,则左右边界确定不了
    // 如果不存在目标值,则 left == right == [target_idx]
    return -1;
}

🍻 寻找元素边界

int find_left(vector<int>& nums, int target) {
	int n = nums.size();
	int left = 0, right = nums.size();
	// [left, right)
	while (left < right) {
		int mid = left + (right - left) / 2;
		if (nums[mid] >= target) {
            right = mid;
        } else {
            left = mid + 1;
        }
	}
	// 如果数组中存在目标,则 left == right == [观测索引]
	// 如果数组中不存在目标,则 left == right == [观测索引]
	if (left < 0 || left >= n || nums[left] != target) left = -1;
	return left;
}

int find_right(vector<int>& nums, int target) {
	int n = nums.size();
	int left = 0, right = nums.size();
	// [left, right)
	while (left < right) {
		int mid = left + (right - left) / 2;
		if (nums[mid] > target) {
            right = mid;
        } else {
            left = mid + 1;
        }
	}
	// 如果数组中存在目标,则 left == right == [观测索引] + 1
	// 如果数组中不存在目标,则 left == right == [观测索引]
	if (right - 1 < 0 || right - 1 >= n || nums[right - 1] != target) right = 0;
	return right - 1;
}

🍻 线性函数

// 单调递减函数
int func(vector<int>& nums, int x);

int find_left(vector<int>& nums, int x) {
	int n = nums.size();
	int left = x_left, right = x_right;
	// [left, right)
	while (left < right) {
        int mid = left + (right - left) / 2;
        if (func(nums, mid) > target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    // 如果查找的元素存在,则 left == right == [target_idx]
    return left;
}

int find_right(vector<int>& nums, int x) {
	int n = nums.size();
	int left = x_left, right = x_right;
	// [left, right)
	while (left < right) {
        int mid = left + (right - left) / 2;
        if (func(nums, mid) > target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    // 如果查找的元素存在,则 left == right == [target_idx] + 1
    return right - 1;
}

🍻 数组

🍻 二分查找 [有序数组] > [寻找元素] > (二分法)

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        // [left, right)
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > target) {
                right = mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        // left = right = [target_idx] + 1
        cout << left << " " << right << endl; 
        return -1;
    }
};

🍻 在排序数组中查找元素的第一个和最后一个位置 [有序数组] > [寻找元素边界] > (二分法)

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = find_left(nums, target);
        int right = find_right(nums, target);
        return {left, right};
    }

    int find_left(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0, right = nums.size();
        // [left, right)
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        // 如果数组中存在目标,则 left == right == [观测索引]
        // 如果数组中不存在目标,则 left == right == [观测索引]
        if (left < 0 || left >= n || nums[left] != target) left = -1;
        return left;
    }

    int find_right(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0, right = nums.size();
        // [left, right)
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        // 如果数组中存在目标,则 left == right == [观测索引] + 1
        // 如果数组中不存在目标,则 left == right == [观测索引]
        if (right - 1 < 0 || right - 1 >= n || nums[right - 1] != target) right = 0;
        return right - 1;
    }
};

🍻 在 D 天内送达包裹的能力 [线性函数] > [寻找元素边界] > (二分法)

class Solution {
public:
    int shipWithinDays(vector<int>& weights, int days) {
        return find_left(weights, days);
    }

    // 运送量为 x 时,  天数 f(x) 为多少
    int func(vector<int>& weights, int x) {
        int day = 0;
        int sum = 0;
        for (int i = 0; i < weights.size(); ++i) {
            sum += weights[i];
            if (sum <= x) continue;
            if (sum > x) {
                day++;
                sum = weights[i];
            }
        }
        if (sum > 0) day++;
        return day;
    }

    int find_left(vector<int>& nums, int target) {
        int n = nums.size();
        int max_num = *max_element(nums.begin(), nums.end());
        int left = max_num, right = 500 * n;
        // [left, right)
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (func(nums, mid) > target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
};

🍻 分割数组的最大值 [线性函数] > [寻找元素边界] > (二分法)

class Solution {
public:
    int splitArray(vector<int>& nums, int k) {
        return find_left(nums, k);
    }

    // 当每次可以装 x 时,需要分成几份
    int func(vector<int>& nums, int x) {
        int k = 0;
        int sum = 0;
        for (int i = 0; i < nums.size(); ++i) {
            sum += nums[i];
            if (sum <= x) continue; // 还可以装,不分
            if (sum > x) {          // 装不下了,划分下一次装
                k++;
                sum = nums[i];
            }
        }
        if (sum > 0) k++;           // 最后一车
        return k;
    }

    int find_left(vector<int>& nums, int target) {
        int max_num = *max_element(nums.begin(), nums.end());
        int left = max_num, right = max_num * nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (func(nums, mid) > target) {
                left = mid + 1;
            } else if (func(nums, mid) <= target) {
                right = mid;
            }
        }
        return left;
    }
};

🍻 爱吃香蕉的珂珂 [线性函数] > [寻找元素边界] > (二分法)

class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        return find_left(piles, h);
    }

    // 吃香蕉函数
    long func(vector<int>& nums, int K) {
        int H = 0;
        for (auto num : nums) {
            H += num / K;
            if (num % K != 0) {
                H++;
            }
        }
        return H;
    }

    int find_left(vector<int>& nums, int x) {
        int n = nums.size();
        long left = 1, right = 10e9;
        // [left, right)
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (func(nums, mid) > x) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        // 如果查找的元素存在,则 left == right == [target_idx]
        return left;
    }
};

🍺 前缀和

🍻 数组

🥂 [笔试]. 平均数为 K 的最长连续子数组 (区间均值) (区间长度) (区间和) (前缀和) (哈希表 PRE, IDX)

1. 求满足 nums[i] 到 nums[j] 的均值为 K 的连续子区间
2. 将所有元素减 K,等价于求满足 nums[i] 到 nums[j] 的区间和为 0 的连续子区间
3. 定义一个哈希表,表示 {前缀和 preSum[i], 前缀和等于 preSum[i] 的索引}
4. 如果前缀和第一次出现,则添加
5. 如果前缀和第二次出现,则将其与先前的前缀下标计算区间距离
// pre[j] - pre[i] = 0, i < j, j = cur
// pre[j] = pre[i], i < j, j = cur
class Solution {
public:
    void maxLen(vector<long>& nums, long mean) {
    	int size = nums.size();
        unordered_map<long, long> preSum;  // 前缀和数组 (前缀和, 索引)
        long res = 0, cur_sum = 0;
        // 构建新数组
        for (auto& num : nums) num -= mean;
        // 构造前缀和数组
        preSum[0] = 0;
        for (int i = 0; i < size; ++i) {
            cur_sum += nums[i];
            // 找到有出现过
            if (preSum.find(cur_sum) != preSum.end()) {
                max_len = max(max_len, i + 1 - preSum[cur_sum]);
            } else {
                preSum[cur_sum] = i + 1;
            }
        }
        if (res == 0) res = -1;
        cout << res;
    }
};

🥂 560. 和为 K 的子数组 (区间和) (区间个数) (前缀和) (哈希表 PRE, NUM)

1. 求从 nums[i] 到 nums[j] 的区间和为 K 的区间个数
2. 等效于求 preSum[j] - preSum[i] == K 的区间个数
3. 将 j 从 0 到 size 遍历,依次求 i 中满足 preSum[preSum[j] - K] 的个数
4. 定义一个哈希表,表示 {前缀和 preSum[i], 前缀和等于 preSum[i] 的个数}
5. 将这和符合条件的个数累加起来,得到结果
// pre[j] - pre[i] = k, i < j
// pre[i] = pre[j] - k, i < j, j = cur
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int size = nums.size();
        int cur_sum = 0;
        int count = 0;
        unordered_map<int, int> prefixSum;  // [前缀和, i 满足次数]
        prefixSum[0] = 1;
        for (int i = 0; i < size; ++i) {
            cur_sum += nums[i];
            // 找有前缀和为 sum - k 的 i
            if (prefixSum.find(cur_sum - k) != prefixSum.end()) {
                count += prefixSum[cur_sum - k];
            }
            // 更新 j 的前缀和数组
            prefixSum[cur_sum]++;
        }
        return count;
    }
};

🍻 区域和检索 – 数组不可变 [数组] > [区间和] > (前缀和)

class NumArray {
private:
    vector<int> nums;
    vector<int> preSum;
public:
    NumArray(vector<int>& nums) {
        this->nums = nums;
        preSum.resize(nums.size());
        preSum[0] = nums[0];
        for (int i = 1; i < preSum.size(); ++i) {
            preSum[i] = preSum[i - 1] + nums[i];
        }
    }
    
    int sumRange(int left, int right) {
        return preSum[right] - preSum[left] + nums[left];
    }
};

🍻 矩阵

🍻 二维区域和检索 – 矩阵不可变 [矩阵] > [子矩阵和] > (前缀和)

class NumMatrix {
private:
    vector<vector<int>> matrix;
    vector<vector<int>> preSum;
public:
    NumMatrix(vector<vector<int>>& matrix) {
        this->matrix = matrix;
        int m = matrix.size(), n = matrix[0].size();
        preSum = vector<vector<int>>(m + 1, vector<int>(n + 1, 0));
        for (int i = 1; i < m + 1; ++i) {
            for (int j = 1; j < n + 1; ++j) {
                preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1]
                    - preSum[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return preSum[row2 + 1][col2 + 1] - preSum[row2 + 1][col1]
            - preSum[row1][col2 + 1] + preSum[row1][col1];
    }
};

🍺 差分

🍻 数组

🍻 拼车 [数组] > [区间叠加和] > (差分)

class Solution {
public:
    bool carPooling(vector<vector<int>>& trips, int capacity) {
        int start = INT_MAX, end = 0;
        // 寻找边界
        for (auto trip : trips) {
            if (trip[1] < start) start = trip[1];
            if (trip[2] > end) end = trip[2];
        }
        int len = end - start + 1;
        vector<int> diff(len);
        vector<int> res(len);
        // 因为到站了下车, 所以应该在 diff[to] 处减, 而不是 diff[to+1]
        for (auto trip : trips) {
            diff[trip[1] - start] += trip[0];   // 上车
            diff[trip[2] - start] -= trip[0];   // 下车
        }
        // 计算柱状图
        res[0] = diff[0];
        if (res[0] > capacity) return false;
        for (int i = 1; i < end - start + 1; ++i) {
            res[i] = res[i - 1] + diff[i];
            if (res[i] > capacity) return false;
        }
        return true;
    }
};

🍻 航班预订统计 [数组] > [区间叠加和] > (差分)

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        int start = 1, end = n;
        vector<int> diff(n);    // 差分
        vector<int> res(n);     // 叠加
        for (auto book : bookings) {
            diff[book[0] - start] += book[2];
            if (book[1] < n) {
                diff[book[1] - start + 1] -= book[2];
            }
        }
        res[0] = diff[0];
        for (int i = 1; i < n; ++i) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
};

版权声明:本文为博主作者:進擊的小老虎原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/qq_39547794/article/details/135620915

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2024年4月16日
下一篇 2024年4月16日

相关推荐

此站出售,如需请站内私信或者邮箱!